This chapter describes how XML files can be read into user-defined structures within the Fire language.
XML is becoming more and more the de facto when tranferring data between applications. Fire has an xmlread command which parses an XML file and loads its data into a Fire structure.
As with all xml data transfer the recipient of an xml file, in this case a Fire application, is expected to know the schema of the data. Therefore a Fire structure (or class) must be created before the data is read. This structure will contain members corresponding to the data tags in the xml file. Consider the following trivial xml file:
<?xml version="1.0" encoding="ISO-8859-1"?>
<poi> <name>War Memorial</name> <id>6512</id> <location>(25466,71625)</location> <date>18-Dec-1919</date> </poi>
A Fire structure to receive this data might be as follows:
atable mytab
structure ~mytab.poi_t {
string name
string date
string id
string location
}
and the Fire code to create an object of this type (here called warmem) and load the xml data (from a file called warmem.xml) would be:
# Create an instance of the structure
~mytab.poit_t warmem
# Read the xml file contents into it
xmlread warmem.xml,warmem
This is a very simple case, where all the xml tag data is assumed to be textual data. However, we can vary the structure member types to enable type-coercion. This is often the requirement, enabling the data to be used more intelligently in subsequent Fire commands. In the case above we could have declared the structure as follows:
atable mytab
structure ~mytab.poi_t {
string name
time date
numeric id
point location
}
The order of elements within a Fire structure does not have to follow the order of tags within the xml file. Data assocation is done by name, not by position.
As expected, all xml comment tags i.e. <!-- ... --> are ignored during parsing.
In addition DTD and XSL definitions are currently ignored. In short, only data tags are parsed.
XML tags and attributes are treated in the same way when parsed. The following 2 xml files would be interpreted the same way:
<?xml version="1.0" encoding="ISO-8859-1"?>
<poi> <name>War Memorial</name> <id>6512</id> <location>(25466,71625)</location> <date>18-Dec-1919</date> </poi>
<?xml version="1.0" encoding="ISO-8859-1"?>
<poi id="6512" date="18-Dec-1919"> <name>War Memorial</name> <location>(25466,71625)</location> </poi>
Tag and attribute names will map directly to names in the receiving Fire object, i.e. an xml tag or attribute named id will get loaded into the member id of the Fire object. Case is ignored.
Where there is a conflict of name with possible reserved Fire member names, there are 2 ways of getting round this:
Prefix the Fire member name with m_, e.g. m_id, or
Redirect the load via an xmltag attribute on the receiving stucture member.
To illustrate, using the example in the previous section, some xml tag values are here directed into structure members of different names:
structure ~mytab.poi_t {
string name
time m_date
numeric id
point pt
}
~mytab.poi_t.pt.xmltag = 'location'
On a subsequent xmlread command, the tag or attribute value date will be loaded into the structure member m_date, and the tag or attribute value location will be loaded into the structure member pt.
If there is no Fire object member for an xml tag or attribute, the xml tag data is discarded. Similarly if there is no xml data for a Fire object member, the Fire object member retains its current value.
Any XML namespace prefix is discarded during name matching.
You may have noticed that neither the Fire structure name poi_t nor the name of the object being loaded warmem matches the outer XML tag poi. This is because by default there is no matching done on this name, so in effect any xml file can be loaded into any Fire type. Of course if no sub-tag names match Fire structure names, nothing will get loaded.
If a specific outer tag name is required, e.g. as an extra integrity check, a -tag switch can be added to the xmlread command, e.g.
xmlread warmem.xml,warmem,-tag='poi'
The result of this will be that if the xml data outer tag is not <poi> .... </poi>, then the xmlread command will fail.
Sometimes, there is a requirement for testing the validity of an xml file before reading its contents. The xmltest function is available for this purpose and performs 2 duties:
it determines whether the file is a bona-fide xml file and will pass the parsing checks,
it returns the value of the outermost tag so you can check whether its data is the type you expect.
Often xml data will contain tag multiple values , e.g.
<band>
<name>Led Zeppelin</name>
<genre>Heavy Metal</genre>
<member>John Bonham<member>
<member>John Paul Jones<member>
<member>Jimmy Page<member>
<member>Robert Plant<member>
</band>
There is one value for the name tag, but 4 values for the member tag. The following Fire structure would typically be used:
structure ~mytab.rockband_t {
string name
string genre
string member[]
}
As you can see, a placeholder array member has been declared of unspecified length. During the load process this length will get extended appropriately (in the case of Led Zeppelin to 4, although it often sounded like more). If no member tags are found, this array will remain with a length 0 after the load.
XML data can get very verbose and convoluted when supplying data for complex schemas. To accommodate such schemas it is usually necessary for Fire structures within structures to be defined.
You will notice that many Fire sub-structures are declared as arrays of unspecified length. This method is used to detect whether the elements have been included in the xml data.
Fire structures do not have a structure value, although their members do. Parent xml tags can have a text value so a tvalue member can be added to a Fire structure to access this parent value. This value can be coerced from a text value into another Fire data type just like the other members. Consider the following snippet of XML:
<employee dept="Accounts" age="32">Joe Kinnear</employee>
This could be read into an object of the following Fire structure:
structure employee_t {
string tvalue
string dept
numeric age
}
where the tvalue member will be given the value Joe Kinnear.
The tvalue member could be aliased if a different member name was required, e.g.
structure employee_t {
string full_name
string dept
numeric age
}
employee_t.full_name.xmltag = 'tvalue'
As we have discussed in an earlier section, even though xml data is primarily textual, by declaring Fire types other than type string, data values can be coerced. The allowed destination Fire types are as follows: numeric, point, time and blob, as well as the default type string.
Before looking at these in details we should consider the xml string values prior to coercion. Text values for attributes, e.g. <name att="value"> pose no problems because the values can be quoted and, apart from the double quote which must be escaped (via \"), all characters can be included raw in the value.
However, tag values, e.g. <att>value</att> usually contain what is known as parsed character data, which means white space is compressed and some characters (< > ' " &) have a special meaning. To get round this, either these characters are name-escaped (via the sequences < > ' " or &) or the whole data can be enclosed by the user-friendly sequence <![CDATA[ and ]]>, in which case all characters are treated as raw data and no character escaping is necessary.
As an example, consider 2 ways in which the string &<"hello there">& might be supplied as a tag value in an XML file:
<att>&<"hello there">&</att>
<att><![CDATA[&<"hello there">&]]></att>
In both cases, the input is pretty unsightly, but ours not to reason why. That's XML for you.
The coercion of string values into other types behaves generally as one would expected.
Integer, real and floating-point constants and expressions are all permitted, e.g.
<id>45</id> <total>98.72</total> <dvalue>4.5e-17</dvalue> <calc>4 + sqrt(17)</calc>
2-D or 3-D point values are permitted, with comma-separated x,y or z ordinates, within parentheses or without, e.g.
<pt>(10,20)</pt> <pt3d>55,17.3,912</pt3d>
The usal time/date Fire date and time constants are permitted, e.g.
<last_accessed>9/31/1989, 15:35</last_accessed> <birthday>14-Jan-1982</birthday>
Blobs are useful when multi-line string values are supplied. Once a value has been coerced into a blob, the blob can be treated as a file. Consider an example xml file which supplies some fire code to be executed, e.g.
<mydata>
<width>400</width>
<height>500</height>
<code><![CDATA[
args w=numeric, h=numeric
# Draw a window of supplied dimensions
window gw = wgraphic -dim=<w,h>
box gw,-draw
]]></code>
</mydata>
and the Fire code to define a fire structure, read the data and execute fire code within it:
atable test
# Define the Fire structure (class)
structure ~test.thing_t {
numeric width
numeric height
blob code
}
# Read the xml file into an instance of one of these ~test.thing_t obj xmlread mydata.xml,obj
# Execute the blob as though it were a macro exec obj.code(obj.width,obj.height)