Documentation
Download Latest Version Index History XCell Classes Extended Name Attributes
CapeSoft Logo

CapeSoft xFiles 4
Documentation

Installed Version Latest Version

Introduction

CapeSoft xFiles is a collection of classes written in Clarion to deal with XML formatted text.It includes both a Stream-based parser, and also a Tree-based parser. Stream-based parsers are optimized for speed, Tree-based parsers are slower, but allow extra functionality. By including both, xFiles allows you to choose the correct parser for the correct situation.

xFiles is tightly integrated to Clarion data structures, meaning that it understands Tables (Files), Views, Queues and Groups. Creating XML from these structures, or consuming XML into these structures is very straight-forward. Nested structures (Groups and Queues inside other Groups and Queues) are supported, as are Views with multiple nested Joins. (Views are Read Only, you can create XML from a View, but you don't read XML into a VIEW).

XML is used in many different places as a way to exchange data between programs. Some of the more common structures (SOAP for web services, and OpenSpreadsheet XML for Excel Spreadsheets) are also included.

xFiles is primarily used by writing code in embeds, but it is also integrated into other tools, like NetTalk (for creating SOAP-based Web Services), SendTo (for creating spreadsheet XML files) and so on.

xFiles originally started with a streaming parser, which emphasizes performance. xFiles 4 adds a tree parser which allows a much finer control over the content of the XML. In this documentation it's important to know when a section applies to the streaming parser, the tree parser, or both.

Requirements

xFiles 4 requires StringTheory 3 and Reflection (which is a free tool).
 

Webinar

Introducing xFiles 4 webinar is at ClarionLive, #674.
 

Online Code Generator

xFiles makes possible an online Clarion Code generator for XML loading and saving. This tool inspects some same XML that you provide, and then generates appropriate Clarion structures, and xFiles code, for creating or consuming that XML. This greatly reduces the time it takes to use XML in your program.

Upgrading From xFiles 3

Upgrading from xFiles 3 to xFiles 4 has been designed to be as painless as possible. In most cases the upgrade will only take a few seconds. However the following should be noted;
  1. xFiles 4 makes use of the StringTheory and Reflection accessories. The global extensions for these need to be added to apps that have the xFiles global extension.
  2. Hand-coded apps should note updated instructions.
  3. There are new classes available, so check out the section Overview of the Classes to see what class is most appropriate to your situation. This also lists the different default settings between xFileXML and xFilesStream classes.

Converting an object from xFileXML to xFilesStream

The xFileXML class is derived from the xFilesStream class, and exists only to provide backward compatibility. It is recommended to change objects from xFileXML to xFilesStream as they are encountered. When doing this there are some minor updates to the code that may be required.

Overview of xFiles 4 New Features

This section serves as a quick primer for those programmers used to xFiles 3 (and earlier). It highlights important new features, and refers to documentation on those features. Note 1: the xFileXML class has been renamed to xFilesStream. However the old name, xFileXML has been kept for backwards compatibility, and it will still work. For new work don't use that name though.

What is XML?

XML is an acronym for Extensible Markup Language. It is an open standard for describing data in a standard, easy to use, format.

XML uses a similar tag structure as HTML, however XML tags define what elements contain, and not the format for displaying them. While HTML uses predefined tags, XML allows tags to be defined by the developer. This makes XML extremely flexible, while still being easy to use. XML is an excellent format for transferring and storing information.

XML is more rigid than HTML, it is not tolerant of errors, XML documents need to be "well formed", which means they must comply with fairly rigid rules.

Advantages of XML

For more information visit http://www.w3.org/XML/. XML definition and information based on content from http://www.answers.com/topic/xml.

XML versus JSON

XML has been around a long time, and has grown to be a large complicated beast. XML structures can get very complicated, and unwieldy. It is also fairly verbose, every tag name being repeated twice. To go back to a simpler time JSON was invented, and JSON deliberately does not include almost all the complexity of XML. JSON has been created as a simple data specification, nothing else. On the one hand it lacks all the bolt-on features XML has acquired. On the other it lacks all the bolt-on complexity that XML has acquired.

In most situations, if JSON is an available option then it is preferred by developers. It is somewhat more compact than XML, easier to read, faster to parse and so on. However JSON lacks the ubiquity of XML, and does not have the rich structure that XML offers.

Overview of the Classes

During this document several main classes are described. It's important to understand how the classes are different, and what their purpose is. Mixing up the documentation for one class, when another class is actually being used, will lead to frustration. Some of the classes listed below are split into multiple actual working classes, however the ones listed below are the ones you will make use of in your program.
Primary Classes
Class Use Case
xFilesStream The original xFiles class , this is a streaming parser. Allows the creation and consumption of Clarion data structures; Tables (aka files), Views, Queues and Groups. Use this for simple XML or Clarion structures where speed is the primary goal. Not recommended for Queues-In-Queues, or Queues-In-Groups structures. Some of the property default values in this class are different to the xFileXML class - see below for details.
xFilesTree
New with xFiles 4 this is a tree-based parser. Allows the creation and consumption of Clarion data structures; Tables (aka files), Views, Queues and Groups. Used for the more complicated XML structures it supports unlimited nesting and so on. Has more features than the streaming parser, and is easier to use, but at the cost of performance.
XmlTree New to xFiles 4, this class allows you to read, manipulate and write XML as a "tree" of XML nodes. This class has no knowledge of Clarion structures, but allows you to easily build, or manipulate an XML Tree. The xFilesTree class above is derived from this, so all the methods in here are available in the above class as well.
xFileXML This is derived from the xFilesStream class. It allows existing code to compile, and also preserves some the default property values from xFiles 3.
Property xFileXML Default xFilesStream / xFilesTree Default
MaxPrefixLengthInXml
(includes colon)
4 7
ReplacementChar . _
SaveEncoding ISO-8859-1 UTF-8
TagCase xf:CaseAny xf:CaseAsIs
Other Changes:
  • ValidateUpdateRecord is no longer called before AddQueueRecord. This call was probably a bug. ValidateRecord IS called as always.
Useful Classes
Class Use Case


XmlClarionClass A class for digesting sample XML, and generating Clarion code for it. In other words it writes Clarion data structures and code, when given an example XML file as input.
xFilesXsdClass A class for consuming and creating XSD files.
xCell
This class is used to create (not consume) XML files which conform to the OpenSpreadSheet XML file format. These files can be opened in Excel, Open Office, Libre Office and so on.
xFileSetting
Performs a similar role to an INI file in your program. It allows you to specify a settings file name, and then lets you read and write settings. The settings are cached internally in a queue. They can be saved on demand, and are automatically saved when the object goes out of scope.

Adding xFiles to an APP

Global Extension

To Activate xFiles in an application go to the Global Extensions for the App, and add the Activate CapeSoft xFiles global extension.
On the Options tab you can set default options for the local extensions, but usually the default settings are left as-is.

If this is a multi-dll system then xFiles is usually exported from one of the apps. On this app go to the Multi-DLL tab and make sure both options are ticked on. This is usually done in the Data (or Root) DLL - the one which is exporting all the File Declarations.

In all the other apps in the system (EXE's and DLL's) go to the Multi-DLL tab and make sure only the first option is ticked on. (For other DLL's this will be the default setting, but remember to do it for the EXE apps as well.)

Local Extension

The local extension is an easy way to declare an object in a procedure. You can add as many of these as you like (each with a unique Object name) to a procedure. This approach is only necessary if you need to add code to any of the methods (ie create a derived object.) If you are just going to use the object, as in most of the examples in this document, then you can just declare the object in embed code. For example;

xml  xFilesTree

Adding xFiles to a Hand-Code Project

To add xFiles to a hand-coded project (with no APP and hence no Global Extension template) do the following;
  1. Add
    include('Reflection.Inc'),Once
    include('StringTheory.Inc'),Once
    include('xFiles.Inc'),Once

    to your main module
  2. Add the xFiles, Reflection and StringTheory defines to your project. Set

    xFilesLinkMode=>1;StringTheoryLinkMode=>1;
    ReflectionLM=>1;xFilesDllMode=>0;StringTheoryDLLMode=>0;ReflectionDM=>0

    if the classes should be linked into the project, and

    xFilesLinkMode=>0;StringTheoryLinkMode=>0;ReflectionLM=>0;xFilesDllMode=>1;StringTheoryDLLMode=>1;ReflectionDM=>1


    if the object is exported from another DLL.

    Also make sure the _ABCDllMode_ and _ABCLinkMode_ defines are set appropriately for the project.
  3. When you want to declare an xFiles object in a procedure you can declare it using the class name. For example;

    xml  xFilesStream

    If you wish to derive methods (see methods documentation) then declare it with the method (for example)

    xml         class(xFilesStream)
    AssignField   Procedure (Long DataStartPos,Long DataEndPos),Long, derived
                End


    and then add the derived procedure (adding your own code)

    xml.AssignField Procedure (Long DataStartPos,Long DataEndPos)
      Code
      parent.AssignField(DataStartPos,DataEndPos)


    For a full list of available classes and method declarations see the xfiles.inc file.

Encoding: Unicode and ANSI

This is a section you probably won't find interesting to begin with, but it's a section you will need to return to once you start consuming or creating XML.

Hint: If the subject of encodings, mappings, unicode or ANSI are unfamiliar to you, then the ClarionLive webinar #583 is a recommended watch. The following will make a lot more sense if you have a grounding in string encoding.

XML files can be encoded in either ANSI or Unicode mappings, using code-page, utf-8 or utf-16 encodings. By contrast Clarion applications (as at the time this is written) work strictly in ANSI, with various font Charsets. xFiles has a number of default behaviors, which you can override should you need to do so.

XML files typically specify their format as a part of the declaration line at the top of the file.

<?xml version = "1.0" encoding = "UTF-8" >

Loading

If no encoding is set in the incoming XML file, then the incoming format is assumed to be UTF-8.

Since Clarion is ANSI, incoming data will be converted from UTF-8 to ANSI automatically. If you do NOT want this conversion to happen then call

xml.SetDontUTFDecode(true)

When converting from UTF to ANSI, a Code Page is used. xFiles will try to determine the code page of your application by interrogating the system{prop:charset} property, when the XML object is constructed. If you wish to select a different code page you can call either

xml.SetCodePage(someCharSet)

supported CharSet values are; CHARSET:ANSI, CHARSET:GREEK, CHARSET:HEBREW, CHARSET:ARABIC, CHARSET:BALTIC, CHARSET:CYRILLIC, CHARSET:TURKISH, CHARSET:EASTEUROPE, CHARSET:THAI, CHARSET:CHINESEBIG5.

or

xml.SetCodePage( , someCodePage)

Note the leading , character in the parameter list. Any Windows Code Page value can be used here. The most common values are declared in StringTheory.inc, see, for example, st:CP_WINDOWS_1252.

After executing a load, the GetLoadEncoding method will return a string indicating the format that the XML arrived as.

encoding = xml.GetLoadEncoding()


Saving

By default, XML output from the xFilesStream and xFilesTree classes are encoded using UTF-8.  the old xFileXML class defaults to ISO-8859-1.
This default can be overrident by calling the SetSaveEncoding method.

xml.SetSaveEncoding('ISO-8859-2')

Supported encodings are; ISO-8859-1 through ISO-88509-9, UTF-8, UTF-16,

NOTE: The xFileStream Class does not support utf-16. If UTF-16 output is required, use the xFilesTree class. Currently If UTF-16 or UTF-32 is requested from the xFileStream class, then it'll create a UTF-8 encoded output. This may change in future releases, so should not be relied upon.

xFilesTree Class

The xFilesTree class is different to the streaming class, because there is an extra step along the way. The streaming class takes the data from the source, and creates the XML output. The codepage, and encoding, can easily be set.

The tree class is different because there is this interim structure - the tree. The data flows from the source to the tree, to the destination. So the source can be encoded in one way, and the destination another, but it might be better for the tree to follow the source, or the tree to follow the destination. Because either way may be preferable, there is a property to set the tree format - to match either that of the XML encoding, or the Clarion encoding. This property is called TreeEncoding, and it defaults to xf:clarion. It can be set to xf:xml using the SetTreeEncoding method.

xml.SetTreeEncoding(xf:xml)

This property should not be set like this for UTF-16. The tree does not support UTF-16, so setting the tree encoding to xf:xml, when the XML is UTF-16, will cause it to fail.

Extended Name Attributes

Before beginning with explanations of how to use xFiles to create and consume XML, it's worth taking a moment first to discuss Clarion data structures, and the use of Extended Name Attributes.

Clarion has 4 primary data structures (GROUP, QUEUE, FILE, VIEW) which xFiles uses as the source, and/or destination, of XML conversions. Indeed the primary use case of xFiles is to convert these structures to an XML string (or file on disk), or to import XML from a string or file into one of these structures.

In the past matching XML to these structures could be difficult because XML has some features that Clarion does not, and vice versa. Often code was necessary in embed points (or derived methods) to add additional information to xFiles so it could do the import, or export, as you desired. For example formatting a date (a LONG in Clarion) into a formatted string in the XML, or deformating the date when importing the XML.

In 2019 a new approach to feeding information to generic code was proposed on ClarionHub. The core of the suggestion was to extend the use of the NAME attribute (something Clarion was already doing in some places) to act as a more generic extension of the Clarion data structures. This was followed up with the release of CapeSoft Reflection as a free implementation of those ideas in 2021.

xFiles 4 takes these ideas on board, and full support for extended name attributes, using the Reflection class, has been added both to the existing xFiles streaming parser, and the new xFiles Tree parser. Spending a few minutes understanding this approach will greatly simplify your use of xFiles. xFiles makes reading and writing XML trivial, IF you have the right structures, and the correct Extended Name Attributes in place.

Hint: The Name attribute is limited to 100 characters long by the Clarion language.

For example; Take a simple Clarion queue

INVOICEQUEUE     Queue
CUSTOMER             String(100),name('Customer')
DATE                 Long,name('Date')
PAID                 Byte,name('Paid')
SIGNATURE            String(1024),name('Signature')
                End


In the above example some important information is already in the Name attribute - specifically the case of the tag name to use in the XML. (Hint: in Clarion Labels are case insensitive, Names are not.) But using extended names, this can be taken further;

INVOICEQUEUE    Queue
CUSTOMER             String(100),name('Customer')
DATE                 Long,name('Date | @d6')
PAID                 Byte,name('Paid | attribute')
SIGNATURE            &StringTheory),name('Signature | StringTheory | Cdata')
                End


A full list of the extended attributes supports are;
Attribute Description
[types] Byte, Bfloat 4, Bfloat8, Decimal, pDecimal, Long, Ulong, String, Cstring, PString, Signed, Unsigned, Word, Dword, Real, Sreal, Short, UShort,  Clarion data types. Typically they do not need to be set, they will be detected, but they can be included, and are valid attributes.
@Picture A Clarion (Extended) picture which will be used for formatting the XML when creating, or deformating when importing. Supports Extended Pictures (as supported by StringTheory) 
Attribute
Only applies to creating (not loading) XML. Marks the field as an attribute of the parent row boundary.
Attribute(Field)
Only applies to creating (not loading) XML. Marks the field as an attribute of another field. The Field part is Case Sensitive.
Base64
The field will be Base 64 encoded before saving into XML. When loading the field will be base 64 decoded before writing into the Clarion structure.
Binary, Bin Does the same as Base64. Deprecated (but still works.) Rather use base64. See Base64 above.
Boolean XML values will be set to true or false. Clarion value to be 0 or 1 to match.
Cdata
Only applies to creating XML. The field will be marked as CDATA when saving as XML. Does not apply to attribute fields. When loading, if the incoming XML is marked as CDATA then it is automatically handled.
Private Only applies to creating XML. The field will not be exported to XML. See also ReadOnly below.
Queue The field is a reference to another queue type. This option is only supported by the tree parser (xFilesTree), not the streaming parser (xFilesStream). When exporting, the reference is followed, and the data in the queue added to the XML.
ReadOnly Only applies to consuming XML. The field contents in the structure will not be set from the XML. See also Private above.
Rename The tag name to use in the XML is different to the External Name that is set. In other words this overrides the External Name tag.
Setting the value of rename to 0, Rename(0), means the tag name is blank. (In XML tags are not allowed to start with a digit.) However using Rename(0) is not recommended, use XMLName(0) instead, because some outputs (like JSON) do not allow for blank names.
Required Only applies to creating XML. Fields that are required are included in the export, even if they are blank (or 0).
RowName Only for xFilesTree, only for Queue reference fields. (See Queue above). Specifies the tag for each row in the Queue. Can be blank. eg
Rowname() or RowName(product).
[types] StringJson, CstringJson, Treated as a String, Cstring
[types] StringPtr, CstringPtr, PStringPtr,
StringJsonPtr, CStringJsonPtr
The field is a pointer (&string, &cstring, &pstring respectively. On export the contents of the field will be exported, as for a string. On import the pointer will be NEWed (if not set) and the field populated.
[types] StringTheory, StringTheoryJson
The field in the structure is a reference to a StringTheory object. The contents of the object will be used when exporting, and when importing the object will be NEWed (if necessary) and populated.
[types] StringXML, CstringXML, StringXMLPtr, CstringXMLPtr, StringTheoryXML The field in the structure is a string, but contains (valid) XML data. For export it is injected into the XML "as is". For import it is read into the field "as a string". ie the contents of this part of the XML document are not separated into different fields.
Table A pointer to a FILE structure. (&File). Export Only.
View A pointer to a TYPE structure (&View). Export Only.
XMLName
The tag name to use in the XML is different to the External Name and the Rename that is set. In other words this overrides the External Name, and Rename tags. Setting the value of XMLName to 0, XMLName(0), means the tag name is blank. (In XML tags are not allowed to start with a digit.)

Hint: The Name attribute is limited to 100 characters long by the Clarion language.

Setting Attributes at Runtime

In some cases it's not possible to set the attributes for the structures where the structure is declared. In these situations you can set the attributes at runtime.

Setting these attributes makes use of the Reflection object, which is a property of the xFilesTree and xFilesStream classes. You can call methods for this object using the syntax

xml.reflection.whatever

The Reflection class is documented at https://capesoft.com/accessories/reflectionsp.htm .

Most of the reflection methods take a GroupName and a ColumnName. Figuring out these names can be tricky, so it's best not to try too hard - and simply ask the class to tell you. In other words, before embarking on the process of figuring out the correct reflection calls, add this line AFTER your call to xml.Save, or xml.Load.

xml.Reflection.Walk()

This call sends the list of group names, and column names to DebugView++. If you run that utility on your computer, and then get to the code that calls Walk, you'll see what the Reflection class figured out. And then using the names there you can supplement it. For example;

[rf]FIELD: GroupName=[queue] ColumnName=[date] Num=[1] Type=[rf:NotSet] Rename=[] Pic=[] Attributes=[DATE]
[rf]FIELD: GroupName=[queue] ColumnName=[time] Num=[2] Type=[rf:NotSet] Rename=[] Pic=[] Attributes=[TIME]


In the above output you can see the group name is queue and the Column names are date and time.

Once you know this you can add calls to the xml.reflection methods AFTER the call to SetTagCase and BEFORE the call to xml.Save or xml.Load.

If you want to Override attributes that exist (ie that have been set in the field Extended Name) then start with a call to

xml.reflection.Parse(groupname,structure)

If you are just going to supplement the field information then you can skip the above line.

There are a number of Set methods you can use, as listed in the table below;

Method Use
xml.reflection.SetAttributes
Sets multiple attributes for a field. This method takes a string, exactly as you would use it in the Name attribute for the field.
xml.reflection.SetAttributes('queue','date','date | @d6 | rename(datum)')
xml.reflection.SetAttribute Sets a single attribute for a field.
xml.reflection.SetAttributes('queue','date','private')
xml.reflection.SetPicture Sets the picture for a field
xml.reflection.SetPicture('queue','date','@d6')
xml.reflection.SetRename Sets the name of the tag in the output
xml.reflection.SetRename('queue','date','datum')
xml.reflection.SetType Sets the type of the field in the output
xml.reflection.SetType('queue','date','datum')

Using xFiles In Code

The declaration and use of an xFiles object in code always follows the same pattern. First the declaration;

xml  xFilesTree

The label on the left (the object name) and the class on the right. If in doubt use xFilesTree. Another option is xFilesStream. You can read more about the differences in Overview of the Classes .

when it comes time to use the object, there are typically at least two setup lines, and some other possible options, then the action. The two lines are

xml.start()
xml.SetTagCase(xf:case)


The Start method "clears" the object, so it can be reused if it has been used already. It's a good habit to always call Start. The SetTagCase method tells the obect what case the tags should be. Valid options are xf:caseLower, xf:caseUpper, xf:CaseAsIs and xf:CaseAny.

next come any optional settings you want to set. For example;

xml.SetOmitXMLHeader(true)

Then finally the action you want to achieve - typically a Load or Save.

xml.Save(SomeClarionStructure,'somefile.xml','fileBoundary','RecordBoundary')

Put it all together and you end up with something like this;

xml  xFilesTree
  code
  xml.start()
  xml.SetTagCase(xf:CaseAsIs)
  xml.Save(InvoicesQueue,'somefile.xml','invoices','invoice')


Reading XML

Things you need to know to load XML

XML Files come in all shapes and sizes, but reading them into a Clarion structure can usually be done with a few lines of code. In additional the File can be either stored on disk, or already be in RAM as a string, or a StringTheory object. Importing from any of these is trivial. Creating XML is also trivial.

Creating the xml object in your procedure.

You can use the xFiles extension template to add an XFiles object to your procedure. Or you can hand-code the object declaration in the data section of your procedure. It'll look something like this;

xml   xFilesStream

Should I create an xFilesStream object, or an xFilesTree object?

If in doubt create an xFilesTree object. This has more features, and is able to work more easily across more complex structures.

If the XML you are going to load (or save) is very simple, and does not contain queues inside groups (or other queues) then either class will be fine, but the xFilesStream class should be faster.

If you are using the online code-writer (capesoft.com/xfilescode) then it will likely generate code for a xFilesStream object.

Matching the field names to the XML.

The secret to the simplicity of the Load is that the NAME (or External Name property) of the fields you are loading matches the XML tags in the XML file. For example, if you have XML that looks like this:

<xml>
    <server>www.capesoft.com</server>
    <port>80</port>
</xml>

then your group should have the same field names:

Settings    Group
Server        String(80),name('server')
Port          Long,name('port')
            End


xFiles simply matches up the names and the tags, and copies the data across for you. XML tag names are case sensitive (Clarion Labels are not) so use a Name attribute for the field is a good idea. (If you are new to the distinction between Labels and Names then see ClarionHub here.)

In some cases the XML tags have names that are not legal Clarion field names. Or you may just want to change the tag name to match your field name. In this case set the Name attribute to match the tag name. For example:

<xml>
    <web-server>www.capesoft.com</web-server>
    <web-port>80</web-port>
</xml>

then in your group set the Name attributes;

Whatever    Group
Server        String(80),Name('web-server')
Port          Long,Name('web-port')
            End

Attributes

XML allows attributes to be assigned to a tag. For example;

<xml>
    <server protocol="http">www.capesoft.com</server>
    <port>80</port>
</xml>

This is equivalent to;

<xml>
    <server>www.capesoft.com</server>
    <protocol>http</protocol>
    <port>80</port>
</xml>

xFiles parses incoming attributes exactly as if they were tags. So the group for the above could be

Whatever    Group
Protocol      String(10)
,Name('protocol | attribute(server)')
Server        String(80),Name('server')
Port          Long,Name('port')
            End

or

Whatever    Group
Protocol      String(10)
,Name('protocol | attribute')
Server        String(80),Name('server')
Port          Long,Name('port')
            End

or

Whatever    Group
Protocol      String(10)
,Name('protocol')
Server        String(80),Name('server')
Port          Long,Name('port')
            End

All these structures will work for loading XML into Clarion. For writing XML though see Saving Fields As Attributes.

TIP: The order of the fields in the group is not important, except in the case where multiple fields have the same external name.
TIP: For more on creating XML that contains attributes, see here.
TIP: The attribute notation inside the name property is not required for a load. It will be used during a Save though.
TIP: In case you were wondering,  the correct form to save the sample XML above, is the first example group, with
    Protocol String(10),Name('protocol | attribute(server)')

XML containing structures inside other structures

Xml can contain a structure inside a structure. For example;

<xml>
    <server>www.capesoft.com</server>
    <protocol>http</protocol>
    <port>80</port>
    <logging>
      <local>1</local>
      <summary>1</summary>
    </logging>
</xml>


This can be matched in clarion by placing a group inside your group.

Whatever    Group
Protocol      String(10)
Server        String(80)
Port          Long
Logging       Group
Local           Long
Summary         Long
              End
            End

XML containing multiple records

In multi-record XML xFiles needs to know the FileBoundary and the RecordBoundary. These properties tell xFiles what part of the xml file to parse, and most importantly when a record is complete. For example in the following xml

<?xml version="1.0" encoding="US-ASCII"?>
<table>
  <item>
    <name>Bruce</name>
     <age>29</age>
  </item>
  <item>
    <name>Bob</name>
     <age>40</age>
  </item>
</table>

The file boundary is <table> (since all the records fall between <table> and </table>) and the record boundary is <item> (since the data we are interested in, the name and age, falls between <item> and </item>)

We can store this xml file in a Queue, which would look something like this;

NamesQueue    Queue
name           String(20),name('name')
age            Long,name('age')
              End

TIP: The first step is looking at the XML file in question and deciding on the most appropriate structure to load it into. A Group is best if the xml contains a "single record" and is typically used for program settings and things like that. If the structure contains a repeating structure (in other words, multiple records) then a Queue or File is more appropriate. Of course an In-Memory file can be used as a File rather than using a Queue.

TIP: Although a valid XML file always has a single FileBoundary, that wraps the XML from top to bottom, not all systems generate XML that conforms to this standard. So xFiles allows the FileBoundary to be blank. If it is blank, then xFiles parses the file as if there is no file boundary. In the case of a Group, it is common to leave the FileBoundary, and just use the RecordBoundary. For example;

<?xml version="1.0" encoding="US-ASCII"?>
<data>
  <server>www.capesoft.com</server>
  <port>80</port>
</data>

In this case the FileBoundary should be set to blank and the RecordBoundary should be set to data.

There is a special case where the xml you are parsing looks like this;

<product>1</product>
<product>2</product>
<product>3</product>

In this case there is a list of products, where each <product> tag symbolizes both a complete record, and the field name. Or, to describe it another way, in this xml the File boundary AND the Record Boundary are both missing. In this situation you call the .Load method as normal, and you must include both the file, and record boundaries in the call, as blank strings.

Tag Case

XML is case sensitive. This adds a layer of complexity to it since Clarion is a case-insensitive language. Fortunately this complexity is not difficult to manage. This primary mechanism is the use of the TagCase property. This can be set to one of XF:CaseLower, XF:CaseUpper, XF:CaseAsIs or XF:CaseAny.

In most cases, for a .Load XF:CaseAny is an appropriate option.

For a .Save it gets more complex because the case is usually determined by the program you are talking to. You can force all the tags to be lower, or upper, case using XF:CaseLower and XF:CaseUpper respectively. If the tags have mixed case then your structure will need External Names on all the components, and use XF:CaseAsIs.

Being explicit about the case before a load or save is recommended. Use the SetTagCase method to set the case.

xml.SetTagCase(xf:CaseAsIs)

Loading an XML File from the disk into a Clarion Structure

Once you have determined the data structure you will be using, if it is a Queue or a File, you also need to determine the File Boundary and Record Boundary. Once you know that then importing the XML file is a single line of code.
xml.Load(Structure,XmlFileName,[FileBoundary],[RecordBoundary])

The last 2 parameters are optional in the case of a Group, but it's always better to include them if you know what they are.

For example;
xml.Start()
xml.SetTagCase(XF:CaseAny)
xml.Load(NamesQueue,'c:\temp\names.xml','table','item')


or

xml.Start()
xml.SetTagCase(XF:CaseAsIs)
xml.Load(Settings,'.\settings.xml','settings')

Loading an XML File from a string into a Clarion Structure

This is just as easy as loading it from a file. The syntax of the load method changes slightly to accommodate the name, and length, of the string instead of the file name.
xml.Load(Structure,String,Length,[FileBoundary],[RecordBoundary])

For example;
xml.Start()
xml.SetTagCase(XF:CaseLower)
xml.Load(Settings,SomeString,len(clip(SomeString)))
or
xml.Start()
xml.SetTagCase(XF:CaseUpper)
xml.Load(NamesQueue,net.packet.bindata,net.packet.bindatalen,'table',item')


Tip: Because the String parameter is passed as a pointer, you can't put a constant in here, you must use a variable. In other words the following will  fail to compile;

xml.Load(Settings,'<server>www.capesoft.com</server>',100)

Loading XML from a StringTheory object into a Clarion Structure

Not surprisingly, it's equally easy to load from a StringTheory object.It's similar to loading a file, except the object is passed instead of the file name.

xml.Load(Structure,StringTheoryObject,[FileBoundary],[RecordBoundary])

The last 2 parameters are optional in the case of a Group, but it's always better to include them if you know what they are.

For example;
xml.Start()
xml.SetTagCase(XF:CaseAny)
xml.Load(NamesQueue,str,'table','item')


or

xml.Start()
xml.SetTagCase(XF:CaseAsIs)
xml.Load(Settings,str,'settings')

Loading XML from the Web into a Clarion Structure

When two programs want to communicate across the web they often pass their information formatted as XML. Web Services are nothing more than servers that answer "Questions" using XML. This XML is usually wrapped in a SOAP envelope, but not always. A SOAP envelope is nothing more than some extra XML syntax included in the packet.

In short, you do not need to be at all worried about Web Services, or SOAP. It's all just XML, and xFiles can handle it just like any other XML. However it arrives, it will be available to you as a string, and you can parse this string into a Clarion structure just as described in the section above.

Tip: Interacting with a Web Service typically consists of 2 parts. A Request, and a Response. This section deals with handling the Response. For more information on forming the Request, see the section Creating SOAP requests.

If, for example, you have used a NetTalk WebClient object to fetch the XML from the server, then you would add a single line of code into the .PageReceived method to parse the incoming reply into a Group, Queue or File. The NetTalk response is already a StringTheory object, so this can just be passed into xFiles. For example;

xml.load(resultGroup,self.thispage,'','ChangeLengthUnitResponse')

If the page being received is an RSS feed, then the following line would copy the feed into a Queue.

xml.load(RssQueue,self.thispage,'channel','item')

The Queue declaration would look something like this;

RssQueue  QUEUE,PRE(rss)
Title       STRING(255)
Link        STRING(255)
Description STRING(1024)
          END

Loading into a structure with a pointer

Groups and Queues allow for pointers, Files do not.

There are times when you are not able to dictate the maximum size of the incoming xml field. For example say you had an XML structure that looked like this;

<images>
  <image>
    <id>1</id>
    <bin>xxx</bin>
  <image>
</images>


Usually this would match a clarion group structure like this;

images  Queue
id        Long
bin       String(255)
        End


In the above group the maximum size of the string is arbitrarily set as 255 characters. The XML may contain a longer image, but only the first 255 characters would be in the queue. Of course something like an image may be very large, so it's difficult and undesirable to place an upper limit on the size it can be. The solution to this is to make the field in the group a pointer to a string, or StringTheory object. For example;

images  Queue
id        Long
bin       &String
        End


To support this one of two approaches should be taken. The first is the easiest, and also the one best suited to not-leaking memory.

1. Use Extended Name Attributes (recommended)

This approach is supported by both the xFilesStream class, and the xFilesTree class.

By specifying the type in the Extended Name attributes you allow xFiles to do all the work for you.

images  Queue
id        Long
image     &String,name('image | &String')
        End

Defining it this way allows xFiles to "know" that the field is a pointer, and assign sufficient space for the value as the field is loaded.
You could also use a StringTheory object in the Group (or Queue). Supported pointers are &String, &StringTheory, &Cstring and &PString.

When you are done with the Queue (or Group) and before it goes out of scope - you MUST call

xml.DisposeQueue(images)
or
xml.DisposeGroup(whatever)

This will free up all the allocated memory. Failure to do this (or free the allocated memory yourself) will result in a memory leak.

2. Embed code in derived methods (not recommended)

This approach is no longer recommended. It is included here because it was the approach used by xFiles 3, and earlier, and so you may find this code in existing programs.  As you can see this code is more work than the above method, and runs more risk of leaking memory.

This approach is only supported by the xFilesStream class, not the xFilesTree class.

The key here is that right before xFiles places the data into the string it needs to get sufficient memory from the OS. To do this means you need to add a bit of embed code into the AssignField method.

There are two different methods called AssignField - the one to embed code into is the one that takes two LONG parameters. The code goes before the parent call. For example;

 if lower(self.CurrentTag) = 'image'
    axg.image &= new string(DataEndPos - DataStartPos + 1)
    self.CurrentField &= axg.image
end


As you can see DataEndPos and DataStartPos determine the length of the data you are wanting to save, and CurrentField is reset to the newly gotten memory.

Note that this memory has been added to your structure, and you are responsible to DISPOSE it when it is no longer needed.
Specifically you MUST manually dispose the field before the procedure ends or your program will leak memory.

This is especially true if you are using a pointer in a Queue. You must DISPOSE the field (axg.image in this case) for each record in the queue before the queue record is deleted or the queue is FREEed.

If you wish to use a StringTheory pointer instead of a string then the code is slightly different;
xmlGroup group
id         Long
image      &StringTheory
          end


And the code in AssignField looks like this;

if lower(self.CurrentTag) = 'image'
  xmlgroup.Image &= new(StringTheory)
  xmlgroup.Image.SetValue(self.BinData [DataStartPos : DataEndPos])
  return ReturnValue
end


It is important that this comes before the parent call, and in this case there's a RETURN before the parent call.

One difference to this approach to the &String mentioned above, is that the contents of the StringTheory object will be exactly the contents of the XML. No decoding of CDATA, nor ampersand decoding, nothing like that occurs. If you need to do any decoding etc then you can use StringTheory methods to do that after the call to SetValue.

As before, when using this approach, you are responsible for DISPOSEing of the StringTheory object before it goes out of scope.

Loading Parent and Child records at the same time

Although XML is a specification for explaining the content of a text file, it does not dictate the structural nature of the text file. For example the following three xml files contain the same information, but the structure of the xml file is different. And it should be noted that these are only three of many possible configurations.

Layout 1

In this layout the line items are included in the xml file, but are not inside the Invoice tag. Each LineItem explicitly includes a link to the Invoice that it belongs to.

<invoice>
  <number>4</number>
  <customer>Fred</customer>
</invoice>
<lineitem>
  <invoice>4</invoice>
  <product>xFiles</product>
</lineitem>
<lineitem>
  <invoice>4</invoice>
  <product>NetTalk</product>
</lineitem>

Layout 2

In this layout the line items are inside the Invoice tag, however they still explicitly link to the Invoice in question.

<invoice>
  <number>4</number>
  <customer>Fred</customer>
  <lineitem>
    <invoice>4</invoice>
    <product>xFiles</product>
  </lineitem>
  <lineitem>
    <invoice>4</invoice>
    <product>NetTalk</product>
  </lineitem>
</invoice>


In simple situations the Layout 1 case and the Layout 2 case, can easily be handled using a two pass approach. Import the file twice, once for the invoices, and once for the line items. Once the two imports are completed all the necessary records for both tables will have been imported. However there may be cases where the more complicated method, which is required for Layout 3, has some advantages.

Layout 3

In the third layout the line items are inside the invoice, but there is no explicit link. Rather the position of the line item, in relation to the invoice, determines which record the line items belong to.

<invoices>
  <invoice>
    <number>4</number>
    <customer>Fred</customer>
    <lineitems>
      <lineitem>
        <product>xFiles</product>
      </lineitem>
      <lineitem>
        <product>NetTalk</product>
      </lineitem>
    </lineitems>
  </invoice>
</invoices>


Using xFilesTree Class (recommended)

For the above layout we need a Clarion structure which allows for nested records, and that means queues-in-queues, or queues-in-groups.

Hint: Turning complicated XML structures into nest queues can be painful to do by hand. The easiest approach is to use https://capesoft.com/xFilesCode to generate them for you. That online utility also generates the sample code, and NewPointer code.

The above structure can be represented like this;

InvoicesQueue            Queue
number                     Long,Name('number')
customer                   STRING(255),Name('customer')
lineitems                  &lineitemsQueueType,Name('lineitems | queue | RowName(lineitem)')
                         End

LineItemsQueueType       Queue,Type
product                    STRING(255),Name('product')
                         End



You may not have used queues in queues before, so it's worth taking a moment to explore the syntax above.

Firstly, Clarion does not allow for the direct declaration of a queue inside a queue, so a TYPE declaration is used. LineItemsQueueType is declared, then used as a reference inside InvoicesQueue.

By now the Extended Name Attributes should be familiar, in this case the field is marked as a queue, and the rowname (the record boundary) of each child entry is set to lineitem.

For a SAVE this would be all that is required, however for a LOAD an additional bit of code is required. First the object is declared with a derived method;

Xml                     Class(xFilesTree)
NewPointer                 Procedure(*Cstring pGroupName, *CString pColumnName),Derived
                         End


And then the derived method is also declared. (Typically in the "Local Procedures" embed point).

xml.NewPointer          Procedure(*Cstring pGroupName, *CString pColumnName)
  ! note that pColumnName is the xml tag name, not the label. case sensitive.
  Code
  Case pGroupName
  Of 'invoices'
    Case pColumnName
    Of 'lineitems'
      invoices.lineitems &= NEW lineitemsQueueType 
    End
  End


This takes care of creating the new child queues as they appear in the XML.

The code to load the XML into the queue is very simple;

  xml6.start()
  xml6.SetTagCase(xf:CaseAsIs)
  xml6.load(InvoicesQueue,'invoices.xml','invoices','invoice')


Once you have finished using the queue, and before the quue goes out of scope, you MUST call

  xml6.DisposeQueue(InvoicesQueue)

Using xFilesStream Class (not recommended)



Layout 4

Layout 4 is a slight adjustment on Layout 3, which necessitates some small adjustments to the code.

First the XML  note that in this case there is no wrapper (<lineitems>) around the list of items.

<invoices>
  <invoice>
    <number>4</number>
    <customer>Fred</customer>
    <lineitem>
      <product>xFiles</product>
    </lineitem>
    <lineitem>
      <product>NetTalk</product>
    </lineitem>
  </invoice>
</invoices>


The matching change happens in the declaration, not the code;


InvoicesQueue            Queue
number                     Long,Name('number')
customer                   STRING(255),Name('customer')
lineitems                  &lineitemsQueueType,Name('lineitems | queue | XmlName() | RowName(lineitem)')
                         End


Notice the blank XmlName (which tells the class there's no "queue boundary") and the RowName tells it where each record starts.

Adjusting incoming data records before they are saved

When importing into a table or queue, you may want to add your own code to massage the record just before it is added. Fortunately some methods (InsertFileRecord, UpdateFileRecord, AddQueueRecord) exist to make this easy to do.

Note: The method declarations are different between the xFilesStream Class and the xFileTree Class. They are declared as follows;

sXml                     class(xFilesStream)   ! streaming parser
InsertFileRecord           Procedure () ,DERIVED
AddQueueRecord             Procedure (Long pFirst=0) ,DERIVED
UpdateFileRecord           Procedure () ,DERIVED
                         End


and

tXml                     class(xFilesTree)     ! tree parser
InsertFileRecord           Procedure(*FILE pTable),Long,Proc,DERIVED
UpdateFileRecord           Procedure(*FILE pTable),Long,Proc,DERIVED
AddQueueRecord             Procedure(*Queue pQueue, *Cstring pGroupName, Long pFirst=0),Long,Proc,DERIVED
                         End


In both cases you only need to derive the methods that are applicable to you.

Here's an example;

Consider the case where you have a table that contains a number of fields which match fields in an XML file. Loading the XML into the table is straight-forward. But assume there are also two extra fields in the table record, say ImportDate and ImportTime which need to be primed when the incoming records are added.
To do this add embed code to the InsertFileRecord method, before the parent call. For example (xFilesStream object);

sXml.InsertFileRecord PROCEDURE ()
  CODE
  cus:ImportDate = today()
  cus:ImportTime = clock()
  PARENT.InsertFileRecord ()


The call to InsertFileRecord does the file driver ADD command, so the ERRORCODE can be tested immediately after the PARENT call if you need to handle the error.

and for the xFilesTree object;

tXml.InsertFileRecord           Procedure(*FILE pTable)
  code
  cus:ImportDate = today()
  cus:ImportTime = clock()
  return parent.InsertFileRecord(pTable)


In this case if there is an error, then the ErrorTrap method is called and the method will return xf:Error .

Deformating incoming XML fields

It is possible that an incoming XML field will need to be reformatted as it is being imported. For example the incoming xml may contain a date in yyyy/mm/dd format that needs to be converted to Clarion LONG format for use in a group, queue or table.

As before there are two ways to do this - in the Extended Name Attribute, or in code.

1. Extended Name Attributes (recommended)

Both xFiles classes support using a picture in the Extended Name attributes. For example;

InvoiceQueue   Queue
Date             Long,name('Date | @d6')
Time             Long,name('Time | @t1')
Amount           Long,name('Amount | @n14')
               End


In this situation the Queue contains LONG values, while the XML contains formatted date, time and amount values.
Extended Pictures are also supported, you are not limited to Clarion pictures. These are especially useful when using XML that contains date-time stamps.

2. In Embed code

xFilesTree Class
The method to embed into is

DeformatValue  Procedure(*Cstring pGroupName, *Cstring pColumnName, String pValue)

This passes in the group name, column name, and value. It should return the deformatted value. For example;

case pGroupName
of 'group'
  Case pColumnName
  of 'temperature'
    return(pValue * 1.8 + 32)
  End
End
Return pValue
xFilesStream Class
The AssignField method. Note that here are two AssignField methods - you need to embed in the one prototyped as

xFileXML.AssignField Procedure (String pString)

You can add code before the parent call which assigns the incoming pString parameter into  the appropriate field. The field currently being dealt with is in the CurrentTag property. For example;

xFileXML.AssignField Procedure (String pString)
  code
  case lower(self.CurrentTag)
  of 'date'
    self.CurrentField = deformat(pString,@d10)
! yyyy/mm/dd
    return
! this is important to avoid the call to PARENT
  end
  parent.AssignField(pString)


Loading an HTML Table into a Queue

Often data is received as HTML, in an HTML <table> structure. There are two approaches you can use to move this data into a Queue using xFiles.

Consider the following xml;

<table class="tableList tight w100">
    <tr>
        <th>First Name </th>
        <th>Inititals </th>
        <th>Last Name </th>
    </tr>
    <tr>
        <td class="left">Ryan</td>
        <td class="left">&nbsp;</td>
        <td class="left">Abbott</td>
    </tr>
</table>

With StringTheory

If you have StringTheory then this is the easiest approach. It works by using StringTheory to clean up the HTML before passing it to xFiles.

xml xFileXML
str StringTheory

htmlQueue QUEUE
col1 String(255)
col2 String(255)
col3 String(255)
End

  CODE
  str.LoadFile('whatever.xml')
  str.remove('<td','>',false,true)
! removes all attributes inside the HTML
  xml.start()
  xml.TagCase = XF:CaseAny
  xml.MatchByNumber = true
        ! This is the magic line that gets the columns into columns
  xml.Load(htmlQueue,str.GetValuePtr(), str.Length(),'table','tr')


In the above .Load, all the rows <tr> are moved into the queue. This includes <th> and <td> fields.

Loading Into a Table

Using the Load method you can load directly into a Table structure. By default incoming records are added to the table. If an error occurs on the ADD then the record is ignored, and the import continues.

In some cases the load should behave as an update, as well as an insert. If this is desired then use the SetUpdateTableOnLoad method;

xml.SetUpdateTableOnLoad(true)

This only works if the table has a Primary Key set. Primary Key values cannot be altered while doing the import.

By default an import adds to the existing table. If the table must be cleared before the import then use SetFreeFileBeforeLoad;

xml.SetFreeFileBeforeLoad(true)

Incoming records can be validated before they are added, or updated, to the table. To do this embed into the ValidateRecord method, and return one of xf:ok, xf:filtered, xf:OutOfRange. This method is called when importing into a table, or into a queue. Returning xf:OutOfRange will terminate the import at that point. returning xf:Filtered will skip over this record and move on to the next record.

xml                  Class(xFilesTree)
ValidateRecord         Procedure (), long, virtual
                     End

xml.ValidateRecord  Procedure()
  code
  If something
    return xf:Filtered
  End
  return parent.ValidateRecord()


In addition a different method is called if the record is about the be updated. In that situation the method to embed into is ValidateUpdateRecord. Inside this method the OLD record is loaded into the record buffer, and so allows updates to be filtered based on the original contents, as well as the new contents. (The new contents can be filtered in the ValidateRecord method.) ValidateUpdateRecord should return one of xf:ok or xf:Filtered.

xml                    Class(xFilesTree)
ValidateUpdateRecord     Procedure (), long, virtual
                       End

xml.ValidateUpdateRecord  Procedure()
  code
  If something
    return xf:Filtered
  End
  return parent.ValidateUpdateRecord()


The actual writes to the table are done by two methods, InsertFileRecord and UpdateFileRecord. These simply call the Clarion ADD and PUT commands respectively. They do not can the ABC FileManager methods, so if you want to use those then override these methods. In the xFilesStream class these methods do not do any error checking (that is done by the caller) - in the xFilesTree class these methods test for an error and call ErrorTrap if an error occurs.

For each record that is inserted the RecordsInserted property is incremented. For each record that is updated the RecordsUpdated property is incremented. These can be used (after the load) using the GetRecordsInserted and GetRecordsUpdated methods.

n = xml.GetRecordsInserted() + xml.GetRecordsUpdated()

Loading Into Arrays

It's hard to explain. Here are some examples;

In this example the array values are just included in the group structure without any differentiation, and without any row boundary.
The structure and code are straight-forward.
XML
<Address>
    <Town>Diep River</Town>
    <Country>Cape Town</Country>
    <AdrLine>4th Floor.</AdrLine>
    <AdrLine>Waterford House</AdrLine>
    <AdrLine>Waterford Road</AdrLine>
</Address>

Structure
AddressGroup    GROUP,NAME('Address')
Town              STRING(100),NAME('Town')
Country           STRING(100),NAME('Country')
AdrLine           STRING(100),dim(10),NAME('AdrLine')
                END   
 
Code
tXml  xFilesTree

  txml.start()
  txml.SetTagCase(xf:CaseAsIs)
  txml.Load(AddressGroup,disk,'Address') ! Load From a StringTheory object
In this example the array is inside a boundary.  Meta1 is the array, but each array value has a boundary (meta2).
This would also work if the outside name, and boundary, were the same, the extended Name would just be
,name('meta | rowname(meta)')
XML
<homes>
  <home>
    <meta1>
      <meta2>123 Bond Street</meta>
      <meta2>London</meta>
    </meta1>
  </home>
</homes>


Structure
HomeQueue2   Queue
Meta1          String(20),dim(10),name('meta1 | rowname(meta2)')
             End  
Code
tXml xFilesTree

txml.start()
txml.SetTagCase(xf:CaseAsIs)
txml.Load(HomeQueue2,disk,'homes','home') ! Load From a StringTheory object

Creating XML

In the same way that you can load an XML file into a Clarion structure, you can also create XML files very quickly and easily. If you haven't already done so have a read through the Loading XML - Quick Start Guide. Most of that information is a reflection of what happens when creating XML.

Saving a Clarion Structure to an XML file on the Disk

xml.Save(Structure,XmlFileName,[FileBoundary],[RecordBoundary])

The last 2 parameters are optional but it's always better to include them if you know what they are. Note that if you include one you will need to include both. In the case of a Group, the FileBoundary parameter can be a blank string. For example, for saving a queue;

xml.Save(NamesQueue,'c:\temp\names.xml','table','item')

or for saving a group

xml.Save(Settings,'.\settings.xml','','settings')

You can use a Group, Queue, View or File as the source structure. The Save method returns xf:ok if successful, xf:notok otherwise. If not successful the errorcode will be in the .Error property, and a description of the error will be in the .ErrorStr property.

Saving a Clarion structure to an XML string in Memory

xml.Save(Structure,[FileBoundary],[RecordBoundary])

This is the same as saving the xml to a File, except that the XmlFileName parameter is not used. After the call to save, the xml data will be in the property .xmlData. The length of this string is in .XmlDataLen. For example;

xml.save(NamesQueue,'table','item')
Blob[0 : xml.XmlDataLen-1] = xml.XmlData

Saving a Clarion structure to a StringTheory Object

xml.Save(Structure,StringtheoryObject,[FileBoundary],[RecordBoundary])

Rather than saving to a String, it's easier to save into a StringTheory object. This makes the result a lot easier to work with. For example;

str  StringTheory
xml  xFilesStream
  code
  xml.start()
  xml.SetTagCase(xf:CaseLower)
  xml.save(NamesQueue,str,'table','item')
  str.ToBlob(Someblob)

Arrays

Given a simple Clarion array field;

Names  Group
Name     string(20),dim(3)
       End


There are several ways this array can be rendered in XML.

The first approach, and the default approach (IF a Rowname is NOT set; see below), is to uniquely identify each tag in the XML with the array index number. So the above becomes;

<names>
  <name__1>Charles</name__1>
  <name__2>Henry</name__2>
  <name__3>William</name__3>
</names>


This allows each entry in the array to be uniquely identified, and the position of the item in the array is also preserved.

A property, AddArrayIndexToTag, can be set to false to change this behavior. If this property is set to false then the array is rendered by repeating the same tag, without the numerical suffix;

<names>
  <name>Charles</name>
  <name>Henry</name>
  <name>William</name>
<names>


For example;

xml.start()
xml.SetAddArrayIndexToTag(false)
xml.Save(NamesGroup,str,'','names')


While the items will appear in the order in which they exist in the array, blank items may be suppressed (depending on the three Blank properties) which would mean that the position in the XML no longer completely matches the position in the array.

More control over the generation can be done using the Extended Name Attributes. Adding a RowName attribute sets the name for each field. (This negates the auto-numbering supported by AddArrayIndexToTag above.)   For example;

Names  Group
Name     String(20),dim(3),name('name | rowname(customer)')
       End


results in

<names>
  <
customer>Charles</customer>
  <
customer>Henry</customer>
  <
customer>William</customer>
<names>


If the # character is used inside the rowname, then the array index will be substituted for the # character. For example;

Names  Group
Name     String(20),dim(3),name('name | rowname(customer.#)')
       End


results in

<names>
  <
customer.1>Charles</customer.1>
  <
customer.2>Henry</customer.2>
  <
customer.3>William</customer.3>
<names>


Be aware that XML tags have to start with a character, or underscore. They cannot start with a number, so Rowname(#) would be illegal. If the RowName starts with a #, then a _ is prepended to the number.

Also note that while Clarion allows dimensioned fields to be declared with more than one dimension, in practice the data is stored as a single-dimensioned array. So the XML will display as a single dimension array (all the data will be included though.)

In some cases it's desirable to use the dimensioned field as a node, and have all the array values as nodes under that node. This can be done by adding an XMLName attribute. So

Names  Group
Name     String(20),dim(3),name('name | xmlname(wrapper) | rowname(customer)')
       End


results in

<names>
  <wrapper>
    <
customer>Charles</customer>
    <
customer>Henry</customer>
    <
customer>William</customer>
  <wrapper>
<names>

Formatting Outgoing Fields

It is possible that an outgoing XML field will need to be formatted as it is being exported. For example the desired xml may contain a date in yyyy/mm/dd format that needs to be converted from Clarion LONG format in a group, queue or table.

As before there are two ways to do this - in the Extended Name Attribute, or in code.

1. Extended Name Attributes (recommended)

Both xFiles classes support using a picture in the Extended Name attributes. For example;

InvoiceQueue Queue
Date           Long,name('Date | @d6')
Time           Long,name('Time | @t1')
Amount         Long,name('Amount | @n14')
             End


In this situation the Queue contains LONG values, while the XML contains formatted date, time and amount values.
Extended Pictures are also supported, you are not limited to Clarion pictures. These are especially useful when using XML that contains date-time stamps.

2. In Embed code

In cases where no suitable picture exists, you may need to do formatting in embed code.
xFilesTree Class
Embed the code into the FormatValue method.

xFilesTree.FormatValue          Procedure(*Cstring pGroupName,*Cstring pName, String pValue)

This passes in the group name, column name, and value. It should return the formatted value. For example;

case pGroupName
of 'group'
  Case pColumnName
  of 'temperature'
    return pValue * 1.8 + 32 & 'F'
  End
End
Return pValue
xFilesStream Class
Embed the code in the .SaveCurrentFieldToXML method. There are two instances of this method, so you should use the one which is prototyped as below.

First create a variable to hold the result (in this case called TEXT). This must be declared in the procedure, not the method.

text string(20) ! declared earlier in the procedure, not in the method

xmlFile.SaveCurrentFieldToXML PROCEDURE (Long p_x,Long p_DimCounter,String p_name)
  CODE
  if p_name = 'WAG.SALARY'
    text = 'CASH:' & format(self.currentField,@p<<<<#.##p)
    self.currentfield &= text
  end
  ! Parent Call
  PARENT.SaveCurrentFieldToXML (p_x,p_DimCounter,p_name)


Then, as above, set the Text variable, and importantly assign self.CurrentField to point to Text.

Tip: The p_name parameter is the tag name in the xml, not the field name in the table.

Changing the Name of a Field in Outgoing XML

When exporting XML from a structure the external name of each field is used as the <Tag> name in the XML. For example

xQueue Queue
field1   string(255),Name('Total')
       End


results in XML like this;

<Total>whatever</Total>

Ideally the external Name attribute of the field contains the correct value for the tag. The Rename attribute overrides the external name;

xQueue Queue
field1   string(255),Name('Total | rename(FieldTotal)')
       End


In extreme circumstances you can use XMLName to override the rename.

xQueue Queue
field1   string(255),Name('Total | rename(FieldTotal) | xmlName(xmlTotal)')
       End


If you are not able to set the external name directly

There are times however when you need to override this, and this is done by embedding code into the ExtractNames method, AFTER the PARENT call.

Example;

xml.ExtractNames PROCEDURE ()
CODE
PARENT.ExtractNames ()
self._sFieldName[1] = 'Totalizer'
self._sFieldNameLen[1] = len(clip(self._sFieldName[1]))


Note that it's very important to set both the text of the field name, but also the length property for the fieldname.

Note also that the field number is critical here - the [1] in brackets specifies the field number which is being set.

Properties which can be set before a call to Save (xFilesStream only)

There are a number of properties which you can set just before doing a save. These properties affect the way the Xml looks.
Property Effect
DontReplaceColons By default colons in the field name are replaced with a period. If you wish to suppress this behavior then set this property to 1.
DontUTF8Decode If set then incoming strings are not decoded from utf-8 to ANSI form.
DontUTF8Encode If set then outgoing strings are not encoded into utf-8 form. It is assumed that strings are already in utf-8 form (if the output encoding is set as utf-8)
DemoMode If this is not zero, then the data in the source data structure will be replaced with the DemoModeValue property when the data is written out. If the output structure is a Queue, File or View then DemoMode contains the number of rows to write.
This allows you to create sample output XML, without needing actual demo data in the structure.
DemoModeValue The value to use for all fields and attributes when DemoMode is > 0.
Meta Allows custom meta headers to be added to the xml. This property is added to the top of the xml file, before the root boundary.For example;
xml.meta = '<?mso-application progid="Excel.Sheet"?>'
OmitXMLHeader The XML header (usually <?xml version="1.0">) is not added to the top of the file.
_pFileBoundary Typically passed as a parameter of the call to .Save. This sets the boundary for the outside of the file.
<?xml version="1.0">
<file>
  <record>
  </record>
</file>
_pFileBoundaryAttribute Can be set to allow an attribute string to be included as part of the file boundary.
<?xml version="1.0">
<file albert="fat">
  <record>
   </record>
</file>
_pRecordBoundary Typically passed as a parameter of the call to .Save. This sets the boundary for each record inside the file.
<?xml version="1.0">
<file>
  <item>
   </item>
</file>
RecordBoundaryAttribute A fixed attribute string for the record boundary. This attribute will be applied to all records, and should not change between records.
<?xml version="1.0">
<file>
  <item albert="fat">
   </item>
</file>
RemovePrefix Default is 1. If set, the prefix is not used when matching fields to tags.
RootBoundary An additional XML boundary around the whole file.
<?xml version="1.0">
<root>
  <file>
    <record>
     </record>
  </file>
</root>
RootBoundaryAttribute A string of attributes for the Root Boundary.
<?xml version="1.0">
<root albert="fat">
  <file>
    <record>
     </record>
  </file>
</root>
RSSVersion If you are creating an RSS file, set this property to the RSS version you are using. For example 2.0
<?xml version="1.0" encoding="ISO-8859-1"?>
<rss version="2.0">
If the RssVersion propert is set, the _pFileBoundary is automatically set to <channel>
SaveEncoding The encoding scheme to use for the XML file. Examples are 'utf-8', 'ISO-8859-1' and 'windows-1252'.
<?xml version="1.0" encoding="utf-8">
See also the .UseCharSet method, which sets an appropriate charset based on a Clarion CHARSET:something equate.
SaveBlobsAsCData Default is 1. Blob fields will be encoded as [CDATA] in the XML file. This is necessary for blobs containing binary characters. (ie Non ASCII characters)
SaveMemosAsCData Default is 1. Memo fields will be encoded as [CDATA] in the XML file. This is necessary for memos containing binary characters. (ie Non ASCII characters)
SaveRecords Default is 0, meaning no limit. If set then only this many records will be copied from the source structure to the XML.  This value is checked after the call to ValidateRecord so records excluded by ValidateRecord are not included in the output, and do not count against the SaveRecords limit.
SkipRecords Default is 0, meaning no records are skipped. If set then this number of valid records will be skipped before records are copied from the source structure to the output. This value is checked after the call to ValidateRecord so records excluded by ValidateRecord are not included in the output, and do not count against the SkipRecords limit. Used with the SaveRecords property this property allows for "pages" of data to be exported.
SaveStringsAsCData Default is 0. If set all String fields will be encoded as [CDATA] in the XML file. This is necessary for strings containing binary characters. (ie Non ASCII characters). You can set this property for individual fields. (See Properties which can be set in SaveTweakFieldSettings)
StandAlone Allows you to set the StandAlone property in the xml header. Can be set to 'yes' or 'no'.
<?xml version="1.0" standalone="yes">
SoapBodyBoundary The name of the SOAP Body boundary. The default is soap:Body. Only used if .SOAPEnvelope property is  set to 1.
<?xml version="1.0">
<soap:Envelope>
  <soap:Body>
    <file>
     <record>
     </record>
    </file>
  </soap:Body>
</soap:Envelope>
SOAPEnvelope Set this to 1 to include the SOAP envelope boundary (and Soap Body boundary) around the file.
SOAPEnvelopeBoundary The name of the SOAP Envelope boundary. The default is soap:Envelope. Only used if .SOAPEnvelope property is  set to 1.
<?xml version="1.0">
<soap:Envelope>
  <soap:Body>
    <file>
     <record>
     </record>
    </file>
  </soap:Body>
</soap:Envelope>
SOAPEnvelopeBoundaryAttribute An attribute for the soap:Envelope boundary. The default is
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"

<?xml version="1.0">
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <file>
     <record>
     </record>
    </file>
  </soap:Body>
</soap:Envelope>
xslt If the XML has an associated XSLT file, then you can set that here. This will result in a header being added to the xml file. For example;
xml.xslt = 'somexml.xslt'
results in
<?xml-stylesheet type="text/xsl" href="somexml.xslt" ?>'
See also XSLT.
ZipUseZip Only valid for saving to an XML file on disk. If set, the XML file on disk will be compressed, using the ZIP compression scheme.

Properties which can be set in SaveTweakFieldSettings (xFilesStream only)

These properties are an array, where the array index is the field number of each field in the Group, Queue, File or View record. Setting these properties allows you to modify behavior for individual fields in the record.
Property Effect
ColumnDisabled[x] If this is set to 1 then the field is not exported to the XML file. If the field is inside a group, then you should adjust the _sGroupLen[x] property as well.
_Dimension[x] If this field is an array, then this contains the size of the array. Note that multi-dimensional arrays are always stored as single dimensioned arrays internally. Bob,dim(5,5) is the same as Bob,dim(25).
_sFieldName[x] The name of the field (In other words the <tag> name). If you change this be sure to change the _sFieldNameLen[x] property as well.
_sFieldNameLen[x] The length of the _sFieldName[x] property.
_sFieldNameIsAttribute[x] If set to 1 then the field will be added as an attribute to the record boundary. If set to a negative number then this field will be assigned to the that field as an attribute. In other words if less than zero it contains the value of the field it is attached to, multiplied by -1.
You should not be setting this property directly, see the SetAsAttribute method for setting fields as attributes.
_sFieldNameAttribute[x] The attribute which will be added to the _FieldName tag. If you change this be sure to change the _sFieldNameAttributeLen[x] property as well. For example, if you wish to add the attribute save="yes" to the field number 3 (called, say, filename) then you'd set
_sFieldNameAttribute[3] = 'save="yes"'
_sFieldNameAttributeLen[3] = len(clip(_sFieldNameAttribute[3]))
and the result would be
<filename save="yes>
whatever
</filename>
_sFieldNameAttributeLen[x]  
SaveAsBase64[x] Force this field to be saved as Base64. (Not currently used).
SaveAsCData[x] Force this field to be encoded as [CDATA]. Useful for string fields that contain non-Ascii characters.
_sGroupLen[x] If this field is a group, this contains the number of fields inside the group. If you disable fields inside a group, you should adjust this property as well.
_Over[x] If this field is Over another field, then the parent field number is here.

Methods which can be used during a Save (xFilesStream only)

These methods allow you to embed code that affects the Save as it is happening.
Method Effect
AddAtListStart,
AddAtRecordStart,
AddAtRecordEnd,
AddAtListEnd
Called as the class iterates through the View, Queue or File.
AddText Allows you to inject text into the output. Typically called from AddAtListStart, AddAtListEnd, AddAtRecordStart, AddAtRecordEnd methods.
SetAsAttribute Sets the field either as an attribute of the record, or the attribute of another field. See SetAsAttribute for more information.
SaveCurrentFieldToXML This method is called for each field as the XML string is created. The third parameter is the field name. This provides you an opportunity to alter the value in the field before it is saved to XML. See also Formatting a field before it is saved to XML .
SaveTweakFieldSettings Allows you to set field related properties before the save commences. See also Properties Which can be Set in SaveTweakFieldSettings.
SaveTweakSettings Allows you to override properties explicitly set in the Init method.
ValidateRecord This method is called for each record while looping through the Queue, File or View. You can add your own filter code to this method. Return one of
XF:OutOfRange : Terminates the loop immediately
XF:Filtered   : Cycles to the next record without saving this one to the XML file.
XF:Ok         : Record is saved to the XML.

Using a View to create a filtered, sorted, nested, XML file

Exporting a whole data table, including all records and all fields to an xml file is easy. Simply use the Save method. For example

xFileXML.Save(Customers,'customers.xml')

If however you only want to export a subset of the fields, or records, or if you want to specify the sort order for the export, then the above command is too limited. In this case what you need to do is export the xml using a VIEW as the data source, rather than a file.

For example

xml      xFileTree
ThisView View(Wages)
            Project(wag:Employee)
         End
  code
  xml.Save(ThisView,'employees.xml')

The important thing to remember about views is that you get to decide which fields are included, and which are excluded. In addition you decide what the FILTER and ORDER properties for the view are.

It's useful to remember that all Browses and Reports in your application are based on Views. Thus it is possible to export the contents of a Browse, or Report, to XML by using this xFiles function, and making use of the views constructed for you by these templates.

The real power starts though when you have JOINed tables in the view, and you want to nest child records inside the parent. For example;

PeopleView  View(People)
               Project(Peo:Name)
               Project(Peo:Model)
               Join(Car:GuidKey,Peo:CarGuid)
                 Project(Car:Name)
                 Project(Car:Model)
               End
             End


In this structure there are multiple people, and each person can have multiple cars. Because this is a nested structure, the xFilesTree is the best class to use.

xml  xFilesTree
str  StringTheory

  Code


The code to Save the view follows a similar pattern to other calls, but with some extra options added;

    xml.start()     
    xml.SetTagCase(xf:CaseLower)
    xml.SetNestViewRecords(true)
    xml.SetViewBoundary(PeopleView,Cars,'Cars','Car')
    xml.Save (PeopleView5,str,'Peoples','Person')  


In the above, setting the NestViewRecords property tells the engine to nest the view records - in other words the car records nest inside the people records.

Unlike Queues, Groups and Tables, Views cannot have Extended Name properties. For the individual fields, the Extended Names are inherited from their table structures, but something extra is needed for the JOIN statement. This is the SetViewBoundary method. It allows you to set the File Boundary, and Record boundary (Name, and RowName) for the Join.

The result looks something like this;

<peoples>
  <person>
    <name>Elizabeth</name>
    <model>false</model>
    <cars>
      <car>
        <name>Honda</name>
        <model>Ballade</model>
      </car>
    </cars>
  </person>
  <person>
    <name>Charles</name>
    <model>true</model>
  </person>
</peoples>

Including a Binary file in outgoing XML

If you want to include a file (like say a PDF or Image file) in an XML structure you are creating then the process is fairly straightforward.

There are two considerations in play here. The first is that the file could be of any size. So a &StringTheory field in your group or queue makes the most sense. If the source is a table, then a BLOB field makes the most sense. Of couse a "suitably big string" could be used, but this can be problematic if the binary file ends in trailing spaces.

The second issue is that pure binary is not allowed in XML (not even in CDATA), because the null character ( chr(0) ) is not allowed in XML. Therefore the file has to be encoded first, and the most common encoding is Base64.

xFiles supports the Extended Name Attribute base64 to accomplish this. Items marked as base64 will automatically be encoded into base64 when outputting a structure to xml, and automatically decoded from base64 when loading xml into a structure.

For example;

xml        xFilesStream
Bin1        Group
Name          String(255),name('filename')
File          &StringTheory,name('file | stringtheory | base64')
            End
  Code
  Bin1.Name   = 'attributes1-1.bin'  
  Bin1.File   &= new StringTheory
  Bin1.File.LoadFile(Bin1.Name)

  Xml.start()               
  Xml.SetTagCase(xf:Caselower)
  Xml.save(Bin1,'somefile.xml','','xml')


Handling Structures with Pointers

Pointers are handled the same way when doing a Load or a Save. See Loading into a structure with a Pointer

Storing a Queue Inside a Group or Queue

The use of Queues in Queues, and Queues in Groups is much easier in the xFilesTree class than the xFilesStream class. For this reason it is always recommended to use the xFilesTree class when creating, or consuming, this sort of XML.

Consider the following desired xml;

<invoices>
    <invoice>
        <customer>Fred</customer>
        <date>9 Sept 1970</date>
        <lineitem>
            <product>Bed</product>
        </lineitem>
        <lineitem>
            <product>Chair</product>
        </lineitem>
    </invoice>
</invoices>


In this case the file boundary is Invoices, and the record boundary is Invoice. However inside the data section are some group fields (customer, date) and a repeated queue (lineitem).

The Clarion structures to create this look something like this;

InvoiceQueue QUEUE,PRE()
Customer         LONG ,name('customer')
Date             LONG ,name('date | @d8')
LineItemsQ       &LineItemsQueueType ,name('lineitems | queue | rename() | rowname(lineitem)') ![Note 1]
Total            LONG
             END

LineItemsQueueType   QUEUE,TYPE
Product                LONG,name('product')
Price                  LONG,name('price')
                     END


Note that in Clarion the Queue type is declared outside the group, and a pointer to the queue is contained inside the group.

One thing to note is that InvoiceGroup.LineItemsQ is set to LineItems.

InvoiceQueue.Customer = 'Fred'
InvoiceQueue.LineItemsQ &= new LineItemsQueueType
(and so on)


Saving the InvoiceQueue structure is fairly straight-forward.

xml  xmlTreeClass
  Code
  xml.SetTagCase(xf:CaseAsIs)
  xml.SetOmitXMLHeader(true)
  xml.Save(InvoiceQueue,str,'invoices','invoice')  


[Note 1] note the use of the file and record boundaries here. By setting the RENAME() attribute to blank there is no tag around all the line items in the result. In some cases you would want a tag so in that case omit the RENAME attribute, and the regular field name will be used as the queue boundary for the internal queue.

Storing multiple things in the same XML file.

Up to now all the examples have shown a single entity (Group, Queue, File or View) being stored in an XML file. However it is possible to store multiple entities in the same file or string. The code here varies a bit - the xFilesTree class is simpler to work with here, and has more possibilities, but the xFilesStream class has a different approach.


1. xFilesTree Class

The pattern for the Save changes here slightly. Instead of using a simple call to Save, instead the XML is constructed from parts using the Open, and Add methods. The object is opened with the Open method, and this sets the "root boundary" for the object.

After this is called, one or more calls can be made to the Add method. This allows groups, queues, tables or views to be added to the output.

When all is done the XML can be "saved" to a file on the disk, or StringTheory object using the SaveFile or SaveString methods.

For Example;

xml  xFilesTree
str  StringTheory
  code
  xml.start()
  xml.SetTagCase(xf:CaseAsIs)
  xml.Open('xml')
  xml.Add(ColorQueue,'colorqueue','colors','color')
  xml.Add(ShapeQueue,'shapesqueue','shapes','shape')
  xml.SaveString(str,xf:format)

2. xFilesStream Class

In order to place multiple entities in the same xml file the following basic steps occur;

xml  xFilesStream
  1. Get the class ready, by setting the Root-Node (the wrapper around the whole thing) and telling the class not to automatically close the root node.

    xml.start()
    xml.RootBoundary = 'whatever'
    xml.DontCloseTags = 1
  2. Save the first entity as normal

    xml.Save(ReportFormat ,Loc:XmlName,'Document','Record')   !(For saving to a file)
    xml.Save(ReportFormat ,'Document','Record')  
    !(For saving to a string)
  3. Set the append flag so the following items are appended

    xml.append = 1
  4. Add as many entities to the file as you like, using the normal techniques. Although the boundaries do not have to be unique you may chose to make them unique so they can be loaded separately later on.

    xml.Save(rrDefnFieldsLocalView,Loc:XmlName,'DefnFieldsLocal','Record')
    xml.Save(rrDefnFieldsGlobalView,Loc:XmlName,'DefnFieldsGlobal','Record')

  5. Finally before sending the final entity, tell the object to close the root tag.

    xml.DontCloseTags = 0
    xml.Save(rrDefnBandControlsView,Loc:XmlName,'DefnBandControls','Record')
    xml.append = 0

NOTE: This approach cannot be used if a zipped xml file is being made using the built-in zipping classes.

Saving Fields as Attributes

Simple XML can be thought of as <tag>data</tag>. Despite multiple fields, and increasing levels of fields-within-fields, it follows that relatively simple pattern.

XML however does have another trick up its sleeve. These are called Attributes. The syntax looks something like this;
<tag attributeName="value" anotherAttributeName="value">data</tag>

When loading, xFiles reads these attributes in as data, interpreting the attribute names into field names. From a reading-xml point of view, xFiles (and hence your program) doesn't care if the incoming data was an attribute, or inside a tag. For more information you can read a basic introduction to reading attributes here.

Creating XML with attributes is slightly more complex than just creating basic XML. The streaming class (xFileXML) uses a method (SetAsAttribute) to identify attribute fields. The tree class makes use of Extended Name Attributes. Since these two classes use distinct approaches they are discussed separately.

Attributes - Tree Class

In the tree class approach, the field containing attributes is made into a GROUP. This group can optionally contain an unnamed field which will be used to hold the actual value. It can also contain any number of Attribute fields. For example;

Animals      Queue
SpeciesGroup   Group,name('species')
Species          String(20),name('|)')
Type             String(20),name('type | attribute')
               End
             End


In the above structure a "group" with the name "species" is created. The type field is created as an attribute of the species field.
This approach works whether there is a row boundary or not.

Example 1 Code

Xml xFilesClass
code
Xml.start()
Xml.SetOmitXMLHeader(true)
Xml.SetTagCase(xf:Caselower)
Xml.Save(animals2,str,'animals','animal')


Example 1 Output

<animals>
<animal>
<species type="mammal">Elephant</species>
<animal>
<species type="bird"/>
<animal>
<species type="reptile">Cobra</species>
</animal>
</animals>


Example 2 Code

Xml xFilesClass
code
Xml.start()
Xml.SetOmitXMLHeader(true)
Xml.SetTagCase(xf:Caselower)
Xml.Save(animals2,str,'animals','')


Example 2 Output

<animals>
<species type="mammal">Elephant</species>
<species type="bird"/>
<species type="reptile">Cobra</species>
</animals>


In the above example species has a value of it's own. It's both a group (containing attributes) and has a value (hence the species field.) In some cases the group only has attributes, and in this case the structure can be simplified.

UsersQueue Queue
Emp Group,name('emp')
FirstName String(20),name('firstname | attribute')
LastName String(20),name('lastname | attribute')
End
End


In this example the user field has no value, only attribute values.

Example 3 Code

Xml xFilesClass
Code
Xml.start()
Xml.SetOmitXMLHeader(true)
Xml.SetTagCase(xf:Caselower)
Xml.Save(usersQueue,str,'users','user')


Example 3 Output

<users>
<user>
<emp firstname="Frank" lastname="Furter"/>
</user>
<user>
<emp firstname="Hamilton" lastname="Burger"/>
</user>
</users>


Example 4 Code

Xml xFilesClass
Code
Xml.start()
Xml.SetOmitXMLHeader(true)
Xml.SetTagCase(xf:Caselower)
Xml.Save(usersQueue,str,'users','')


Example 4 Output

<users>
<emp firstname="Frank" lastname="Furter"/>
<emp firstname="Hamilton" lastname="Burger"/>
</users>

Attributes - Streaming Class

The streaming class supports attributes as well, but using a slightly different technique.

Example 5;

The following Clarion structure

Animals Queue
Type String(20)
Species String(20)
End


needs to be saved as xml, with type as an attribute of species, like in this example;

<animals>
<species type="mammal">Elephant</species>
<species type="reptile">Cobra</species>
</animals>


To do this a name attribute is added to the type field;

Animals Queue,pre()
Type String(20),name('type | attribute(species)')
Species String(20)
End


Note that the first part of the name attribute (type) can be case sensitive. So it needs to match the case you want in the XML output.

If you are unable to change the name property then you can make use of the SetAsAttribute method to set it.

xml.SetAsAttribute('type','species')

The first parameter is the field to set as an attribute. The second determines which field it is an attribute of. If this second parameter is omitted the the attribute is assigned to the record boundary tag. (In our example above there is no record boundary, so that would be meaningless in this case.)

Getting the names of the attributes correct is probably the hardest part, because they needs to match the output exactly. In other words it is case sensitive, and should include a prefix if the output tag has a prefix. The easiest approach is to create the XML file with all normal tags, then use that as a reference to get the specific tag name.

Oh that all XML was that simple, but of course it's not. XML allows for the same tag name to be used multiple times in the same xml file, but in different places in the structure. Consider the following XML;

<animals>
<species type="mammal">Elephant</species>
<environment type="land">Grasslands</environment>
<species type="fish">Shark</species>
<environment type="sea">Shallow</environment>
</animals>


This would translate to a Clarion structure like this;

Animals Queue,pre()
Type String(20),name('type')
Species String(20)
Type2 string(20),name('type')
Environment string(20)
End


In this case because the two tags have the same name there needs to be another parameter to determine which tag you have in mind.

self.SetAsAttribute('type','species',1) ! set the first instance of type to be an attribute
self.SetAsAttribute('type','environment',2)
! set the second instance of type to be an attribute

In the above example the third parameter is distinguishing between the first instance, and the second instance of TYPE in the record.

Note that MEMOs and BLOBS cannot be set as attributes.

An alternative is to use the field number in place of the name, but this approach can lead to bugs if the field order in the original structure changes.

Adding Custom Text into the XML file

1. xFilesTree class

Because the xFilesTree class uses a tree, it's very simple to add nodes into the tree at any point. You don't need to inject items into the tree as it is written, it's possible to add (or change) items in the tree after the xml tree is created. After all edits have been completed the tree can be written to a disk file, or StringTheory object, using the SaveFile or SaveString methods.

If you are going to do more than just simply export a Clarion structure (like a Group, Queue, Table or View), then the pattern is as follows;

Declare the object

xml  xFilesTree

Start, and Open the object.

  xml.start()
  xml.setTagCase(xf:CaseAsIs)
  xml.open('rootTag')


Then add as many items to the XML as you like, using the ADD method. This method can take a variety of structures.

Once you have added any large structures, using ADD, you can then edit the existing tree using the various tree-editing methods.

Finally, when the xml tree is complete you can call SaveFile or SaveString to output the resultant xml.

2. xFilesStream class

An  AddText method exists, which allows you to directly inject text into the XML stream as the XML is being created. The method takes a single parameter, which is the text to add.

The text will be added as-is, with no formatting, xml-encoding, unicode translating or anything of that nature. So the text you are adding should be appropriate for the position to be sure that everything is encoded as it should be.

There are four methods which are particularly useful for adding text.
AddAtListStart, AddAtListEnd, AddAtRecordStart and AddAtRecordEnd.

For example in the AddAtListStart method;

self.AddText('<date>' & format(today(),@d10) & '</date><time>' & format(clock(),@t4) & '</time>')

xFilesTree - Tree Manipulations

Background

One of the key reasons to choose the xFilesTree class over xFilesStream class, is the ability to manipulate the tree. In other words, after reading the XML into the tree, or before saving the XML to a string or file, it can be altered.

The tree itself is constructed of nodes. Each node can contain children nodes, and/or one or more values and attributes. There are methods to work with each node, adding or removing values and children.
Hint: Each node in an XML Tree is itself an XML Tree. In this way individual nodes can be extracted, changed, added to and so on.

Hint: Each node can contain multiple values, and there are multiple types for each value. You are likely familiar with the simply value type (xf:value) but XML also supports CDATA (xf:cdata), comments (xf:comment) and Processing Instructions (xf:pi). It's unlikely you will use these other types, but they do exist if you need to add, or remove them.

Each Node in the class contains two queues. One if a queue of Attributes (the Attributes property) and the other is a Queue of children (Children property). The Children include values, other nodes, comments, processing instructions, and so on.

Nodes

First create an object, using the xFilesTree class.

xml xFilesTree

Then, instead of using the regular Load method, to load the XML into a structure, use the LoadFile or LoadString method. This loads the XML into the tree, but doesn't go any further than that.

xml.LoadFile('somefile.xml')

If you are going the other way, then you can use the Save method to save a Clarion structure to the tree, but without creating a file, or string as output. Calling the Save method with just the structure, and boundaries, but no file name or StringTheory object accomplishes this.

From here on you'll use the XmlTree class to manipulate the tree.

Hint: xFilesTree inherits from XmlTree, so all the XmlTree methods are also included in your xFilesTree object. Indeed the xFilesTree object IS an XmlTree.

In order to "walk" around the tree, you will need one or more reference variables. These act as holders - they reference a specific node in the tree. Remember a tree is made up of nodes, and each node is itself a tree. So when you want to deal with one specific node you start by getting a reference to that node.

node &XmlTree

A number of methods exist which help you retrieve the reference to the node yu are looking for. For example, GetNodeByName;

node &= xml.GetNodeByName('price')

In the above line, the tree inside the xml object will be searched for the first node called price. A reference to this node will be returned, and assigned to the node variable. If no node is found then node will be null. You can test this like this;

node &= xml.GetNodeByName('price')
If not node &= null
  ! do something with node
End


When dealing with references like this you MUST always check first if it is null. Failure to do so will result in a GPF.

GetNodeByName also lets you get subsequent instances of the same name;

node &= xml.GetNodeByName('price' ,  , 2)

GetLastNodeByName searches backwards through the tree, if you want the last nodes, not the first one.

GetNodeByname searches the whole tree for the node, although you can limit the depth of the search using the second parameter. This example limits it to the first 3 levels of the tree;

node &= xml.GetNodeByName('price' , 3)

Note that GetNodeByName searches down the tree. It does not find siblings of that name. To find a sibling use GetSiblingByName.

Once you have a node, you may want to get the value of that node. In most cases this is a single string in the XML file, but in some cases a node has multiple values. These values may consist of multiple parts, or indeed have multiple different component types. The GetValue method concatenates all these values together into a single string.

s = node.GetValue()

The GetValue method allows you to determine what component types should be included. By default it includes simple values (xf:value),  cdata values (xf:cdata) and raw values (xf:raw).

so if you pass no parameters then you are doing the equivalent of

s = node.GetValue(xf:value + xf:cdata + xf:raw)

But you could for example, extract just the cdata values using

s = node.GetValue(xf:cdata)

The possible options are xf:value, xf:cdata, xf:raw, xf:comment and xf:pi . Child nodes (xf:node) are ignored by this method. Attributes are not included in this method.

Attributes

Each node can have attributes. Methods allow you to Add, Edit and Delete attributes. These are methods of the tree, so you first set a node reference as per the instructions above.

To add a new attribute, or set the value of the attribute (if it already exists);

node.SetAttribute('somename','somevalue')

To get the value of an attribute;

s = node.GetAttribute('somename')

Of course an attribute could be blank ( <product color=""> ) or missing (<product>) and the difference may be important to you, so

x = node.HasAttribute('somename')

returns true or false.

DeleteAttribute works as you would expect;

node.DeleteAttribute('somename')

Finally, attributes can be sorted alphabetically;

node.SortAttributes()

It can be useful to do a search on the tree, so you can use the GetChildWithAtttribute method to return a node which contains a specific attribute;

child  &XmlTree
  code
  child &= node.GetChildWithAttribute('something')

Children

Each node can have multiple children.

Each child has a type.
Type Description
xf:value A typical text, or numeric value
xf:cdata A text value, but wrapped in a CDATA boundary. This is sometimes used when the data could contain reserved XML characters (like & and < and >) but is not capable of containing binary values.
xf:raw A block of raw text that is not validated or encoded. Putting raw text into your XML could break it. Use with caution.
xf:comment An XML comment. These have the form
<!-- some comment -->
This gets injected into the XML at this point, but is typically ignored by parsers.
xf:pi A processing instruction. These have the form;
<? something ?>
They are not common, except for the start of each XML file which should have
<?xml version="1.0" encoding="UTF-8"?>
xf:node A reference to another node in the tree.
The number of existing children is returned by the records method;

n = node.records()

To add a child to the node you can use the AddChild method;

p = node.AddChild(xf:value,'some value')

or in the case of a new node

p = node.AddChild(xf:node,'SomeNewNodeName')

Because a node can have multiple children, each child has a Position. AddChild returns the position it got added into. Note that this position can change as other children are added, or deleted.

Children can be deleted by calling DeleteChild with the position number. (This changes the position number of all subsequent children.)

node.DeleteChild(4)

To set the value for an existing child (not a xf:node) use the SetChild method

result = node.SetChild('somevalue',index)

You can get the value of a specific child node (not an xf:node) using

s = node.GetChildValue(index)

You can loop through the children using the following code;

loop x = 1 to node.records()
  Get(node.children,x)
  case node.children.ComponentType  
  of xf:node
    child &= node.Get(x)
  Else
     node.children.value.GetValue() ! node.children.value is a StringTheory object.
  End

Result

Once you have finished manipulating the tree you can then save it to a file, or string, using the SaveFile or SaveString methods.

If you are loading the tree, then you can use it using the regular xFilesTree.Load method, without specifying the source as a filename or string. In that case the Clarion structure (File, Queue, Group) will simply load from the existing tree.

xFiles Templates

The Global xFiles Extension Template

In order to use xFiles, you need to add the Global Extension Template.

General Tab

Disable All xFiles Features
If this is on then no xFiles Template code will be generated in the application. Any hand-coded calls will usually result in compile errors.

xFileXML Tab

NOTE: The xFileXML class is deprecated. the xFilesStream class is a drop in replacement. However these global options are only applied to the xFilesXML objects, created by local templates. These template options, like the class are deprecated. They are included for backward compatibility only.

Multi-DLL Tab

This is part of a Multi-DLL program
If your app system consists of multiple apps, then this option should be on for all the apps in the system, including DLL and EXE apps. Failure to turn on this switch in the EXE app (if xFiles is included in that) will likely result in a GPF when xFiles is used in that app.

This is part of a Multi-DLL program
If this is a multi-app system then one DLL is chosen to export the XML class to the rest of the apps. This is typically the Data DLL (although it doesn't have to be.) One app, and only one app, in the system should have this switch on.

Classes Tab

The classes tab manages the current cache of the xFiles Include files. If you encounter errors after upgrading, then it's a good idea to come here and click the Refresh Classes button.

Local xFiles Template (IncludexFilesObject - Use an xFiles Object)

xFiles provides a template to make it simple to add an object to a procedure. The value of adding the object via a template is that all the possible methods are generated, for easy embedding. If you are wanting to write embed code into methods, then use this template to declare the object. Otherwise simply declare the object in embed code as well.

General Tab

Do Not Generate This Object
If this option is on then the template will not generate the object, or any of it's methods.
Object Name
The name of the object in the procedure. If the object is alone in the procedure then use a simple name like XML. if there are multiple objects in the procedure, then give it a unique name.
Class Name
Select a class from the drop-down. Note that the xFileXML class is deprecated and should not be used for new objects.

xFileXML Tab

This tab is only offered for the xFileXML object, which is deprecated. For new code use the xFileStream or xFileTree class instead.
Disable All xFiles Features



To use the template: Extension screenshot The class details tab allows properties to be added to the class and methods to be added, overloaded or customised and provides access to the method and data embeds for the object.

Export to/Import from XML Control Template

You can populate this control template onto a window, and export a file/queue/group to a file from a button on the window.
 Control Buttons screenshot You can setup the following options in the control template: Control Template General Control Template Options tab screenshot

Some Practical Applications

Creating RSS Feed Files

RSS feeds are a popular way of "publishing" Web links, especially if the links are changing, or information in the links are changing. For example the CapeSoft web site has an RSS feed (https://www.capesoft.com/CapesoftAccessoriesRSSFeed.xml) which is updated whenever an accessory is released.

xFiles 4 makes it easy to create RSS files, because most of the work is done in declaring the structure.  Here is an example of a minimal structure;

Rss              Group,name('rss')
version            String(20),name('version | attribute')
Channel           
Group,name('channel')
Title               
String(255),name('title')
Link                
String(255),name('link')
Description          String(1024),name('description')
Items                &ItemsQueueType,name('items | queue | rename() | rowname(item)')
                   End           
                
End 
                
ItemsQueueType   Queue,TYPE
Title             
String(255),name('title')
Link              
String(255),name('link')
Description        String(1024),name('description')
                 End


Both the Channel group and the ItemsQueueType can have extra fields if you wish. For example;

Rss              Group,name('rss')
version            String(20),name('version | attribute')
Channel           
Group,name('channel')
Title               
String(255),name('title')
Link                
String(255),name('link')
Description          String(1024),name('description')
Language            
String(100),name('language')  ! optional                   
PubDate             
String(100),name('pubDate')   ! optional
lastBuildDate       
String(100),name('lastBuildDate') ! optional
Docs                
String(255),name('docs')         ! optional
Generator           
String(255),name('generator')     ! optional
managingEditor      
String(255),name('managingEditor') ! optional
WebMaster           
String(255),name('webMaster')      ! optional
Items                &ItemsQueueType,name('items | queue | rename() | rowname(item)')
                  
End            
                
End 
                
ItemsQueueType   Queue,TYPE
Title             
String(255),name('title')
Link              
String(255),name('link')
Description       
String(1024),name('description')
PubDate           
String(100),name('pubDate')  ! optional
Guid              
String(100),name('guid')     ! optional           
                 End

Declare the object. In this case because of the nested queue, the xFilesTree class will be used.

xml  xFilesTree

  CODE


Populating the group, and queue is straight-forward. First the group;

  rss.version = '2.0'
  rss.channel.title = 'CapeSoft'
  rss.channel.link = 'https://capesoft.com'
  rss.channel.description = 'all things CapeSoft' 
  rss.channel.Items &= new ItemsQueueType

then the Queue. Obviously you wold populate this queue with your own information, sourced from your database or wherever.

  rss.channel.Items.Title = 'xFiles 4'
  rss.channel.Items.Link  = 'https://capesoft.com/accessories/xFilessp.htm'
  rss.channel.Items.Description = 'XML import and export library for Clarion'
  rss.channel.Items.Pubdate = '15 August 2022'
  Add(rss.channel.Items)


you can add as many records to the queue as you like.

Then you can create the xml file.

  xml.start()
  xml.SetTagCase(xf:CaseAsIs)
  xml.SetDontSaveBlanks(true)
  xml.Save(rss,'rss.xml','rss')


Before finishing, to prevent memory leaks;

  xml.DisposeGroup(rss)

Storing Global Settings in an XML file

Many people are used to storing global settings in INI files, or perhaps the Windows registry. There' s a much easier way using xFiles to store global settings.

Because xFiles stores (and retrieves) a generic group structure, you can add variables without worrying about updating your store variable procedure. The only thing you need to be aware of is that variables stored (and retrieved) must be within a group structure. For example:

GlobalSettings       group, pre(GLOSET) ! This group will be stored in the XML settings file
WebSite               string(252)
EmailAddress          string(252)
                    end
FTPPostWindowThread  long              
! Not stored in the XML settings file

You could declare a global object to load, and save this XML, or you could put the code into two small source procedures - one to load, one to save. The procedure approach is shown here;

LoadSettings   Procedure()
xml  xFilesStream
  code
  xml.start()
  xml.SetTagCase(xf:CaseAny)
  xml.Load(GlobalSettings,'GlobalSettingsFile.xml')

SaveSettings  Procedure()
xml  xFilesStream
  code
  xml.start()
  xml.SetTagCase(xf:CaseLower)
  xml.Save(GlobalSettings,'GlobalSettingsFile.xml')



Creating SOAP requests

SOAP Servers are programs out on the network that you can interact with. Each program is different, but a protocol, SOAP, exists which makes it possible for different programs to communicate with each other.

SOAP packets are usually passed between programs using TCP/IP connections, and wrapped with a HTTP header. A tool like NetTalk makes this really easy to do. However inside the packet the request is formatted using XML, and thus XFiles can be very useful in this regard.

A complete description of SOAP is beyond the scope of this document, however you do not usually need to be a SOAP expert in order to make simple SOAP servers and clients.

You will find some working examples in \Clarion\Accessory\Examples\SOAPClient. These examples use NetTalk to run, but you can convert them to use other networking tools if you wish.

A SOAP envelope is automatically wrapped around your regular XML if you set the .SOAPEnvelope property to true.

A typical SOAP request looks like this;
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ChangeLengthUnit xmlns="http://www.webserviceX.NET/">
      <LengthValue>5</LengthValue>
      <FromLengthUnit>Centimeters</FromLengthUnit>
      <ToLengthUnit>Inches</ToLengthUnit>
    </ChangeLengthUnit>
  </soap:Body>
</soap:Envelope>

However different soap servers require different specific tags. To support this xFiles allows you to set the specific text for each part of the packet. Here is the request again, but this time the optional parts have been replaced  with the name of the property you can set.

<?xml version="1.0" encoding="SaveEncoding"?>
<SOAPEnvelopeBoundary SOAPEnvelopeBoundaryAttribute>
SOAPHeader
<SOAPBodyBoundary>
  <_pFileBoundary p_FileBoundaryAttribute>
    <pRecordBoundary RecordBoundaryAttribute>
      <LengthValue>5</LengthValue>
      <FromLengthUnit>Centimeters</FromLengthUnit>
      <ToLengthUnit>Inches</ToLengthUnit>
    </_pRecordBoundary>
  </_PFileBoundary>
</SOAPBodyBoundary>
</SOAPEnvelopeBoundary>


The above properties can all be set in the .SaveTweakSettings method. Note that the _pFileBoundary tag is optional, and should be set to nothing if it is not required.

Other properties you need to set are .SOAPEnvelope(set to true to wrap the XML in a Soap Envelope) and .TagCase (usually you need to set this to XF:CaseAsIs - more on that in a moment.)

For example, the code to create the packet above looks like this
xml.SOAPEnvelope = true
xml.SaveEncoding = 'utf-8'
Xml._pFileBoundary = ''
Xml._pRecordBoundary = 'ChangeLengthUnit'
Xml.RecordBoundaryAttribute = 'xmlns="http://www.webserviceX.NET/"'
Xml.TagCase = XF:CaseAsIs
xml.SOAPEnvelopeBoundaryAttribute = |
                       'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' & |
                       ' xmlns:xsd="http://www.w3.org/2001/XMLSchema"' &|
                       ' xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"'
xml.SOAPEnvelopeBoundary = 'soap:Envelope'
xml.SOAPBodyBoundary = 'soap:Body'
xml.SOAPHeader = '<soap:Header/>'


For the actual XML in the middle, a simple group is all that is required.
RequestGroup   GROUP,PRE(Request)
LengthValue      DECIMAL(9,3),NAME('LengthValue')
FromLengthUnit   STRING(32),NAME('fromLengthUnit')
ToLengthUnit     STRING(32),NAME('toLengthUnit')
              END


(Take notice of the Name attribute, more on that in a minute.)

After setting all the values in the group to their desired state, you can then create the PostString (ready for the NetTalk side of things) using 2 simple lines of code.
xml.Save(RequestGroup)
PostString = xml.xmldata


The call to Save converts the group into XML, and stores the result in the .XmlData property. Because the .SOAPEnvelope property (and all the other properties) have been set appropriately in .SaveTweakSettings, the text that is in .XmlData is the complete SOAP request.

It is worth mentioning a slight complication alluded to earlier which needs to be taken into account. Most SOAP servers are picky, and require the tag names to be case sensitive. In other words
<LengthValue>5</LengthValue> is not the same as
<lengthvalue>5</lengthvalue> which is not the same as
<lengthValue>5</lengthValue>

The most common mistake when speaking to a SOAP server is getting these tag names not-quite-right, and hence the server returns an error. xFiles supports a variety of possible tag cases (when creating the XML) and these are set using the .TagCase property as we saw above. Possible values for .TagCase are XF:CaseLower, XF:CaseUpper, XF:CaseAny and XF:CaseAsIs. The default is XF:CaseAny, which means that xml files being loaded are Case Insensitive. For a Save this implies that the case of the External Name will be used (if it exists) and the tag is Upper Case if there is no external name.

If your SOAP server requires upper, or lower case tags, then you don't need to worry about the External Name for each field, just set the .TagCase property to the appropriate value and continue. If however the server requires a mixed case (as this example does) then you need to set the Name for each item in your group. Be very careful with the case - in this example the LengthValue tag started with a capital letter, but the fromLengthUnit tag did not.

But wait, there's more. Every so often you come across a SOAP packet that has 2 (or possibly more) parts. Something like this;
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <AuthHeader xmlns="urn:schemas-cardinal-com:2006:04:alaris:gateway:alarisgateway">
      <ClientIdentifier>guid</ClientIdentifier>
    </AuthHeader>
  </soap:Header>
  <soap:Body>
    <QueryPatientId xmlns="urn:schemas-cardinal-com:2006:04:alaris:gateway:alarisgateway">
      <deviceId>string</deviceId>
    </QueryPatientId>
  </soap:Body>
</soap:Envelope>


Now this SOAP Packet contains 2 parts, a Header part and a Footer part. Which effectively means the SOAP packet contains 2 structures (in this case 2 groups). The key to understanding how to create this packet rests on two items;
a) See this primarily as a Multi-Part xml file, not as a SOAP packet
b) follow the directions below "Storing Multiple Things in the same XML file"
Aside: A small number of servers require an empty header in order to process the packet correctly. The SOAPHeader property has been included so that you can inject specific text (for example <soapenv:Header/>) between the SOAP Envelope and the SOAP Body. This property is not formatted in any way, if you use it you will need to ensure it is valid xml.

For completeness sake, here is the code that creates the above packet. However you will need to read the section below before this code will make sense.
Structures
AuthHeader         GROUP,PRE()
ClientIdentifier     STRING(255),NAME('ClientIdentifier')
                   END
QueryPatientId     GROUP,PRE()
deviceId             STRING(255),NAME('deviceId')
                   END

Code
xml.SaveEncoding = 'utf-8'
xml.TagCase = XF:CaseAsIs
xml.RootBoundary = 'soap:Envelope'
xml.RootBoundaryAttribute = |
               'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' &|
               ' xmlns:xsd="http://www.w3.org/2001/XMLSchema"' &|
               ' xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"'
xml.DontCloseTags = 1
Xml.RecordBoundaryAttribute = 'xmlns="urn:schemas-cardinal-com:2006:04:alaris:gateway:alarisgateway"'
xml.save(AuthHeader,'soap:Header','AuthHeader')

xml.append = 1
xml.DontCloseTags = 0
xml.Save(QueryPatientId,'soap:Body','QueryPatientId' )


As you can see the above commands are Saving the structures to a string, not a file, and as will all strings the result is in xml.xmldata.

Importing Large Files

Importing XML files can ususally just be done using a xFilesStream, or xFilesTree object. However once files get large (say greater than 100 megs) then RAM, and specifically Ram limitations start to be important.

Ram Limits

The key Ram limitation is caused by Clarion compiling 32 bit programs. Each program has access to 2 gigabytes of Ram, or 3 gigabytes if LARGE_ADDRESS is added to the EXP file.

Ram Usage - Import

RAM is used in several ways during the import;
  1. Original XML loaded into memory
  2. XML Tree created (xFilesTree class only)
  3. Clarion structure populate (queues only)
In a perfect storm, you are loading a large XML file, using the xFilesTree class, into Clarion queues. If the file is sufficently large then you may run out of memory, as each step would take substantial amounts of RAM.

RAM usage can be reduced in a number of ways;
  1. Make use of the SetChunk method to set the "chunk" of the string to load at a time. This approach though is not ideal for utf-8 or utf-16 encoded files, which contain multiple code-points per character. Currently this option only applies to the xmlTree and xFilesTree classes.
  2. Use the xFilesStream class instead of the xFilesTree class. This save significant ram as the XML tree structure is not created. If the XML is complex (ie queues in queues etc) then see the discussion on hybrid imports below.
  3. Consider importing into Table structures rather than Queue structures. Any on-disk table (ie not Memory table) will reduce ram usage, but will slow down processing (as those records are presumably moved to more permanent storage.)
  4. Consider processing imported queues, as they are being imported, so as to reduce the size required by the queues.

Hybrid Imports

One key benefit of the xFilesTree class over the xFilesStream class is that it supports nested queue structures wasily, and ellegantly whereas nested queues in xFilesStream are clumsy to deal with. However a hybrid approach reduces the size of the XMLTree and also simplifies the importing code for xFiles Stream.

The key magic is performed in the xFilesStream class, in the (new) AssignQueue method. This method is called whenever a queue field (marked with the Extended Attribute Queue) is encountered in the parent structure. The name of the XML node, (the "tag") and the contents of the tag are passed into this method. By adding code to this method you can import the sub-structure, perhaps using the xFilesTree class.
For example;

Consider this structure

<export>
  <invoices>
    <invoice>
      <customer>John</customer>
      <lineitems>
        <lineitem><product>Milk</product></lineitem>
        <lineitem><product>Bread</product></lineitem>
        <lineitem><product>Salt</product></lineitem>    
      </lineitems>
    </invoice>
    <invoice>
      <customer>Mike</customer>
      <lineitems>
        <lineitem><product>Beef</product></lineitem>
        <lineitem><product>Pork</product></lineitem>
        <lineitem><product>Chicken</product></lineitem>
      </lineitems>
    </invoice>
  </invoices>
</export>


Clearly this structure consists of a queue (linitems) inside a queue (invoices) so is a prime candidate for xFilesTree.

Feeding this into capesoft.com\xfilescode we get the following matching clarion structures;

invoices                 Queue,Name('invoices | RowName(invoice)')
customer                   STRING(255),Name('customer')
lineitems                  &lineitemsQueueType,Name('lineitems | queue | RowName(lineitem)')
                         End

lineitemsQueueType       Queue,Type,Name('lineitems | RowName(lineitem)')
product                    STRING(255),Name('product')
                         End


The code for the xFilesTree class is also generated. However if the XML file itself is large (say 300 megs) then this will create a very large XML Tree, which will use a substantial amount of RAM. Switching to the xFilesStream class saves the RAM, but xFilesStream does not automatically do child queues. However it can easily do child queues manually, using the AssignQueue method. So the code becomes;

xmlInvoices     Class(xFilesStream)
AssignQueue       Procedure (String pTag, String pString),Derived
                End


then to load the file;

xmlInvoices.start()
xmlInvoices.SetTagCase(xf:caselower)
xmlInvoices.load(InvoicesQueue, 'somefile.xml','invoices','invoice')


them, importantly to flesh out the AssignQueue procedure;

xmlInvoices.AssignQueue            Procedure (String pTag, String pString)
xmlt  xfilestree
str   stringtheory
  Code
  str.setvalue(pString,st:clip)
  Case pTag
  Of 'lineitems' 
     invoices.lineitems &= NEW lineitemsQueueType
     xmlt.start()
     xmlt.SetTagCase(xf:caseLower)
     xmlt.load(invoices.lineitems,str,'','lineitem')
  End       
  Return


This hybrid approach means that the main class (xFilesStream) only creates a short-lived XmlTree structure for each node, and then immediately moves that node into the queues. This gives the benefit of deeply nested structures, while only creating a tree necessary for one parent structure at a time.

xCell Class

Introduction

Spreadsheet programs have the ability to load and save spreadsheets in a specific XML format. The xCell class in xFiles can create an XML file of this structure, which in turn can be opened by Microsoft Office, Apache Open Office, or Libre Office.

To create a document you:
  1. Declare an object
  2. Create a worksheet
  3. (Optionally) Create Styles
  4. (Optionally) Apply Styles to Columns
  5. (Optionally) Apply Styles to Rows
  6. Add Data into Cells
  7. Save the XML to a file.
The XML file needs to contain a number of different parts of information which in turn are collated together to make the final XML file.
There are a number of methods that can be used to create and delete worksheets, manage styles, and set cell contents. Unlike the xFilesXML class  this class does not just simply take a specific structure and turn it into XML. You will need to write the various loops you require yourself.

Declare an Object

The first step is to create an appropriate object. In this example the object will be called ExcelExport:

ExcelExport xCell

Reuse

An object can be reused multiple times. To restore the object to its initial state use the Start method. For Example;

ExcelExport.Start()

Encoding

Encoding is a little complicated. Basically you need to match the encoding of your data with the encoding in the XML output. There are two properties which come into play;
ExcelExport.SaveEncoding . This defaults to ISO-8859-1. If you are using characters not in the ISO-8859-1 set then you probably will want to set this to utf-8.

If the encoding is set to utf-8, then the data may need to be converted to utf-8. This is done by default (using a property DontUTF8Encode which defaults to false) however if your data is already in utf-8 form then this needs to be true.

These two properties best help you move the data from it's existing form into the XML. Here is the matrix;

Your Data Is Set SaveEncoding to SetDontUtfEncode to
Already Utf-8 'utf-8' true
Plain ASCII leave as default leave as default
Uses Extended characters, not part of ASCII 'utf-8' false
Unusually, these properties need to be set BEFORE the call to the Start method. They are not reset by the Start method.

For example;

ExcelExport.SaveEncoding = 'utf-8'
ExcelExport.DontUtf8Encode = true
ExcelExport.Start()

WorkSheets

You need to create a worksheet onto which the data will go. You can have as many worksheets as you like, but they should be added in the order (from left to right) that you want them to appear in the document.

ExcelExport.SetWorkSheet('Some Sheet Name')

The length of the name is limited to 31 characters. If the name is longer then that then a warning will be generated when Excel opens the spreadsheet, and the name will be truncated. The RenameWorkSheet method can be used if you wish to rename the worksheet after it has been created.

ExcelExport.RenameWorkSheet('Some Old Name','Some New Name')

When you are ready to work with a specific sheet you specify it using the WithWorkSheet method. The same name that was used when the sheet was created is now used to identify the sheet. If a sheet of that name is not found, then one will be created with that name.

ExcelExport.WithWorkSheet('Some Sheet Name')

There is also a method to select the sheet by number

ExcelExport.WithWorkSheetNumber(1)

Bear in mind that if worksheets are deleted then the WorkSheet number of a specific sheet can change. Worksheets are numbered with sequential positive integers, so if worksheet 2 of 3 is deleted, then worksheet 3 becomes worksheet 2. If a worksheet with that number does not exist then the method returns a non-zero number. If the worksheet is found then the method returns 0.

Worksheets can be deleted using the DeleteWorkSheet method. This method takes a name, as set by SetWorkSheet. If the worksheet is not found then nothing is deleted. Regardless of success or failure the first worksheet will be selected for subsequent calls, so an immediate call to WithWorkSheet is usually desired. When a worksheet is deleted then all the row and column data associated with the worksheet is deleted. Styles are not associated with one sheet, so deleting a sheet does not clear any of the styles.

ExcelExport.DeleteWorkSheet('Some Sheet Name')

You can also delete all worksheets in a single call by calling

ExcelExport.FreeWorkSheets()

Printing

You can control the desired (maximum) with and height of the report output for the work sheet using the SetSheetPrintFit method. This takes the width and height (in pages) of the (maximum) printed output you desire. (If the spreadsheet will fit on fewer pages anyway, then it will just be on fewer pages.)

ExcelExport.SetSheetPrintFit(2,2)

If you wish to set the width and height separately then you can use SetSheetPrintFitWidth and SetSheetPrintFitHeight. these take one parameter, being the width and height respectively.

The orientation of the page can be set using the SetPageOrientation method. This takes a single parameter, a string  starting with 'L' (or '1') for landscape or a string starting with 'P' for portrait mode. If the parameter is anything else then the mode is set to Portrait. The equates xf:Landscape and xf:Portrait can also be used here.

ExcelExport.SetPageOrientation(xf:Landscape)

You can also set headers and footers for the printed output. The SetSheetHeader method takes 4 parameters; 3 strings and a real.
The strings represent text that can be left, center, and right justified in the header. The fourth (optional) parameter sets the margin (in inches) between the edge of the page and the header text.

Each string can contain a number of Tokens, which will be filled in by Excel at runtime.
Token Description
&P Page Number
&N Number of Pages
&D Date
&T Time
&Z File Path
&F File Name

For example

ExcelExport.SetSheetHeader('&D &T','Customer Report','&P / &N',0.5)

This puts 3 strings on the top of each page, a half inch from the top of the paper.

An alternate form of the SetSheetHeader method exists, which takes an optional single string and an optional margin parameter. You can use three additional tokens in this string;

Token Description
&L Justify following text to the Left
&C Justify Following text in the Center
&R Justify Following Text to the Right

For example

ExcelExport.SetSheetHeader('&L&D &T')
ExcelExport.SetSheetHeader(,0.4)


The SetSheetHeaderMargin method allows you to set the margin field without affecting the text.

ExcelExport.SetSheetHeaderMargin(0.5)

SetSheetFooter methods exist, which are named and behave exactly as their SetSheetHeader counterparts.

The header and footer margins only apply to the header and footer respectively. These are independent of the page margins. The data cells only use the page margins when determining their position. Therefore if margins are not set correctly, it is possible for the cell data to overprint the header and footer data.

You can set the page margins using the method SetSheetPageMargin. This takes four (optional) REAL parameters, top, bottom left and right. As before the value for these fields are in inches. for example

ExcelExport.SetSheetPageMargin(1,1,1,1)

Styles

Styles can be created,  properties for the style can then be set as desired and styles can be applied to cells.

To create a style use the SetStyle method. This takes three settings, the style ID, the style name and optionally the parent. For example;

ExcelExport.SetStyle('id','name')

If the first parameter is omitted then the next sequential style ID is used. The ID is returned by the method. The style name is also omittable.

Once a style has been created you can assign many different properties to it using one of the following methods:

ExcelExport.SetStyleAlignment('id','vertical','horizontal','rotate','wraptext')

Vertical must be one of: Top, Center, Bottom, Justify, Distributed

Horizontal must be one of: Left, Center, Right, Distributed

Rotate must be a number from -90 to 90.

Wraptext must be either 0 (no) or 1 (yes).

ExcelExport.SetStyleInterior ('id','color','pattern')

Pattern is one of the following values;
Solid, Gray75, Gray50, Gray25, Gray125, Gray0625, HorzStripe, VertStripe, ReverseDiagStripe,DiagStripe, DiagCross, ThickDiagCross, ThinHorzStripe, ThinVertStripe, ThinReverseDiagStripe, ThinDiagStripe, ThinHorzCross, ThinDiagCross


ExcelExport.SetStyleFont('id','fontname','family','size','color',bold,italic,'underline')

The color is done in web format (#112233). However if the color is passed as a LONG (ie a Clarion color, without the leading #) then it will automatically be converted into a web color for you.
Underline should be one of '', 'single' or 'double'. A 1 for single underline, or 2 for double underline are also supported.

ExcelExport.SetStyleNumberFormat('id','format')

The number format matches the format string as you would set it in Excel itself. Utility methods do exist to convert Clarion date and time pictures into their matching Excel format (see below).

ExcelExport.SetStyleBorder('id','position','lineStyle','weight','color')

The color is done in web format (#112233). However if the color is passed as a LONG (ie a Clarion color, without the leading #) then it will automatically be converted into a web color for you.

To delete a single style you can call

ExcelExport.DeleteStyle('id')

To clear the current style settings you can call

ExcelExport.FreeStyles()

Utility methods

Utility methods exist for converting from common Clarion data types to Excel Data types..

ExcelExport.AddDatePicture('picture') and ExcelExport.AddTimePicture('picture') automatically create styles, and set the number format, for a specific Clarion date or time picture. The name of the style created is dnnn for dates and tnnn for times where nnn is the clarion picture number. For example;

ExcelExport.AddDatePicture('@D6')

would create a style called d6 which matches the date format of dd/mm/yyyy


Columns

You can set properties for an entire column using the SetColumn method.

ExcelExport.SetColumn(index,'styleId','AutoFitWidth','width')

The index is the column number (where column A = 1, B=2  and so on.)
If you set a styleId here, you must also be sure to create that style using the SetStyle method.
AutoFitWidth is optional. It should be set to 0 or 1. If 1 then only numeric and date columns are auto-fit, Text columns are not auto-fit by Excel.
Width is optional, set this to the specific column width you want.

You can remove the settings for one column using

ExcelExport.DeleteColumn(index)

or all columns using

ExcelExport.FreeColumns()

Rows

As with columns you can set properties for a whole row.

ExcelExport.SetRow(index,'StyleID','AutoFitHeight','height')

You can remove the settings for one row using

ExcelExport.DeleteRow(index)

or all rows using

ExcelExport.FreeRows()

Cells

Obviously the point of the class is ultimately to get data into cells. This is done using the SetCell method.

ExcelExport.SetCell(rowIndex,columnIndex,'data','styleID','formula','type','mergeacross','mergedown')

The row and column indexes indicate the cell which is being written to. Both are incremental integers starting from 1.

All the fields after columnIndex are optional, and multiple calls can be made to set the various properties for the same cell. In other words you might call SetCell twice for the same cell, once to set the data and a second time to set the style.

The data field is the value you want to appear in the cell.

The StyleID matches the id of some style you have set.

The Formula field contains any Excel formula (as you would expect to see it in the spreadsheet.)

The Type field can be either 'Number' or 'String'. If omitted then the class looks at the data field to determine if the cell should be a number or string.

The MergeAcross and MergeDown fields allow you to set the extent of this cell in the worksheet.
If you do merge a cell across multiple cells on the worksheet, then be careful not to call SetCell for those other cells. If you do so the spreadsheet may not load into Excel etc.

You can remove a cell using

ExcelExport.DeleteCell(rowIndex, columnIndex)

You can remove all cells for a specific row using

ExcelExport.FreeCells (rowIndex)

Date and Time Fields

Setting a cell to contain a date, or time, is a two-fold step.

Add Styles

Firstly you create one or more styles with the date and/or time pictures you want to use, as described in the Utility Methods section above.

ExcelExport.AddDatePicture('@D6')
ExcelExport.AddTimePicture('@T1')


For more complex formats (Excel supports more formats than Clarion) and also formats that combine date and time in a single cell you will need to use the SetStyleNumberFormat call when adding a style. For example;

ExcelExport.SetStyle('dt1')
ExcelExport.SetStyleNumberFormat('dt1','[$-409]m/d/yy\ h:mm\ AM/PM;@')

The format used in the SetStyleNumberFormat call can be quite complex. The best way to figure out the style you want is to make a spreadsheet with a cell of the correct style, export that to XML and inspect the XML.
The data is simply the date and time values in the same string, separated by a T symbol.


SetCell - Harder Method

Secondly, when adding a cell set the type to DateTime.

ExcelExport.SetCell(row,column,'2017-12-31','d6',,'DateTime')
ExcelExport.SetCell(row,column,'1899-12-31T' & '2:05' ,'t1',,'DateTime')


As you can see from the above, the data in the SetCell call (the 3rd parameter) should be formatted as @0D10-B for dates and @T1 or @T4 for times. If you are setting just a time, then prefix the time with the generic date, as in the example above.

If desired you can store a Date and Time value together in a single column. As above this consists of setting a style for the cell, and then adding data into the cell.

ExcelExport.SetCell(row,column,'2017-12-31T2:05' ,'dt1',,'DateTime')

Set Cell - Easier Method

This is an alternative to the "secondly" part. You still need to add styles as per the above.

To make all this a bit easier there are 3 specialized SetCell methods that can be used;
SetCellDate, SetCellTime and SetCellDateTime.

these methods follow the same prototype as SetCell, but drop the Type parameter. The Date and Time parameters are Clarion LONG's, so no formatting of the data is needed.

ExcelExport.SetCellDate (rowIndex,columnIndex,date,'styleID','formula','mergeacross','mergedown')
ExcelExport.SetCellTime (rowIndex,columnIndex,time,'styleID','formula','mergeacross','mergedown')

ExcelExport.SetCellDateTime (rowIndex,columnIndex,date,time,'styleID','formula','mergeacross','mergedown')

For example;

ExcelExport.SetCellDate(row,column,someDate,'d6')
ExcelExport.SetCellTime(row,column,someTime,'t1')

ExcelExport.SetCellDateTime(row,column,somedate,sometime ,'dt1')



Formulas

Formulas can be added using the syntax

ExcelExport.SetCell(rowindex, columnindex, , styleid, 'someformula')

Remember rowindex and columnindex are integers, not letters.

The important thing to get right though is the text which makes up 'someformula'. This is not just the same as the text you see on the screen in the Excel Cell, Excel stores it slightly differently.

When constructing a formula you don't use the cell naming scheme as you would in the spread sheet itself. As an example, say you had the formula in the spreadsheet in cell E7, which was represented in the spreadsheet as
=$E$1+$E$2
In the XML file this is represented using RyCx syntax. For example the same formula is written as
R1C5+R2C5
(Read this as row1, column 5 plus row 2 column 5). The $ signs in the original formula mean these cells are "fixed".

A more common way of doing formulas in a spreadsheet is by using relative positions. So if cell F7 was set to =E1+E2 then it's understood that the seventh row is a sum of the first two rows of the previous column. The cell F7 is relative to cells E1 and E2. This is written like this;

R[-6]C[-1]+R[-5]C[-1]

If it was a sum in the same column, then [0] is not included so it becomes

R[-6]C+R[-5]C

Here's another example, this time a horizontal sum across 4 cells, placing the answer in the fifth cell

=SUM(RC[-4]:RC[-1])

Hint: Probably the easiest way to know what the syntax of the formula should be is to create it in a spreadsheet, then Save the spreadsheet as an XML file, then inspect the XML file.

Saving

Up to this point the spreadsheet has been created in memory. Once the spreadsheet is completed it can be saved to the disk using the Save method.

ExcelExport.Save('filename')

If you wish to save it to a string, and not to a file on the disk, then you can do

ExcelExport.Save()

At this point the xml is in the xmlData property of the object.

Example

Putting all the above together, this creates a simple spreadsheet containing two columns of numbers, with a total at the bottom of each column.

ExcelExport.Start()
ExcelExport.SetWorkSheet('Example')
ExcelExport.WithWorkSheetNumber(1)

ExcelExport.SetStyle('xf','xFiles')
ExcelExport.SetStyleFont('xf','Segoe UI',' ',12,Color:Navy)

ExcelExport.SetStyle('tl','Total')
ExcelExport.SetStyleInterior('tl',Color:Maroon)
ExcelExport.SetStyleFont('tl','Segoe UI',' ',12,Color:White)

ExcelExport.SetRow(3,'tl') ! Set Total row to Total Style

ExcelExport.SetCell( 1,1,123,'xf')
ExcelExport.SetCell( 1,2,456,'xf')
ExcelExport.SetCell( 1,3,789,'xf')
ExcelExport.SetCell( 2,1,987)
ExcelExport.SetCell( 2,2,654)
ExcelExport.SetCell( 2,3,321)

ExcelExport.SetCell( 3,1, , , '=SUM(R[-2]C:R[-1]C)')
ExcelExport.SetCell( 3,2, , , '=SUM(R[-2]C:R[-1]C)')
ExcelExport.SetCell( 3,3, , , '=SUM(R[-2]C:R[-1]C)')

ExcelExport.Save('c:\temp\a.xml')


Examples

Example Comments
ABC ABC example
Legacy Legacy example
MultiDllABC Multi DLL ABC example
MultiDllLegacy Multi DLL Legacy example
UnitTest A test app which tests various functionality in the class.
XMLClarionTest A test app for testing the structure generating functionality.

Class Reference

Class Description
xFileSettings An easy to use replacement for GetINI and PutINI - writes to XML files instead of INI files.
xFilesStream A streaming based parser, used to create and consume XML directly into and out of Clarion structures, such as Group, File, Queue and VIew.
xFilesTree A tree based parser, used to create and consume XML directly into and out of Clarion structures, such as Group, File, Queue and VIew.
Includes a tree representation of the XML for easy manipulation
XmlTree An XML tree of nodes.
xFilesPropertyClass A properties class for the XMLTree class. Is not usually used directly.
xCell A class used to create Open Spreadsheet XML files, which can then be opened in Excel, or other spreadsheet programs.
xFileBase Internal base class, offers some functionality to the xFilesStream class. Should not be used directly.
xFileXML Based on xFilesStream, but with some different default values to make it compatible with xFiles 3. This class is deprecated, and should not be used. Use the xFilesStream class or xFilesTree class instead.


xFileBinary
(deprecated)
This class is deprecated, and should not be used.
Binary file handling - high speed reading and writing of all file types (binary and text) with minimal coding (simply call a single method to load the file into memory using a string or save a file from memory to disk.)
xFileExplode
(deprecated)
This class is deprecated, and should not be used.
Handling decompression of files compressed using the PKWare implode/explode algorithm. Does not support compression.
xFileFastTimer
(deprecated)
This class is deprecated, and should not be used.
High speed, accurate timers.
xFileLinkDLL
(deprecated)
This class is deprecated, and should not be used.
Easy support for run time linking of DLLs and locating procedures in the DLLs when loaded.
xFileZip
(deprecated)
This class is deprecated, and should not be used.
Handles compression and decompression using a ZIP algorithm.
xFileBaseCompress
(deprecated)
This class is deprecated, and should not be used.
Internal base class for compression.

Class Hierarchy

Classes

xFilesPropertyClass

This class holds the properties for the xmlTree class. All nodes in the tree reference the same properties object. This class is typically not instantiated separately, and the properties are (usually) not accessed directly. Use the SET and GET methods in the XmlTree class instead.

Properties

Property Description
AddArrayIndexToTag see Arrays.
CodePage
see Encoding: ANSI / Unicode.
DontAbbreviateEmptyTags In XML empty tags can be written as
<tag/> instead of <tag></tag>. By default xFiles will use the abbreviated form, if this is not desired, then call SetDontAbbreviateEmptyTags(true) .
DontSaveBlanks In XML blank tags can be excluded. If this is not desired call SetDon'tSaveBlanks(true).
DontUTFDecode If the incoming XML is UTF encoded, then it will automatically be decoded when added to the tree. Set this to true if the incoming xml should not be decoded.
ForceDecimalEncode128 If you want all extended characters in the output to be encoded as &#nnn; then set this to true.
LoadEncoding
You can query this value, using GetLoadEncoding(), after loading an xml string or file. It will contain a String with the encoding name in the XML (before it was decoded.)
OmitXmlHeader If this is true, then the XML header is not included in the output. Default is false.
<?xml version="1.0" encoding="UTF-8"?>
SaveEncoding
Defaults to UTF-8. This is the desired encoding out the XML output.
StandAlone
TagCase Defaults to xf:CaseAsIs. See Tag Case.
TreeEncoding Defaults to xf:Clarion. Can be set to xf:xml. See Encoding: ANSI / Unicode.
Version
Defaults to 1.0. The XML version number of the XML being created.

Classes

XmlTree

This class represents a tree of XML nodes, where each node is either another XMLTree, or a leaf value. As a tree it can be manipulated.
XML can be loaded into this tree via a string, or disk file. And the tree can be exported to a string or disk file.

Each node in the tree points to a common xFilesPropertyClass Object. This allows all nodes to share a set of common properties. The creation and management of this object is handled internally by the XMLTree class.

It acts as the foundation for the xFilesTree class which builds on this class, interfacing the tree to a standard Clarion data structure, like a Group, Queue, File or View.

Queue Types

xFilesAttributesQueueType
AttributeName - Cstring(255) The attribute name.
AttributeValue - &StringTheory The attribute value.
NameSpaceDeclaration - Byte can be true or false. Set to true if the attribute is a name space declaration.
xFilesChildrenQueueType
ComponentType - Byte The type of XML component. Supported types are
xf:value, xf:node, xf:cdata, xf:comment, xf:pi, xf:raw
Node - &XMLTree If the component type is xf:node, in other words it is another XML node, then the child node reference is here.
Value -&StringTheory If the component is not a node, then the value is in here.

Properties

Property Description
Attributes
&xFilesAttributesQueueType
A queue of all the attribute values associated with this node. This is data included in the tag declaration. For example;
<tag whatever="something" whateverElse="somethingElse">
This queue is not typically accessed directly, see the FreeAttributes, SetAttribute, GetAttribute, HasAttribute, DeleteAttribute, and SortAttributes methods.
Children
&xFilesChildrenQueueType
A queue containing all the children of this node. Children can include data (ie leaves of the tree) or other nodes.
ObjectName
AString
The tag name of this node.
ParentObject
&XMLTree
The node immediately above this node in the tree.
Properties
&xFilesPropertyClass
A set of properties, stored in an object, which are shared between all nodes in the tree.

Methods

  • AddChild
  • AddNode
  • AttachNode
  • CheckForUtf16
  • CheckProperties
  • CreateAttributesString
  • CreateChild
  • DeleteAttribute
  • DeleteChild
  • DeleteHeader
  • ErrorTrap
  • Flatten
  • Free
  • FreeValues
  • FreeAttributes
  • Get
  • GetAttribute
  • GetByName
  • GetChildWithAttribute
  • GetFirstChild
  • GetNodeByName
  • GetNthChild
  • GetLastChild
  • GetLastNodeByName
  • GetSiblingByName
  • GetValue
  • GetValueByName
  • HasAttribute
  • HasAttributes
  • HasChildNode
  • HasHeader
  • HasMultipleChildrenAllOfSameName
  • HasSiblingsOfSameName
  • IsRow
  • Load
  • LoadFile
  • LoadString
  • MaxDepth
  • NodeStart
  • ParseAttributes
  • ParseComment
  • ParseCData
  • ParseTag
  • ParseValue
  • ParseXMLHeader
  • Position
  • Records
  • SaveChildren
  • SaveFile
  • SaveNode
  • SaveString
  • SetAttribute
  • SetChild
  • SetHeader
  • SortAttributes
  • Start
  • Trace
  • WalkTree
MyTableBaseClass

Error

Error ()

Description

Returns the contents of the Error property.

Return Value

String

See Also

InterpretError, ErrorCode, ErrorTrap
Classes

xFilesTree

Building on the XMLTree class, this class adds support for Clarion data structures. It allows data in the structures to be exported to the XMLTree, and equally for data in the tree to be copied into the structures.

Properties

Property Type Description
Action Long One of xf:NotSet, xf:Load or xf:Save. Can be used in later methods (like ValidateRecord) to "know" if the object is currently performing a Load or a Save.
DemoMode Long If set to a non-zero value, then the object won't access the Source, but rather generate sample data (based on the source structure). The number of records in the output matches the value in this property.
DemoModeValue Cstring(100) A string, to use as a placeholder for values when in Demo mode.
ExportBlobsWithView Byte If a View is created with no PROJECTed fields, then blobs are not included. To Include Blobs in Views of this kind, set this property to true.
FreeFileBeforeLoad Byte Default is false. If set to true then the Table is emptied before the XML is imported.
FreeGroupBeforeLoad Byte Default is true. The group is cleared before the XML is imported.
FreeQueueBeforeLoad Byte Default is true. The queue is freed before the XML is imported.
Group &Group
JoinBoundaries Byte
LogoutOnImport Byte
LoopedRecords Long
MatchByNumber Byte If true then matches fields by number instead of by name.
NestViewRecords Byte Default is false. If set to true, then child tables in the view will appear as child nodes in the XML.
NoMemo Long
Progress Long
ProgressControl Long
Queue
&Queue
RecordsInserted Long
RecordsUpdated Long
Reflection
&ReflectClass
RootNode
&XmlTree The root node of the XML tree of this object.
SaveFields Long
SaveRecords Long
SkipRecords Long
Table &File
TableQueue
&xFilesTableQueue
TempDeformat &StringDeformat
TempFormat &StringFormat
TempName &StringTheory
TempValue &StringTheory
UpdateFileOnLoad Byte
Using Long
View &View
Worker &StringTheory

Methods

MyTableBaseClass

Error

Error ()

Description

Returns the contents of the Error property.

Return Value

String

See Also

InterpretError, ErrorCode, ErrorTrap
xFileXML Methods
The XFileXML class provides the ability to easily load XML files from disk into a group, file or queue, and save a group, file, view or queue to disk as an XML file. Writing a data structure to disk can be done by calling a single Save method. Similarly, reading an XML file into a group, file or queue is as simple as calling the Load method.
AddRecord Add a record to the object loaded by xFileXML (queues and files only).
AddText AddText into the XML being created. Allows custom text to be injected into the output stream at specific points...
Copy Copy data to or from the data structure being used by xFileXML. Allows the contents of a queue to be copied into a File being used, or data from a different queue or group to be copied into the queue or group used by the xFileXML object.
Init Initialize an xFileXML object to use a particular queue and data structure.
Load Load from file to the data structure.
Save Save to the XML file (or create the XML in memory).
Start Return the object to its initial state.
Advanced Class Methods
Generic File Loading and Saving
SaveBinData Saves the binData string property to the file name specified.
LoadBinData Loads a file into the binData string property.
XML file modification and settings
CreateFooter Creates a string that is appended after the file data.
CreateHeader Creates the XML header that is added to the file.
DisableField Allows you to remove a field from a Table, View, Queue or Group before saving.
LoadTweakSettings Called when the Load is about to be done to allow the object setting to be modified before the load code is executed.
SaveTweakSettings Called when the Save is about to be done to allow the object setting to be modified before the save code is executed.
SetAsAttribute Sets a field to be an attribute, either of the record, or of another field.
UseCharSet Sets the .Saveencoding based on a Clarion Charset Equate.
Record handling and management
AddAtListStart
AddAtRecordStart
AddAtRecordEnd
AddAtListEnd
Called when generating lists. Acts as a point for calling AddText.
GetValue Gets a value of a field when passed the field name (Files only).
SetValue Sets the value of a field when passed the name and value (Files only).
InsertFileRecord Inserts a record in the file (File only).
UpdateFileRecord Updates a record in the file (File only).
RecordCount Returns the number of records in the file or queue.
FreeFileData Deletes all records in the file.
FreeGroupData Clears the group.
FreeQueueData Deletes all queue records.
ValidateRecord Used when reading into a Queue, or File, or writing from a Queue, File, or View. This method allows you to suppress records by returning the value
ValidateUpdateRecord Used when reading into a Queue, or File.
This method is called when doing a LOAD directly into a Table or Queue. Where ValidateRecord validates the incoming values, ValidateUpdateRecord validates the existing-record values. So, for example, if a database record is locked, and may not be updated, then this method allows you to override the update, and not write the new values to disk.
Return XF:Filtered from the method to override the update.
This method is only called for updates, not for inserts. Filtering for inserts remains in either ValidateRecord, or InsertFileRecord.
AddOldField If one of the fields in your Group, Flie, Queue or View has changed, and exported XML files exist that are still using the old field names, you can use this to tell XFiles to import the the value into the renamed field.
LoadAllOldFields This is called when Load is called. Embed your calls to AddOldField() here, after the parent call.
CleanFileName Called when a file is saved to ensure there are no invalid characters in the file name.

xFileXML Properties

Property Description See Also
AddArrayIndexToTag
(Long)
If set to true (the default) then XML tags for array values will be extended to include the array index. See Arrays for more. Arrays
AddFields
(Long)
The number of fields to add when inserting a record into the queue by calling the AddRecord method. AddRecord method
BinData
(&String)
The actual internal data store for loaded files. Memory allocation and disposal is handled by the class. When an XML file is loaded the data is loaded into this buffer and the size of the loaded data is stored in the BinDataLen property. Generally this property does not need to be manipulated directly unless the LoadBinData and SaveBinData methods are being used to write this directly to disk (which allows generic file reading and writing as well as customization of the data being read and written). LoadBinData method
SaveBinData method
BinDataLen property
BinDataLen
(Long)
The size of the BinData property (in bytes). This is set automatically when an XML file is loaded from disk. BinData Property
ColumnDisabled
(Byte, dim(XF:MaxFields))
This is an array map of the columns that allows a column to be enabled(0) or disabled(1). All columns are enabled by default, but you can disable individual columns by setting the byte in the map for that particular column. E.g. (to disable column 4):

ThisFileXML.ColumnDisabled[4] = 1

CopyFields
(Long)
The number of fields to copy between the two data structures when calling the Copy method. Allows the number of fields being used to be limited. Copy method
CRLF
(cString(10))
A string containing the line-ending to place after each tab. This makes the xml more readable, but at the cost of some extra characters in the xml file.  Defaults to <13,10>.  
DontAbbreviateEmptyTags
(Long)
By default empty tags are saved as the compact form - for example <abc/>. If however the unabbreviated form needs to be saved then set this property to true. When true tags will be saved in the form <abc></abc>.
Note that this setting applies after the DontSaveBlank settings above. In other words if the Blank property is true then this property has no effect.
 
DontAddLastFields
(Long)
Performs the same function as DontCopyLastFields, except for the AddRecord method - it limits the number of fields that are added when inserting a record into the queue. AddRecord method
DontCopyLastFields method
DontCleanFileName
(Long)
If set to 1 the CleanFileName method will not remove invalid characters from the file name, but simply return it as-in. This should not be used in normal circumstances.
DontCopyLastFields
(Long)
The same as the DontLoadLastFields property, except for copying from one queue to another using the Copy method. Copy method
DontLoadLastFields
(Long)
Performs the same function as the LoadFields property, however it allows the number of fields to be excluded to be specified from the last field in the queue, rather than specifying the number of fields to be included from the first field in the queue. LoadFields property
DontReplaceColons
(Long)
By default xFiles replaces colons(:) with periods (.) when writing prefixes to the file. Colons are not valid in the XML tags except for namespaces. Set the DontReplaceColons property to true to not replace colons in the XML file with full stops and zero to leave them as colons. This does not effect the operation of xFiles, however setting DontReplaceColons to true will result in invalid XML that is not viewable in XML viewers and parsers.
DontSaveBlanks
DontSaveBlankStrings
DontSaveBlankNumbers
DontSaveBlankGroups
(Long)
By default all fields in the structure are added into the XML as it is created. However XML also allows for the possibility of omitting blank fields from the output. These properties allow you control over what fields (if any) are excluded. The first property (DontSaveBlanks) is the equivalent of all the others being set.  
IgnoreGroupTags
(Long)
Saving: If this property is set then Groupings of fields in your Clarion structure is ignored. The structure is saved as if the structure contains no groups.

Loading: If this property is set, then xml fields are imported by name, regardless of their grouping (or lack of grouping) in the xml. In other words the import will pay no attention to grouping in the xml, or grouping in the Clarion structure. If the xml however does contain groups, and inside those groups are duplicated field names, then the import will be unable to distinguish the fields.

IndentBy The number of spaces (default = 2) to indent rows by to make the xml more readable. Set to 0 to minimize the size of the xml. Set to 0 to suppress indenting. The default value is 2.  
LoadFields
(Long)
This allows the number of fields to be loaded to be different from the number of fields in the queue, for example the first 5 fields might contain data, and the second 5 might be for temporary data, or the result calculations that aren't stored in the XML file. Setting LoadFields to 5 will load the first 5 fields of the queue from the XML file, even if there are ten fields in the queue or file.  Load method
SaveFields property
DontLoadLastFields property
LoadFromString
(Long)
When this property is set the Load() method loads the XML data in the xmlData string instead of loading it from disk. This property does not need to be set manually any longer as the Load() method now directly supports passing a string containing the XML to loading into the passed data structure. See Also
Load, Save, saveToString
Meta Allows custom meta headers to be added to the xml. This property is added to the top of the xml file, before the root boundary. For example;
xml.meta = '<?mso-application progid="Excel.Sheet"?>'
 
OmitXMLHeader
(Long)
Omits the first tag in the XML file. See CreateHeader and CreateFooter for more information on the header and footer tags. LoadFields property
_pFileBoundary
(pString(252))
This is the root boundary of the xml. The xml specification specifies that the xml document needs a boundary at the beginning and end of the xml.
_pFileBoundaryAttribute
String(XF:MaxAttributesSize)
Added to the file boundary tag when writing an XML file.  
_pRecordBoundary
(pString(252))
This is the boundary between each record in the queue/table/view of file being loaded. This allows the tag used as the record boundary to be specified.
ProgressControl If this property is set to the Use Equate of a progress control on the window, then the progress control will be updated during the Load or Save call. The progress bar is automatically unhidden on start, and hidden when the Load or Save is completed.
for example;
xml.ProgressControl = ?ProgressWhatever
xml.Load(....
 
Records The number of records added to the XML in the last save.  
RecordBoundaryAttribute Added to the record boundary tag when writing an XML file.  
RecordNumber
(Long)
As each record is written to the output duing a .SAVE, the RecordNumber is increased. This allows you to know the current record number (in the output) as the data is being written.
SaveFields Specify the number of fields to save.  
SaveRecords Default is 0, meaning no limit. If set then only this many records will be copied from the source structure to the XML.  This value is checked after the call to ValidateRecord so records excluded by ValidateRecord are not included in the output, and do not count against the SaveRecords limit.  
SkipRecords Default is 0, meaning no records are skipped. If set then this number of valid records will be skipped before records are copied from the source structure to the output. This value is checked after the call to ValidateRecord so records excluded by ValidateRecord are not included in the output, and do not count against the SkipRecords limit. Used with the SaveRecords property this property allows for "pages" of data to be exported.  
SaveToString
(Long)
When this property is set the Save() method saves the XML data in the xmlData string instead of writing it to disk. The Save() method can be passed a blank file name if this is set, as the file name parameter is not used. See Also
Save, loadFromString
SOAPEnvelope
(Long)
When this property is set the Save() method wraps a SOAP envelope around the XML data. See Also
Creating SOAP requests
SOAPEnvelopeBoundary, SOAPEnvelopeBoundaryAttribute, SOAPBodyBoundary
SOAPEnvelopeBoundary
(pString(252))
This property determines the text for the Soap Envelope tag, to be used if the XML is wrapped as a SOAP request. The default is <soap:Envelope>. Be aware that some SOAP servers may treat this tag in a case sensitive manner. See Also
Creating SOAP requests
SOAPEnvelope, SOAPEnvelopeBoundaryAttribute, SOAPBodyBoundary
SOAPEnvelopeBoundaryAttribute
(cString(XF:MaxAttributesSize))
This property lets you set the attribute (if any is required) of the Soap Envelope Boundary tag. See Also
Creating SOAP requests
SOAPEnvelope, SOAPEnvelopeBoundary, SOAPBodyBoundary
SOAPBodyBoundary
(pString(252))
This property determines the text for the Soap Body tag, to be used if the XML is wrapped as a SOAP request. The default is <soap:Body>. Be aware that some SOAP servers may treat this tag in a case sensitive manner. See Also
Creating SOAP requests
SOAPEnvelope, SOAPEnvelopeBoundary, SOAPEnvelopeBoundaryAttribute
StoreWholeRecord
(Long)
If this property is set to true, and a field in the structure exists, with the same name (or external name) as the record boundary, then the xml for the whole record will be stored "as is" in this field.
TagCase
(Long)
The field name tags are automatically detected for you by the xFiles class. In some XML situations the case of these tags may be important. This property gives you control over the case used. This property  defaults to XF:CaseAny. Other options are XF:CaseLower, XF:CaseUpper and XF:CaseAsIs. In order to have mixed case tags, the Name property for each field in the group/file/queue must be set. This property is only applied to field-level tags, not header or boundary tags.

The default is XF:CaseAny, which means that xml files being loaded are Case Insensitive. For a Save this implies that the case of the External Name will be used (if it exists) and the tag is Upper Case if there is no external name.
See Also
Creating SOAP requests
UpdateAsWhole If an incoming record updates an existing record, then assume the incoming record overwrites _all_ the fields of the existing record. Note this mode is not supported if the file has memo or blob fields.  
UpdateFileOnLoad If you don't want records to be updated when importing, then CLEAR this property (set by default).  
XmlData
(&String)
A string that is populated with the XML by the Save() method, if saveToString is set to 1. This allows XML to be "saved" into memory rather than written to disk directly. See Also
Properties: loadFromString, saveToString
Methods: Load, Save
XmlDataLen
(Long)
The length (in bytes) of the xmlData string property. The xmlData property is used for loading and saving XML to and from memory rather than from disk. See Also
Properties: loadFromString, saveToString
Methods: Load, Save
Xslt
(string(XF:TempHeaderStringSize))
The name of an XSLT Style sheet which will be referenced in the xml header.  

xFileLinkDLL Methods
The xFilesLinkDLL class provides runtime DLL loading. This allows you to load a DLL when your program is running and access functions provided by that DLL. The DLL itself is not linked into your application at compile to using a LIB file. This is useful when your application depends on an external DLL, or there is functionality that is only provided when a particular DLL is available, or for functions that are specific to certain version of Windows etc./td>
Construct Constructor that is called when the object is created.
Destruct DDestructor that is called when the object is destroyed.
LinkDLLFunction Loads a function from a DLL. If the DLL is not already loaded it loads the library.
Init Initialization method, does not perform any functionality and is provided for overriding the default behavior.
xFileLinkDLL Properties
dontDisplayMessages Replaces colons with full stops when writing prefixes to the file (on by default)
_DLLModules Specify the number of fields to load

xFileXML Class Methods

AddRecord

AddRecord (<any p_F1>,<any p_F2>,<any p_F3>, ...)

Description

Adds a record to the xFilesXML record queue. The method takes up to 20 ANY parameters, which allows the addition of records to a queue where the type of a field is not known.

Parameters

Parameter Description
any p_F1 Up to 20 optional parameters of the ANY type can be passed. Each parameter populates a field in the record that is to be added to the queue.

See Also:

Load, Save

AddText

AddText(String p_text)

Description

Add text into the outgoing XML stream when doing a Save.

Parameters

Parameter Description
p_text The text to add into the XML. This text is added in "as is". All encoding etc needs to be done on the text before inserting it in with this method.

See Also:

AddAtListStart, AddAtRecordStart, AddAtRecordEnd, AddAtListEnd

CCopy

Copy (*Group p_Group, byte p_Direction=0)
Copy (*Queue p_Queue, byte p_Direction=0)

Description

Copies the records from the internal xFileXML group/queue property to the passed group/queue, or from the passed group/queue to the xFiles group/queue. The direction parameter specifies whether the data is copied from the parameter to the xFilesXML property or from the property to the passed variable.

Parameters

Parameter Description
*Group p_Group or
*Queue p_Queue
The queue or group to copy to or from (depending on the value of the direction parameter).
byte p_Direction If p_Direction is 0 then the data is copied from the xFilesXML object to the passed group/queue.
If p_Direction is 1 then records are copied from the passed queue or group to the xFilesXML object.

See Also:

Load, Save

Init

Init (*Queue p_Queue, string p_FileName)
Init (*Group p_Group, string p_FileName)
Init (*File p_File, string p_FileName)
Init (*View p_View, string p_FileName)

Description

Initialises the xFileXML object to the a particular data structure and file, you can call this method at any point to change the data structure that is read from and written to, and the XML file that is used. If you are setting any of the class properties such as LoadFields and SaveFields then Init must be called after setting the class properties in order to use them.

Note: As well as passing a Queue, you can also pass a Group, File or View to the Init method to have xFiles read and write using a group, file, view or queue. (Views are Write-Only, not Read).

Note: You don't need to call Init unless you set advanced class properties like xFileXML.loadFields, xFileXML.saveFields and xFileXML.copyFields. If you set these properties to change which fields are saved then you need to use the first method above and call Init(). Otherwise you can call the Load and Save methods and pass them the queue/group/file/view and file name without calling Init().

Parameters

Parameter Description
*Queue p_Queue or
*Group p_Group or
*File p_file or
*View p_view
The label of the structure that the data is read from or written to.
string p_FileName The name of file to read from and write to.
Examples
Example
myQueue           queue
from                  string(80)
to                    string(80)
                  end
xmlFileName       string(260)
 CODE
 xmlFileName = 'myXmlFile.xml'
 thisFileXML.Init(myQueue, xmlFileName)

See Also:

Load, Save

Load

Load (*Queue p_Queue, string p_fileName, <string p_FileBoundary>,<string p_RecordBoundary>)
Load (*Group p_Group, string p_fileName, <string p_FileBoundary>,<string p_RecordBoundary>)
Load (*File p_File, string p_fileName, <string p_FileBoundary>,<string p_RecordBoundary>)
Load (*Queue p_Group, *String p_XMLString, Long p_XMLStringLength, <string p_FileBoundary>,<string p_RecordBoundary>)
Load (*Group p_Group, *String p_XMLString, Long p_XMLStringLength, <string p_FileBoundary>,<string p_RecordBoundary>)
Load (*File p_File, *String p_XMLString, Long p_XMLStringLength, <string p_FileBoundary>,<string p_RecordBoundary>)
Load ()

Description

Loads an XML file into the data structure. The first parameter is the Clarion data structure that will receive the parsed XML. Valid structures are a Queue, File or Group.

The second parameter is the name of the file on disk, or the name of the string which is holding the XML in memory. If the XML is in a string, then an additional parameter, the length of the string, is required at this point.

Following this are 2 optional parameters that allow you to specify the File Boundary, and Record Boundary tags.

The .Load method (no parameters) ultimately loads the XML into the specified structure. It is included here for completeness but it should not usually be called directly.

Parameters

Parameter Description
*Queue p_Queue or
*Group p_Group or
*File p_File
The queue, file or group that the xml data will be loaded into.
string p_FileName The name of file to read from.
*String p_XMLString The string variable containing the whole, valid, XML data.
Long p_XMLStringLength The length of the XML string.
<string p_FileBoundary> TThe file boundary tag in the XML file. Optional. Default is file, view, queue or group. If the p_FileBoundary parameter is set then the p_RecordBoundary parameter must be set as well.
<string p_RecordBoundary> The Record boundary tag in the XML file. Optional. Default is item (for File or Queue) or data (for Group). If the p_RecordBoundary parameter is set then the p_FileBoundary parameter must be set as well.
Examples
Example - Calling Load by passing the data structure and file name.
xml               xFileXml
myQueue           queue
from                  string(80)
to                    string(80)
                  end

xmlFileName       string(260)

  CODE
  xmlFileName = 'myXmlFile.xml'
  xml.start()
  xml.SetTagCase(sf:caseLower)
  xml.Load(myQueue, xmlFileName,'table','record')

See Also:

Save, Init, Load

Save

Save (*Queue p_Queue, string p_fileName, <string p_FileBoundary>,<string p_RecordBoundary>)
Save (*File p_File, string p_fileName, <string p_FileBoundary>,<string p_RecordBoundary>, <key pKey>)
Save (*File p_File, string p_fileName, <key pKey>)

Save (*View p_View, string p_fileName, <string p_FileBoundary>,<string p_RecordBoundary>)
Save (*Group p_Group, string p_fileName, <string p_FileBoundary>,<string p_RecordBoundary>)
Save (*Queue p_Queue, <string p_FileBoundary>,<string p_RecordBoundary>)
Save (*File p_File, <string p_FileBoundary>,<string p_RecordBoundary>, <key pKey>)
Save (*File p_File, <key pKey>)

Save (*View p_View, <string p_FileBoundary>,<string p_RecordBoundary>)
Save (*Group p_Group, <string p_FileBoundary>,<string p_RecordBoundary>)
Save ()

Description

Saves a Clarion data structure into an XML file, or string.

The first parameter is the Clarion data structure that holds the data to be exported. Valid structures are a Queue, File, View or Group.

The second parameter is the name of the file on disk. If the XML is to be stored in a string, in memory, then this parameter is ignored.

Following this are 2 optional parameters that allow you to specify the File Boundary, and Record Boundary tags.

The .Save method (no parameters) ultimately saves the XML into the file or string. It is included here for completeness but it should not usually be called directly.

Parameters:

Parameter Description
*Queue p_Queue or
*Group p_Group or
*File p_File or
*View p_View
The label of the queue, file, view or group that contains the data to be saved to the XML file or string.
string p_FileName The name of the XML file to store the data in. If this file does not exist it will be created. If this parameter is omitted completely then the XML will be written into a string. This string is accessible after the call to .Save in the object's XmlData property. The length of the string is in the XmlDataLen property.
<string p_FileBoundary> The file boundary tag in the XML file. Optional. Default is file, view, queue or group. If the p_FileBoundary parameter is set then the p_RecordBoundary parameter must be set as well.
<string p_RecordBoundary> The Record boundary tag in the XML file. Optional. Default is item (for File or Queue) or data (for Group). If the p_RecordBoundary parameter is set then the p_FileBoundary parameter must be set as well.
<key pKey> Allows you to specify the order (by selecting a key) when saving a table to xml. If the key is omitted, and a primary key exists, then the primary key is used./td>
Examples
Example  - Calling Save by passing the data structure and file name
xml     xFileXml
myQueue queue
from      string(80)
to        string(80)
        end 
xmlFileName string(260)
  CODE
  xmlFileName = 'myXmlFile.xml'
  xml.start()
  xml.Save(myQueue, xmlFileName)


See Also:

Load(), Init()

Start

Start ()

Description

Returns the object to its initial state ready for reuse.

xFileXML Advanced Methods

Generic File Loading and Saving

SaveBinData

SaveBinData (<string fileName>)

Description

Saves the current contents of the binData property to disk using the either the file name passed using the fileName parameter, or the file name set when the Init() method of the object was called. This method along with the LoadBinData() provides generic reading and writing of files to and from memory. This method does not parse the string in any way and will not load the data into a queue, group or file. Use the Load() method to load XML files into data structures. This method essentially exposes the generic file handling provided by the parent xFileBinary class, which provides file handling (reading and writing) along with on the fly compress/decompression etc..

When used in conjunction with the.saveToString property this can be used to customise the XML produced before writing it to disk by calling SaveBinData.

Note: To create a ZIP compressed file containing the data in the  .binData string, set the ZipUseZip property to 1 before calling the SaveBinData. This same technique can be use to ZIP compress and decompress XML files on-the-fly using the Load() and Save() methods.

Parameters

Parameter Description
string fileName An optional parameter that specifies the name of the file to save. This must be passed if the Init() method has not been called to set the file name.

Return Values

If the call fails it returns zero and the .Error and .ErrorStr properties the class are set with the error details. You can add code to display these errors if you need to.

Examples

Example - Saving data from the binData string to disk
code  if not xFileXml.binData &= null
     Dispose(xFileXml.binData)
  end
  xFileXml.binData &= new string(myXmlSize) 
  xFileXml.binDataLen = myXmlSize
  ! Add data to the string, such as custom XML 
  xFileXml.binData = Clip(string1) & Clip(string2)
  xFileXml.SaveBinData('myfileName.xml'
)

See Also

LoadBinData, Save, Load, binData, binDataLen, saveToString, loadFromString

LoadBinData

LoadBinData (<string fileName>)

Loads a string directly into the binData property of the class with doing any parsing or handling of the contents of the file. When this method is called the .binData property of the class is receives the contents of the file (memory allocation and disposable is handled by the class). In addition the .binDataLen property of the class is set to the size of the file loaded (in bytes).The .binData property is a string.

This method along with SaveBinData() expose generic file reading and writing using the xFileXML class. This can be useful when used with the .saveToString and .loadFromString properties. The data can be loaded and manipulated manually before the Load() and Save() methods are called to load (or save) the data to and from memory.

Note: To load a ZIP compressed file, set the _ZipUseZip property to 1 before calling the LoadBinData() method. This same technique can be use to ZIP compress and decompress XML files on-the-fly using the Load() and Save() methods.

Parameters

Parameter DDescription
string fileName An optional parameter that specifies the name of the file to load. This must be passed if the Init() method has not been called to set the file name.

Return Values

If the call fails it returns zero and the .Error and .ErrorStr properties the class are set with the error details. You can add code to display these errors if you need to.

Examples

Example  1 - Load data from the to disk to the xFileXML.binData string
code  xFileXml. LoadBinData('myfileName.xml')
  ! the .binData now contains the contents of the file
See Also

SaveBinData, Save,Load, binData, binDataLen, saveToString, loadFromString

XML file modification and settings

CreateHeader

CreateHeader ()

Sets the header for the XML file and adds it to the BinData property. This is called before data is added to the XML file. The string created is a standard XML header such as:
'<?xml version="1.0"?><13,10>'

Note: If the saveEncoding property is set then this string includes the encoding for the file. The binData property is set to the string created by this method and a carriage return, linefeed pair is added to the end of the header string (ASCII 13, 10) as indicated in the code snippet above. The binDataLen property is set to the length of the binData string property (the number of bytes in the header).

Parameters

None

Return Values

None

See Also

CreateFooter

CreateFooter

CreateFooter()

Creates the footer for the XML file. This is simply the closing tag for the file, such as the file boundary (see the ._pFileBoundary property). In general this method does not need to be overridden unless a custom header has been created or additional wrapper tags have been added.

Note: The footer is appended to the .binData string and the .binDataLen property incremented by the number of bytes added.

Parameters

None

Return Values

None

See Also

CreateHeader

DisableField

DisableField(structure, Name)

Allows you to disable (ie exclude) a field from a structure prior to calling the Save method.

Note: The DisableColumn property is set by this call. Clearing the property before subsequent calls to Save is recommended.

Parameters

Parameter Description
Structure The name of a Table, View, Queue or Group
Name The name of the field inside the group that should be omitted when doing a Save

Return Values

None

See Also

CreateHeader

LoadTweakSettings

LoadTweakSettings()

A purely virtual method that allows the programmer to override the settings before the Load() method actually loads and parses the XML file. Use this for setting the file or record boundaries or other settings that need to be customised to load a specific file.

Parameters

None

Return Values

None

See Also

SaveTweakSettings()

SaveTweakFieldSettings

SaveTweakFieldSettings()
A purely virtual method that allows the programmer to override the settings before the Save() method actually creates the XML file. Use this for setting the file or record boundaries or other settings that need to be customized before the XML is created and saved to disk or memory.

Parameters

None

 Return Values

None

See Also

LoadTweakSettings, SaveTweakSettings

SetAsAttribute

SetAsAttribute(String p_Fieldname,<String p_OfName>,Long p_instance=1)

Allows the programmer to set a field as being the attribute of the record, or of another field. Called from inside SaveTweakFieldSettings.

Parameters

Parameter DDescription
FieldName The tagname of the field to be set as an attribute.
OfName [optional] The name of the tag which this attribute is attached to. If omitted then the attribute is attached to the record identifier.
Instance [optional] Tag names can be duplicated in xml output. So if there are multiple tags with the same name, then use this parameter to differentiate between them.
OfInstance [optional] The instance number of the OfName to attach the attribute to. (Because it is also possible to have duplicate names at the tag level as well as the attribute level.)
Return Values

None

See Also

Saving Fields As Attributes
 

UseCharset

UseCharset(Long p_CharSet)

Sets the .SaveEncoding property based on a Clarion charset equate.

Parameters

Parameter Description
long p_Charset A standard Clarion Charset equate

Return Values

None

Record handling and management

GetValue

GetValue (string p_Field, long p_dim = 0) Retrieves the value of a field when passed the name of the field. For multi dimensional fields (Clarion 6 only) the second parameter allows the dimension to be retrieved to be specified. Note that the Init()() method must have been called to specify the data structure that the xFilesXML object is manipulating before this method is called.

Note: This method is thread safe and wraps access to queues in a critical section. All the methods in xFiles are built to ensure thread safe access to data (including global, unthreaded data)

Parameters

Parameter DDescription
string p_Field A string that contains the name of the field to be fetched.
long p_dim An optional parameter that specifies which dimension to fetch for multi dimensioned fields. Supported in Clarion 6 only
Return Values

The value of the field is returned as an ANY variable. If the field is not found an empty string is returned.

See Also

SetValue()

SetValue

( string p_Field, string p_Value, long p_dim = 0)

Sets the values of a specified field. Works for queues, groups and files. Note that the Init method must have been called to specify the data structure that the xFilesXML object is manipulating before this method is called.

Note: This method is thread safe and wraps access to queues in a critical section. All the methods in xFiles are built to ensure thread safe access to data (including global, unthreaded data).

Parameters

Parameter Description
string p_Field A string that contains the name of the field to be to be set.
string p_Value A string that contains the value to set the field to
long p_dim An optional parameter that specifies which dimension to set for multi dimensioned fields. Supported in Clarion 6 only
Return Values

Returns zero for success and -1 if the field is note found.

See Also

GetValue

InsertFileRecord

InsertFileRecord ()

This method adds the contents of the current file buffer as a new record in the file that is associated with xFiles. This method is not generally used directly.

Note: The Load(), Save() or Init() method must be called before this method is used in order for the xFilesXML object to be associated with a particular data structure.

Note: This method only supports files and does not update any relationships.

Parameters

None

Return Values

None

See Also

UpdateFileRecord

UpdateFileRecord

UpdateFileRecord ()

This method updates the current file record with the contents of the current file buffer. This method is not generally used directly.

Note: The Load(), Save() or Init() method must be called before this method is used in order for the xFilesXML object to be associated with a particular data structure.

Note: This method only supports files and does not update any relationships.

Parameters

None

Return Values

None

See Also

InsertFileRecord()

RecordCount

xFileXML.RecordCount ()

Returns the number of records in the File or Queue that is associated with the xFiles object.

Note: The Load(), Save() or Init() method must be called before this method is used in order for the xFilesXML object to be associated with a particular data structure.

Parameters

None

Return Values

The number of records in the Queue or File that is currently associated with the xFilesXML object.

FreeFileData

xFileXML.FreeFileData ()

Deletes all records in the File associated with this object. All records are removed, however no relationships are updated. This method is not generally called directly. This method only applies if the xFilesXML object is being used with a File.

Note: The Load(), Save() or Init() method must be called before this method is used in order for the xFilesXML object to be associated with a particular data structure.

Parameters

None

Return Values

The number of records in the Queue or File that is currently associated with the xFilesXML object.

FreeGroupData

FreeGroupData ()

Clears all fields in the group that is associated with the xFileXML object. This method only applies if the xFilesXML object is being used with a Group.

Note: The Load(), Save() or Init() method must be called before this method is used in order for the xFilesXML object to be associated with a particular data structure.

Parameters

None

Return Values

None

FreeQueueData

FreeQueueData ()

Deletes all records in the Queue associated with this object. This method only applies if the xFilesXML object is being used with a Queue.

Note: The Load(), Save() or Init() method must be called before this method is used in order for the xFilesXML object to be associated with a particular data structure.

Parameters

None

Return Values

None

AddOldField

AddOldField  (string p_OldFieldName, string p_InFilePos), long

If one of the fields in your Group, Flie, Queue or View has changed, and exported XML files exist that are still using the old field names, you can use this to tell XFiles to import the the value into the renamed field. You should embed your calls to this method in the LoadAllOldFields procedure, after the parent call, as this gets call when Load is called.

Parameters

Parameter Description
string p_OldFieldName A string that contains the odl name of the field
string p_InFilePos The position in the file record at which this field resides.

A typical call to this procedure would look something like this:

MyXML.AddOldField('OldFieldName', where(OldFieldFile.Record, OlfFieldFile.OldFieldName))


Return Values

Returns 0 if adding this old fieldname was successful. Will only fail if there is no more space for extra fieldnames. The maximum is XF:MaxFields.

See Also

LoadAllOldFields()

LoadAllOldFields

LoadAllOldFields ()

This is called when Load is called. Embed your calls to AddOldField() here, after the parent call. Be sure to embed after the parent call as the parent call clears all the old fieldnames (used for the previous load).

Parameters

None

Return Values

None

See Also

AddOldField

CleanFileName

CleanFileName (string p_Filename, long p_flag=0)

This is called when Load is called to ensure that the file name has no invalid characters. ASCII control characters with values below 21 are replaced by spaces and characters that are illegal in Windows file names (* / \ ? < > :) are removed. Use this method to remove all non-legal characters from a string so that it can be used in a file name. Note that this method removes backslashes ( \ ) so it should be used on just the file name, but NOT the path.

To use it on a Path set the flag to XF:ALLOWBACKSLASH this flag also implicitly allows a : in character 2 (for example 'c:\temp.xml'). Illegal characters are replaced with a space.

To disable this behavior set the xFileXML.dontCleanFileName property to true.

Parameters

Parameter Description
p_FileName The name of the file to be saved, excludes the path to the file (unless the p_flag parameter is set to XF:ALLOWBACKSLASH to allow backslashes and handle paths). For example 'settings.xml'.
p_flag This is a flag to override the default behavior and allow back slashes. If this contains XF:ALLOWBACKSLASH then backslashes are allowed. Use this to allow paths to be checked.

Return Values

Returns the valid file name, after illegal characters have been removed.

xFileLinkDLL Methods - Runtime DLL Loading

LinkDLLFunction (string p_DLLName, string p_FunctionName, *long p_fpFunction)

This method loads a function from a DLL when passed the name of the DLL and the name of the function to locate. If the DLL has not already been loaded it loads the DLL. The address of the function is located and stored in the passed p_fpFunction parameter .

Parameters

Parameter Description
string p_DLLName A string containing the name of the DLL that contains the function to load. If this library has not already been loaded then the method will load it before locating the required function address.
string p_FunctionName The name of the function to locate in the library.
*long p_fpFunction A long that will receive the address of the function in the library.

Return Values

Returns zero for success, or an error code if it fails.

Examples

See the "Runtime DLL Loading QuickStart Guide" for a full example and how to declare and use the addresses returned.

Example  1 - Load a function called 'unzOpen' from a DLL called 'zip.dll'/th>
code  ret = xLinkDLL.LinkDLLFunction ('zip.dll', 'unzOpen', fp_unzOpen)

See Also

DontDisplayMessages

xFileLinkDLLProperty Reference

These are some of the properties of the xFileLinkDLL class that you might find useful to set directly.

dontDisplayMessages long This option ensures that an error message is not displayed if a DLL fails to load or a function cannot be located. Set this option to 1 to not displayed error messages. You can use the .Error and .ErrorStr properties the class are set with the error details to display these errors if you need to.

Deriving a Class and Using in Your Apps

Xfiles provides provides a number of embed points where you can embed your own code into a generated xFiles object. This approach works well if you have a single app, and the code only needs to go into one place. However if you are wanting to override the default behaviour across multiple procedures, then the best approach is to derive one of the the xFiles classed adding your own code. Then that derived class can be applied to the App. This section shows how to do both of those things.

Deriving an xFiles Class

Deriving a class consists of creating two new text files. An INC and a CLW file. You can call these anything you like (typically they have the same name, just a different extension.) These files should be in the same folder as the xFiles.INC and xFiles.CLW files. Usually this is in \clarion\accessory\libsrc\win. In the examples below these files are called CustomxFiles.INC and CustomxFiles.CLW.

INC File

The INC file contains very little boilerplate code. Just a simple INCLUDE statement, and the derived class declaration.For example;

  include('xFiles.Inc'),once

CustomxFilesTree Class(xFilesTree), Type, MODULE('CustomxFiles.Clw'), |
                    LINK('CustomxFiles.Clw',xFilesLinkMode), DLL(xFilesDllMode)
Parse              Procedure(*View pView),DERIVED
                 End


Note the MODULE and LINK point to your custom CLW file. You can use the xFilesLinkMode and xFilesDLLMode as is.

In this file, the INCLUDE at the top points to the parent-class Include file, in this case xFiles.Inc.

CLW File

The CLW file has a little more boilerplate, then the custom methods are defined. You only need to derive the methods you want to change, not the other methods.

   member()
   map
   end

include('CustomxFiles.Inc'),once

CustomxFilesTree.Parse     Procedure(*View pView)
  code
  message('yo yo ... no')
  parent.Parse(pView)


Note that in this file, the INCLUDE points to your custom include file.

Using the Derived Class in an App

Using the derived class in an application is a simple process;
  1. Go to the xFiles Global Extension, to the Classes Tab. In the Additional INC Files list add your custom INC file. In this example CustomxFiles.INC.
  2. Click the Refresh Classes button. Then click the Classes button. You should see your new derived class listed there.
  3. You can now use your class in hand-code, declared simply as
    somexml  CustomxFilesTree
    or in the local extension template simply use CustomxFilesTree (or whatever your class name is) for the Class Name.

Support

Your questions, comments and suggestions are welcome. See our web page (www.capesoft.com) for new versions. You can also contact us in one of the following ways:
CapeSoft Support
Email
Telephone +27 87 828 0123

Installation

To download the latest installation please visit the CapeSoft Downloads page.
To install extract the Installation Program from the SAF file using the free CapeSoft Safe Reader (download for free from https://www.capesoft.com/utilities/Safe/safereader.htm). Run the Installation Program for your version of Clarion.

Distribution

xFiles ships as source so it is compiled into the application and there are no additional files needed for distribution. There are no runtime royalties for using xFiles. Also see the License & Copyright section for more information.

License & Copyright

This template is copyright © 2003-2024 by CapeSoft Software. None of the included files may be distributed. Your programs which use xFiles can be distributed without any xFiles royalties.

Each developer needs his own license to use xFiles. (Need to buy more licenses?)

This product is provided as-is. CapeSoft Software and CapeSoft Electronics (collectively trading as CapeSoft), their employees and dealers explicitly accept no liability for any loss or damages which may occur from using this package. Use of this package constitutes agreement with this license. This package is used entirely at your own risk.

Use of this product implies your acceptance of this, along with the recognition of the copyright stated above. In no way will CapeSoft , their employees or affiliates be liable in any way for any damages or business losses you may incur as a direct or indirect result of using this product.

For the full EULA see https://capesoft.com/eula.html

Version History

 Download latest version here

Version 4.31 - 8 April 2024   Version 4.30 - 27 March 2024   Version 4.29 - 15 March 2024   Version 4.28 - 26 February 2024   Version 4.27 - 16 January 2024  
Version 4.26 - 8 November 2023   Version 4.25 - 25 October 2023   Version 4.24 - 27 July 2023   Version 4.23 - 23 May 2023   Version 4.22 - 5 April 2023   Version 4.21 - 30 March 2023  
Version 4.20 - 1 March 2023   Version 4.19 - 20 February 2023   Version 4.18 - 6 February 2023   Version 4.17 - 27 January 2023  Version 4.16 - 26 January  2023  Version 4.15 - 20 January  2023 Version 4.14 - 13 January  2023
Version 4.13 - 9 January  2023 Version 4.12 - 23 November 2022 Version 4.11 - 23 November 2022
Version 4.10 - 18 November 2022
Version 4.09 - 11 November 2022
Version 4.08 - 8 November 2022 Version 4.07 - 27 October 2022 Version 4.06 - 20 October 2022
Version 4.05 - 3 October 2022 Version 4.04 - 17 September 2022 Version 4.03 - 6 September 2022 Version 4.02 - 24 August 2022 Version 4.01 - 22 August 2022 Version 4.00 - 22 August 2022 Version 3.25 - 17 August 2021 Version 3.24 - 24 May 2021 Version 3.23 (29 April 2021) Version 3.22 (22 April 2021) Version 3.21 (26 November 2020) Version 3.20 (16 September 2020) Version 3.19 (2 September 2020) Version 3.18 (18 August 2020) Version 3.17 (23 March 2020) Version 3.16 (13 January 2020) Version 3.15 (9 January 2020) Version 3.14 (4 December 2019) Version 3.13 (21 August 2019) Version 3.12 (12 July 2019) Version 3.11 (18 February 2019) Version 3.10 (5 December 2018) Version 3.08 / 3.09 (13 September 2018 ) Version 3.07 (11 August 2018 ) Version 3.06 (7 August 2018 ) Version 3.05 (26 July 2018 ) Version 3.04 (26 July 2018 ) Version 3.03 (4 July 2018 ) Version 3.02 (19 June 2018 ) Version 3.01 (10 April 2018 ) Version 3.00 (6 March 2018 ) Version 2.99 (8 February 2018 ) Version 2.98 (13 Dec 2017 )
Version 2.97 (8 Dec 2017 )
Version 2.96 (7 Dec 2017 ) Version 2.95 (5 Dec 2017 ) Version 2.94 (26 Oct 2017 ) Version 2.93 (30 Aug 2017 ) Version 2.92 (7 Aug 2017 ) Version 2.91 (31 July 2017 ) Version 2.90 (13 June 2017 ) Version 2.89 (3 May 2017 ) Version 2.88 (31 January 2017 )
Version 2.86 (6 January 2017 ) Version 2.85 (12 December 2016 )
Version 2.84 (1 November 2016 )
Version 2.83 (31 August 2016 ) Version 2.82 (5 August 2016 ) Version 2.81 (12 May 2016 ) Version 2.80 (18 April 2016 ) Version 2.79 (17 March 2016 ) Version 2.78 (17 February 2016 ) Version 2.77 (17 February 2016 ) Version 2.76 (5 February 2016 ) Version 2.75 (4 February 2016 ) Version 2.74 (24 November 2015) Version 2.73 (10 November 2015) Version 2.72 (9 November 2015) Version 2.71 (29 October 2015) Version 2.70 (3 September 2015) Version 2.69 (22 July 2015) Version 2.68 (15 July 2015) Version 2.67 (8 July 2015) Version 2.66 (6 May 2015) Version 2.65 (30 March 2015) Version 2.64 (27 March 2015) Version 2.63 (26 March 2015) Version 2.62 (24 March 2015) Version 2.61 (25 February 2015) Version 2.60 (30 December 2014) Version 2.59 (1 December 2014) Version 2.58 (27 November 2014) Version 2.57 (25 November 2014) Version 2.56 (17 November 2014) Version 2.55 (11 July 2014) Version 2.54 (11 July 2014) Version 2.53 (10 June 2014) Version 2.52 (30 March 2014) Version 2.51 (20 March 2014) Version 2.50 (5 March 2014)
Version 2.43 (17 February 2014)
Version 2.42 (22 January 2014) Version 2.41 (21 December 2013) Version 2.40 (8 July 2013) Version 2.39 (14 May 2013) Version 2.38 (14 March 2013) Version 2.37 (11 January 2013) Version 2.36 (24 November 2012) Version 2.35 (18 September 2012) Version 2.34 (27 August 2012) Version 2.33 (26 June 2012)
Version 2.32 (19 June 2012)
Version 2.31 (5 April 2012) Version 2.30 (10 February 2012) Version 2.29 (10 February 2012) Version 2.28 (24 January 2012) Version 2.27 (11 October 2011) Version 2.26 (6 Sept 2011)  Version 2.25 (20 July 2011) Version 2.24 (28 June 2011) Version 2.23 (4 May 2011) Version 2.22 (24 March 2011) Version 2.21 (24 March 2011) Version 2.20 Version 2.19 Version 2.18 Version 2.17 (24 January 2011) Version 2.16 (19 January 2011) Version 2.15 (14 January 2011) Version 2.14 (1 December 2010) Version 2.13 (27 November 2010) Version 2.12 (15 November 2010) Version 2.11 (26 September 2010) Version 2.10 (24 August 2010) Version 2.09 (4 August 2010) Version 2.08 (23 July 2010) Version 2.07 (12 July 2010) Version 2.06 (18 May 2010) Version 2.05 (2 April 2010) Version 2.04 (March 2010) Version 2.03 (1 March 2010) Version 2.02 (17 February 2010) Version 2.00 (5 January 2010) Version 1.99 (11 December 2009) Version 1.98 Version 1.97 Version 1.96 Version 1.95 Version 1.94 (20 August 2009) Version 1.93 (13 August 2009) Version 1.92 (28 July 2009) Version 1.91 (21 July 2009) Version 1.90 (15 July 2009) Version 1.89 (13 July 2009) Version 1.88 (18 May 2009) Version 1.87 (6 May 2009) Version 1.86 (17 April 2009) Version 1.85 (4 April 2009) Version 1.84 (23 March 2009) Version 1.83 (12 March 2009) Version 1.82 (10 March 2009) Version 1.81 (26 February 2009) Version 1.80 (19 February 2009) Version 1.78 Beta (10 November 2008) Version 1.77 Beta (23 September 2008) Version 1.76 Beta (12 September 2008) Version 1.75 Beta (10 July 2008) Version 1.74 Beta (19 June 2008) Version 1.73 Beta (16 June 2008) Version 1.72 Beta (12 June 2008) Version 1.71 Beta (11 June 2008) Version 1.70 Beta (19 May 2008) Version 1.69 Beta (13 May 2008) Version 1.68 Beta (2 April 2008) Version 1.67 Beta (14 March 2008) Version 1.66 Beta ( 14 March 2008) Version 1.65 Beta (29 Feb 2008) Version 1.64 Beta (20 Feb 2008) Version 1.63 Beta (15 Feb 2008) Version 1.62 Beta (5 November 2007) Version 1.61 Beta (26 October 2007) Version 1.60  Beta (13 September 2007) Version 1.52 Beta (7 September 2007) Version 1.51 Beta (31 August 2007) Version 1.50 Beta (25 July 2007) Version 1.40 Beta (07 July 2007) Version 1.30 Beta (29 June 2007) Version 1.23 Beta (1 May 2007)
Version 1.22 Beta (15 February 2007)
Version 1.20 Beta (06 February 2007)
Version 1.10 Beta (3 August 2006)
Version 1.09 Beta (19 June 2006)
Version 1.08 Beta (19 June 2006)
Version 1.07 Beta (6 June 2006)
Version 1.05 Beta (27 February 2006)
Version 1.04 Beta (10 November 2005)
Version 1.03 Beta (8 November 2005)
Version 1.02 Beta (13 September 2005)
Version 1.01 Beta (08 September 2005)
Version 1.00 Beta (31 August 2005)