Introduction and Overview
eXML is a RISC OS module which adds support for the XML file format to applications written in any language, including BBC BASIC. Existing ports of XML libraries have generally been C sources written by others, optimised for the C language, and not straightforward to call from other languages; some have also been withdrawn by their authors. eXML exists to give RISC OS software a single, dependable set of SWI calls for creating and parsing XML documents, without requiring a C compiler or a callback-based programming style.
Only a subset of the full XML specification is implemented. The intention is to extend coverage over time, but this is not guaranteed, and several calls described in this chapter are only partially implemented or not yet implemented at all; each such call says so explicitly.
Approach
Most existing XML libraries rely heavily on callbacks and handlers: the main program registers functions that the parser calls when it recognises something of interest. This is a natural style in C or C++, but it does not suit simpler languages such as BBC BASIC.
eXML instead follows the same pattern as the RISC OS window manager. The
application calls SWI eXML_Parse with the
appropriate parameters, and the call exits with a return code describing
what was found. Once the application has dealt with that return code, it
calls SWI eXML_Parse again, and repeats this
until it receives the Finished code, at which point it
resumes the normal flow of the program.
When handling long XML files, it may be desirable to call
Wimp_Poll frequently from within the parsing loop.
Take care when doing this: another running task may not be aware that your
program is still in the middle of parsing a document.
Encodings
Several character encodings can be used for the data in a document. The eXML calls refer to these encodings by a fixed number:
| Number | Encoding |
|---|---|
| 0 | UTF-8 |
| 1 | UTF-16 |
| 2 | ISO-8859-1 (Latin-1) |
| 3 | US-ASCII |
| 4 | MacRoman |
| 5 | Big5 |
Not every listed encoding is necessarily implemented in the current release
of the module: selecting an unsupported encoding produces the
UnknownEncoding result code from
SWI eXML_Parse. Until wider encoding support is
confirmed, use the standard Latin-1 encoding (2) for documents. UTF-8, in
particular, though ASCII-compatible and defined in Unicode, ISO 10646-1 and
RFC 2279, is not currently supported.
Reserved characters are escaped transparently on both read and write, so calling code does not need to handle this itself:
| Character | Escaped form |
|---|---|
| < (less than) | < |
| > (greater than) | > |
| & (ampersand) | & |
| ' (apostrophe) | ' |
| " (quote) | " |
Workspace and the element stack
The workspace used by the eXML module must be supplied by the calling application, rather than being owned by the module itself. This allows the module to be replaced by a newer version at any time without disturbing running applications, and allows several programs (or several independent parts of one program) to run concurrently, each with its own workspace and its own preferences. For example, a C or Assembler program expects strings to be terminated with a 0x00 byte, whereas BBC BASIC expects a 0x0D (Carriage Return) terminator; because each caller supplies its own workspace, each can select the terminator it needs without affecting any other caller.
Within the supplied workspace, eXML keeps a stack of currently open elements, fixed at 1024 bytes. Each element that is opened is pushed onto this stack as a null-terminated string, and the module maintains pointers to the current element. This lets the module track the order in which elements are opened and closed, report an error if it finds an inconsistency (for example, elements closed out of order), and, when writing, close any elements you have left open without you having to name them individually.
The module name 'eXML'
The module is called eXML, pronounced "ex-em-el", in the same way
as the abbreviation XML. The extra leading e exists
because SWI names cannot begin with the letter X: that
letter is reserved as a prefix which suppresses a SWI's normal error
handling. To trap errors from an eXML call yourself, prefix its name with
X in the usual way, for example
SYS "XeXML_StartElement", ....
Library
A BBC BASIC library is supplied in the !eXML
application folder. Place this folder somewhere the Filer sees at start-up,
such as inside !Boot.Resources, so that programs using
the library can find it.
Technical Details
eXML offers a set of SWI calls that any application can use to create and parse XML files. Because the module can be dynamically replaced, and each caller supplies its own workspace, each application (or each routine within an application) can hold its own set of preferences to be honoured by eXML independently of any other caller.
Before eXML can be used, the application must reserve a block of memory of suitable size for the workspace. The application requests the required size by calling SWI eXML_InitBuffer with a null pointer, then calls it again with a pointer to the newly claimed memory to initialise it to sensible defaults. Preferences are then set with SWI eXML_SetOption; for example, textual strings returned by the module are terminated with a 0x00 byte by default, which suits C or Assembler, but BASIC needs a 0x0D (Carriage Return) terminator instead, and SWI eXML_SetOption is used to change this.
The available calls fall into four groups, described in the four SWI Calls sections below: calls to initialise the XML workspace, calls to read (parse) an XML file, calls to write an XML file, and universal calls that are not specifically tied to XML parsing or writing.
Data structures
Attribute array
When SWI eXML_Parse reports a StartElement or Declaration, or when supplying attributes to SWI eXML_Open, SWI eXML_StartElement, or SWI eXML_Declaration, attributes are held as an array of pairs of pointers: a pointer to the attribute's name, followed by a pointer to its value. The array is terminated by a null pointer. For example, the element
<frame id="1" name="time and date">
produces the following attribute array:
| Offset | Size | Contents |
|---|---|---|
| 0 | 4 | pointer to "id" |
| 4 | 4 | pointer to "1" |
| 8 | 4 | pointer to "name" |
| 12 | 4 | pointer to "time and date" |
| 16 | 4 | Reserved, must be zero |
A pointer to an attribute array is 0 (rather than a valid pointer to an empty array) whenever no attributes were found, or none are to be supplied.
Context array and context string
On exit from SWI eXML_Parse, R3 normally points
to a zero-terminated array of word pointers, one per currently open element,
in the order the elements were opened - the path from the document root
down to (but not including) the element currently being processed. If, for
example, an element is opened while the parser is at
document/chapter/page, the
context array
on exit looks like this:
| Offset | Size | Contents |
|---|---|---|
| 0 | 4 | pointer to "document" |
| 4 | 4 | pointer to "chapter" |
| 8 | 4 | pointer to "page" |
| 12 | 4 | Reserved, must be zero |
If bit 0 of the flags word is set on entry to
SWI eXML_Parse, R3 instead points to a single
context string using the current element separator (see
Set the character used to separate element names in a context string),
for example /document/chapter/page, rather than to an
array of pointers. The name of the element currently being opened or closed
is not included in either form of context, because at that point it is not
yet (or is no longer) part of the context; append it yourself if you need
the full path, eg.
context$ = context$ + "/" + $element%.
SWI eXML_Context produces the string form of the
context on demand, at any point, without needing bit 0 of the flags word to
have been set.
Colour data block
SWI eXML_ColourOp reads and writes colour values through a fixed-size, fixed-format data block held in memory supplied by the caller - either a short-lived block used purely for one conversion, or part of a larger structure holding several colours or other parameters. Always request the required size first (see Initialise a colour data block, or find its required size), since it may grow in a future version of the module.
| Offset | Size | Field | Contents |
|---|---|---|---|
| 0 | 4 | Flags | Bit flags; not used at present |
| 4 | 4 | Model | The original colour model used (RGB, CMYK, HSV, YUV); see below |
| 8 | 4 | Format | The original format (syntax) used to define the colour; see below |
| 12 | 4 | Value | The colour, always as a RISC OS colour word (see below) |
| 16 | 4 | Original | The colour in its original colour model |
| 20 | 32 | Name | Name of the colour, if it was a named colour, including terminator |
The Model field is stored purely for reference, for example so that your software can preselect an appropriate colour model in a colour picker matching how the colour was originally specified.
The colour Value is always held as a RISC OS colour word,
&BBGGRRtt: one byte each for blue, green and red,
and a trailing transparency byte (0 for fully opaque, 255 for fully
transparent).
Colour model numbers:
| Number | Model |
|---|---|
| 1 | RGB (Red, Green, Blue) |
| 2 | CMYK (Cyan, Magenta, Yellow, Key/black) |
| 3 | HSV (Hue, Saturation, Value) |
| 4 | YUV (Luminance, B-Y component, R-Y component) |
Colour format numbers, used both to report the format a colour was read in and to select the format written by Write a colour data block out as a formatted text string:
| Number | Format |
|---|---|
| 0 | Default - use the format already recorded in the data block |
| 1 | RISCOS - &BBGGRRtt, eg. &FF13D400 |
| 2 | RGB - rgb(red,green,blue), values 0-255 |
| 3 | RGB% - rgb(red%,green%,blue%), values 0%-100% |
| 4 | FullHex - #RRGGBB |
| 5 | ShortHex - #RGB (4096 colours) |
| 6 | CMYK - cmyk(cyan,magenta,yellow,key), values 0-255 |
| 7 | CMYK% - cmyk(cyan%,magenta%,yellow%,key%) |
| 8 | HSL - hsl(hue,saturation%,lightness%) |
| 9 | HSV - hsv(hue,saturation,value), values 0-255 |
| 10 | HSV% - hsv(hue,saturation%,value%) |
| 11 | Name - a named HTML colour, eg. white, yellow, green |
| 12 | YUV - not yet implemented |
| 13 | YUV% - not yet implemented |
| 14 | Simple - red,green,blue[,transparency] with no prefix |
The full set of named HTML colours recognised is: white, yellow, orange, red, fuchsia, silver, gray, olive, purple, maroon, aqua, lime, teal, green, blue, navy, black. Names are not case-sensitive, and their use is discouraged in favour of an explicit RGB value wherever possible.
SWI Calls: Initialising the XML Workspace
The SWI calls are grouped, across this section and the three that follow it, as they are in the original specification: calls to initialise the XML workspace, calls to read (parse) an XML file, calls to write an XML file, and universal calls used by both directions (or unrelated to parsing altogether).
| R0 | = | pointer to workspace in the application's own memory, or 0 to request the required size |
| R1 | = | required size, in bytes, of the Content buffer |
| R5 | = | flags; 0 for now |
| R0 | = | required size of the workspace, in bytes (when R0 was 0 on entry) |
Call this SWI twice. First, with a null pointer in R0, to discover the required workspace size; then, having claimed a block of that size, call it again with a pointer to that block in R0. The second call clears the block to zero and fills in sensible default preferences. The resulting pointer (R0 on the second call) must be passed in R0 to every other eXML SWI for that session.
R1 gives the required size of the Content buffer used to hold parsed content data; the module imposes a minimum of 1KB if a smaller value (or a null pointer) is supplied.
This call supersedes SWI eXML_Init and should be used in its place.
content_size% = 10*1024 SYS "eXML_InitBuffer",0,content_size%,,,,0 TO size% DIM xml% size% SYS "eXML_InitBuffer",xml%,content_size%
| R0 | = | pointer to workspace in the application's own memory, or 0 to request the required size |
| R0 | = | required size of the workspace, in bytes (when R0 was 0 on entry) |
This call is deprecated, and is supplied only for backwards compatibility. It assumes a fixed size for the Content buffer (10KB from version 0.18 onwards; 1KB in older versions). SWI eXML_InitBuffer allows the Content buffer size to be chosen and should be used in preference to this call.
As with SWI eXML_InitBuffer, call this SWI twice: once with a null pointer in R0 to discover the required size, and once with a pointer to a claimed block of that size to initialise it.
SYS "eXML_Init" TO size% DIM xml% size% FOR dummy% = 0 TO size% STEP 4 xml%!dummy% = 0 NEXT SYS "eXML_Init",xml%
| R0 | = | pointer to workspace |
| R1 | = |
Variable to set: |
| R2 | = | new value, or pointer to a string, as appropriate to the selected variable |
Once initialised, eXML uses sensible default values for every configurable preference. This call changes one of them for the workspace given in R0. The variable number in R1 behaves as a reason code, selecting which preference is changed and how R2 is interpreted; see each reason below.
| R2 | = | number of spaces to indent by, or 0 to disable indenting |
To produce readable XML output, eXML indents nested elements. This preference sets the number of spaces used per nesting level; the default is 2. Setting it to 0 disables indenting entirely.
SYS "eXML_SetOption",xml%,0,2
<?xml version="1.0" encoding="ISO-8859-1"?>
<page type="Page" category="Test" name="1" fileType="CNDoc">
<begin>
<transition name="Dissolve">
<parameters>
<speed>Fast</speed>
</parameters>
</transition>
</begin>
</page>
| R2 | = | deepest nesting level, counting from 1, at which an extra blank line is inserted before an element; 0 to disable |
Readability can be further improved by inserting a blank line before the opening tag of higher-level elements. By default, extra lines are only inserted at the first nesting level; setting this to 2 or 3 extends that to deeper levels.
| R2 | = | ASCII value of the terminating character |
By default, every string returned by eXML is terminated with a 0x00 byte, suiting C and Assembler. BASIC programs need a 0x0D (Carriage Return) terminator instead; use this preference to change it. The change affects only later calls made with this workspace pointer in R0.
SYS "eXML_SetOption",xml%,2,13
| R2 | = | ASCII value of the separator character, or a string whose first character is used |
SWI eXML_Context, and the string form of context
returned by SWI eXML_Parse, join element names
with / by default, eg.
/document/chapter/page/frame. This preference selects
a different separator character. Avoid choosing a character that can
legally appear inside an element name.
SYS "eXML_SetOption",xml%,3,36 SYS "eXML_SetOption",xml%,3,"$"
| R2 | = | ASCII value of the separator character, or a string whose first character is used |
This preference works like
Set the character used to separate element names in a context string,
but for addressing an attribute within an element's opening tag, eg.
/document/chapter/page@print to address the
print attribute of that page
element, once @ has been set as the attribute
separator.
| R2 | = | RISC OS filetype number |
By default, XML files created by eXML are typed as Text (&FFF), so they can be checked in a standard text editor such as !Edit, !StrongEd or !Zap. This preference sets an alternative filetype, such as &F80 (XML), to be applied when the file is closed. It only affects files being created; it is ignored when reading an existing file.
SYS "eXML_SetOption",xml%,5,&F80
| R2 | = | pointer to a directory path string |
SWI eXML_UpdateFile may need to create a
temporary file. By default, eXML uses
<Wimp$ScrapDir> (creating an
eXML subdirectory within it), falling back to
<eXML$Dir>.Scrap if that variable cannot be
accessed. This preference selects a different directory.
SYS "eXML_SetOption",xml%,6,"<MyApp$Dir>.Files.Temp"
| R0 | = | pointer to workspace |
Clears the stack of attributes accumulated by SWI eXML_Attribute. The stack is cleared automatically once a new element is created, so this call is rarely needed; it exists for the case where the state of the stack is uncertain, for example after an error.
SWI Calls: Reading (Parsing) an XML File
| R0 | = | pointer to workspace |
| R1 | = | pointer to the XML data |
| R2 | = | size of the data, in bytes |
| R5 | = | flags; 0 for now |
The simplest way to parse an XML file is to load it entirely into a buffer in the application's own memory and hand that buffer to eXML with this call, then parse it with repeated calls to SWI eXML_Parse. This only works when the file's size is known and it is small enough to hold in memory, which is true of most everyday XML files.
SYS "OS_File",17,"adfs::4.$.TestFile" TO type%,,,,xml_len% IF type% = 0 THEN ERROR 1,"No such file" DIM xml_buf% xml_len% OSCLI "LOAD "+xml$+" "+STR$~(xml_buf%) SYS "eXML_ParseBuffer",xml%,xml_buf%,xml_len%
| R0 | = | pointer to workspace | ||||||||||||
| R1 | = | pointer to the filename | ||||||||||||
| R5 | = |
Flags:
| ||||||||||||
| R0 | = | handle of the opened file, or 0 if it could not be opened |
Use this call instead of SWI eXML_ParseBuffer when a file is too large to hold in memory, or when its size is not known in advance, such as when parsing streamed data. The file is read a character at a time as parsing proceeds. As with SWI eXML_ParseBuffer, call SWI eXML_Parse repeatedly until parsing is finished.
SYS "eXML_InitBuffer" TO size%
DIM xml% size%
SYS "eXML_InitBuffer",xml%
SYS "eXML_SetOption",xml%,eXML_Option_Terminator,13
SYS "eXML_SetOption",xml%,eXML_Option_Separator,"/"
SYS "eXML_ParseFile",xml%,"adfs::4.$.MyFile" TO in%
REPEAT
SYS "eXML_Parse",xml%,0 TO r%,element%,attr%,context%
CASE r% OF
WHEN eXML_Parser_Start
PRINT "S/"+$element%
WHEN eXML_Parser_End
PRINT "E/"+$element%
WHEN eXML_Parser_ERROR
PRINT "ERROR !!!"
ENDCASE
UNTIL r% = eXML_Parser_Finished OR r% = eXML_Parser_ERROR
SYS "eXML_Close",xml%
| R1 | = | mask; not used at present, should be 0 | ||||||||||||||||||||
| R5 | = |
Flags:
| ||||||||||||||||||||
| R0 | = |
Return code:
| ||||||||||||||||||||||||||||
| R1 | = | pointer to the current element name, for the codes noted above; terminated with the configured terminator (see Set the character used to terminate strings returned by the module) | ||||||||||||||||||||||||||||
| R2 | = | pointer to an attribute array, or 0 if there are no attributes, for Start and Declaration | ||||||||||||||||||||||||||||
| R3 | = | pointer to a context array or context string, depending on bit 0 of the flags on entry |
Once the workspace has been initialised and a document has been presented with SWI eXML_ParseBuffer or SWI eXML_ParseFile, call this SWI repeatedly to parse it, until it returns the Finished or ERROR code. eXML tracks the exact position reached within the document; to restart parsing from the beginning, present the document again with SWI eXML_ParseBuffer or SWI eXML_ParseFile.
Your application need not respond to every return code - many can safely be ignored - but it must always respond to ERROR and Finished, so that the parsing loop terminates.
A self-closing element, eg.
<colour type="rgb" value="255,255,255" />,
always produces a Start code immediately followed by an End code, so that
calling code can track nesting depth itself without special-casing
self-closing elements.
When content is found, R1 points to the data and R2 gives the number of bytes to read; a suitable terminator is always present at the byte immediately following the data, so a short string of content can be read directly as a terminated string. The content may arrive as a single block, or scattered across a series of Content events, depending on the available buffer space and the transport in use; calling code should be able to cope with content arriving in several pieces.
SYS "eXML_ParseBuffer",xml%,xml_buf%,xml_len%
REPEAT
SYS "eXML_Parse",xml%,0 TO r%,element%,attr%
CASE r% OF
WHEN eXML_Parser_Start
PRINT "S/"+$element%
WHEN eXML_Parser_End
PRINT "E/"+$element%
WHEN eXML_Parser_ERROR
PRINT "ERROR !!!"
ENDCASE
UNTIL r% = eXML_Parser_Finished OR r% = eXML_Parser_ERROR
| R0 | = | pointer to workspace |
| R1 | = | pointer to a separator string, or 0 to use the default separator ("/") |
| R0 | = | pointer to the context string |
SWI eXML_Parse can return the current context as an array of element-name pointers (see context array); this call instead builds a single, user-readable context string on demand, useful when Content has just been reported and the calling code wants to know which series of elements it belongs to.
The returned string is terminated with the configured terminator (see Set the character used to terminate strings returned by the module). As with the context array, the name of the element currently being opened or closed is not included, since it is not yet (or no longer) part of the context at that point.
REPEAT SYS "eXML_Parse",xml% TO r%,element%,attr% SYS "eXML_Context",xml%,"/" TO context$ PRINT context$ UNTIL eXML_Parser_Finished
| R0 | = | pointer to workspace |
| R1 | = | pointer to the DTD line's contents, as returned by SWI eXML_Parse |
| R5 | = | flags; should be 0 for now |
| R0 | = | 0 if successful, else -> error block | ||||||||||||
| R1 | = | pointer to the document-type (a string containing no spaces) | ||||||||||||
| R2 | = | pointer to the identifier string | ||||||||||||
| R3 | = | pointer to the URL | ||||||||||||
| R4 | = | undefined at present; reserved for future use | ||||||||||||
| R5 | = |
Updated flags:
| ||||||||||||
A well-formed XML document should contain a Document Type Declaration (DTD), usually near the start of the file, so that its structure and syntax can be verified against a set of rules, for example:
<!DOCTYPE svg PUBLIC "-//W3C//SVG//EN" "http://www.w3c.org/dtd/svg.dtd">
Because the syntax of a DTD line is quite free-form - it may, for instance, be spread over several lines - this call decodes it into the useful parts listed above. Document creation code is encouraged to build such lines with SWI eXML_DTD instead of constructing them by hand, as that ensures correct syntax.
There is no support at present for an embedded (in-line) DTD; only a reference to an external DTD file (whether local, SYSTEM, or public, PUBLIC) is decoded. As in-line DTDs are being phased out in favour of XML Schema, it is unlikely that support will be added.
The presence of the referenced DTD file is not checked by eXML: the URL is
supplied purely for the benefit of an external XML validation tool, and by
convention uses a .dtd filename extension.
| R0 | = | pointer to the source (escaped) string |
| R1 | = | pointer to the destination buffer |
| R0 | = | preserved |
| R1 | = | pointer to the terminating NULL in the destination buffer |
Performs the reverse operation to
SWI eXML_EncodeString: it copies the source
string to the destination, translating each escape sequence
(&lt;, &gt;,
&amp;, &apos;,
&quot;) back into the corresponding ISO
Latin-1 character.
| R0 | = | pointer to workspace |
| R1 | = | offset into the buffer (or file) holding the document |
Added in version 0.36. This call reads the parser's current position, allowing an application to record the start and end points of the content held within an element, so that the sub-content between those points can be parsed or otherwise processed again later.
SWI Calls: Writing an XML File
| R0 | = | pointer to workspace | ||||||||||||
| R1 | = | pointer to the filename | ||||||||||||
| R2 | = | pointer to an attribute array, or 0 | ||||||||||||
| R3 | = | pointer to a version string, or 0 | ||||||||||||
| R4 | = | pointer to an encoding string, or 0 | ||||||||||||
| R5 | = |
Flags:
| ||||||||||||
| R0 | = | file handle, or 0 if not successful |
Creates a new XML file ready for further calls (such as SWI eXML_StartElement) to write its content; eXML tracks correct opening and closing of elements, attributes and content as they are written. Call SWI eXML_Close once the file is complete.
As part of opening the file, this call writes the XML declaration as the
first line of the file, eg.
<?xml version="1.0" encoding="UTF-8"?>.
The version and encoding attributes may be supplied directly in R3 and R4
(the common case, since both are more or less mandatory), or as ordinary
attributes in the array pointed to by R2 - or a mixture of the two, though R2
may be 0 if only version and encoding are required. R2, R3 and R4 are all
optional and should be 0 if not used.
The call returns the handle of the newly opened file (or 0 on failure), so that the caller can track it and close it if an error occurs partway through writing.
SYS "eXML_Open",xml%,"adfs::4.$.TestFile",0,"1.0","UTF-8"
produces:
<?xml version="1.0" encoding="UTF-8"?>
and the following is equivalent:
SYS "eXML_Attribute",xml%,"version","1.0" SYS "eXML_Attribute",xml%,"encoding","UTF-8" TO attr% SYS "eXML_Open",xml%,"adfs::4.$.TestFile",attr%
This call behaves identically to SWI eXML_Open; see that entry for its registers and use.
| R0 | = | pointer to workspace | ||||||||||||
| R1 | = | pointer to a buffer to hold the data; the first word of the buffer must hold its size | ||||||||||||
| R2 | = | pointer to an attribute array, or 0 | ||||||||||||
| R3 | = | pointer to a version string, or 0 | ||||||||||||
| R4 | = | pointer to an encoding string, or 0 | ||||||||||||
| R5 | = |
Flags:
| ||||||||||||
| R0 | = | error code (0 if OK) |
Works in the same way as SWI eXML_Open, except that all further output is written into the supplied memory buffer rather than to a file. This is useful, for example, when building a header for streamed (eg. internet) communication that does not need to be saved to disc first.
| R0 | = | pointer to workspace |
| R1 | = | RISC OS filetype to give the file, or 0 to use the currently configured filetype (see Set the RISC OS filetype used for files created by the module) |
| R0 | = | pointer to the buffer, if SWI eXML_OpenBuffer was used to create the file (otherwise preserved) |
| R1 | = | number of bytes written, if SWI eXML_OpenBuffer was used to create the file (otherwise preserved) |
| R2 | = | size of the buffer given in R0, if SWI eXML_OpenBuffer was used to create the file (otherwise preserved) |
Closes the file most recently opened with SWI eXML_Open or SWI eXML_OpenBuffer, and clears the module's internal pointers to it. If it was opened as a file, the filetype given in R1 (or the configured default, Text &FFF, if R1 is 0) is applied. The filetype is only used when writing; it has no effect when reading an existing XML file.
Not yet implemented.
| R0 | = | pointer to workspace |
| R1 | = | pointer to the filename |
| R0 | = | filehandle of the input file, or 0 if not successful |
| R1 | = | filehandle of the output file, or 0 if not successful |
In some situations, an application needs to update only part of an existing XML file, for example one that several applications share. This call opens an existing file (creating a new output file alongside it) so that its content can be scanned for a required element; the caller's own data can then be inserted, the original element skipped, and scanning continued. Once opened, call SWI eXML_Update repeatedly until parsing is finished, in the same way as SWI eXML_Parse.
SYS "eXML_Init" TO size%
DIM xml% size%
SYS "eXML_Init",xml%
SYS "eXML_SetOption",xml%,eXML_Option_Terminator,13
SYS "eXML_SetOption",xml%,eXML_Option_Separator,"/"
SYS "eXML_UpdateFile",xml%,"adfs::4.$.MyFile" TO in%,out%
claim% = 0
REPEAT
SYS "eXML_Update",xml%,,,,,claim% TO r%,,,context$
claim% = 0
CASE r% OF
WHEN eXML_Parser_Start
CASE context$ OF
WHEN "/document/chapter/page"
claim% = 1
ENDCASE
WHEN eXML_Parser_ERROR
PRINT "!!! Parser ERROR !!!"
ENDCASE
UNTIL r% = eXML_Parser_Finished OR r% = eXML_Parser_ERROR
SYS "eXML_Close",xml%,FileType_XML
| R0 | = | pointer to workspace | ||||||||||||||||
| R1 | = | mask; 0 for now | ||||||||||||||||
| R5 | = |
Flags: on the first call, bit 0 must be clear.
| ||||||||||||||||
| R0 | = |
Return code (a subset of those used by SWI eXML_Parse; only these are returned):
| ||||||||
| R1 | = | pointer to the name of the last element | ||||||||
| R2 | = | pointer to an attribute array of the last element | ||||||||
| R3 | = | pointer to an expanded context string, which - unlike SWI eXML_Parse's context - includes the name of the current element |
Call this SWI repeatedly, after opening a file with SWI eXML_UpdateBuffer or SWI eXML_UpdateFile, until parsing has finished. Once the expanded context string in R3 matches the element you were looking for, set bit 0 of the flags on the next call to claim that element (and everything nested within it): it is skipped, and no further elements within it are reported, until it has closed.
| R0 | = | pointer to workspace | ||||||||||||||||||||||||||||||||||||
| R1 | = | pointer to the element name (optionally prefixed with "/" to mark it self-closing; see below) | ||||||||||||||||||||||||||||||||||||
| R2 | = | pointer to an attribute array, or 0 | ||||||||||||||||||||||||||||||||||||
| R3 | = | pointer to a content string, or 0 | ||||||||||||||||||||||||||||||||||||
| R5 | = |
Flags:
| ||||||||||||||||||||||||||||||||||||
| R0 | = | 0 if successful, else -> error block |
Writes the opening tag of a new element to the file (or buffer) opened by SWI eXML_Open or SWI eXML_OpenBuffer. A StartElement should normally be followed by optional content, optional nested elements, and finally a matching SWI eXML_EndElement.
An element may instead be marked empty (self-closing), in which case it may
still hold attributes but cannot hold content or nested elements, and a
/ is inserted before the tag's closing
>, eg.
<colour red="255" green="128" blue="44"/>.
An element can be marked self-closing in any of three ways: by setting bit
0 of the flags on entry; by prefixing the element name in R1 with
/, eg. R1 -> /colour; or by
supplying a content string in R3 (see below). In every case, do not call
SWI eXML_EndElement for the element - it is
closed automatically.
A single short line of text can be inserted as content by pointing R3 at it: the StartElement is written, the content is inserted, and the EndElement is written automatically, so no separate call to SWI eXML_EndElement is required. For example:
SYS "eXML_StartElement",xml%,"comment",attr%,"Just a line of text."
produces:
<comment>Just a line of text.</comment>
If more than one line of text is needed, omit the string in R3 and use SWI eXML_Content between matching SWI eXML_StartElement and SWI eXML_EndElement calls instead.
| R0 | = | pointer to workspace | ||||||||||||||||||||
| R1 | = | pointer to the element name, or 0 to close the most recently opened element | ||||||||||||||||||||
| R5 | = |
Flags:
| ||||||||||||||||||||
| R0 | = | 0 if successful, else -> error block |
Closes an element previously opened with SWI eXML_StartElement - except an element that was marked self-closing, which must not be closed explicitly. Every other opened element must eventually be closed, and elements must be closed in the correct (nested) order; XML does not permit overlapping elements.
eXML checks the element name given in R1 against its internal stack of open elements, and produces an error if the wrong element is closed. Supplying 0 in R1 closes the most recently opened element without this check, but this is not recommended, since it prevents eXML from verifying that elements are being closed in the correct order.
The following opens and closes correctly:
<document>
<page id="1">
<frame>
<text>Example</text>
</frame>
</page>
</document>
whilst this produces an error, because <frame> and <page> close in the wrong order:
<document>
<page id="1">
<frame>
<text>Example</text>
</page>
</frame>
</document>
| R0 | = | pointer to workspace | ||||||||||||||||||||||||||||
| R1 | = | pointer to the data | ||||||||||||||||||||||||||||
| R2 | = | number of bytes to write | ||||||||||||||||||||||||||||
| R5 | = |
Flags:
| ||||||||||||||||||||||||||||
| R0 | = | 0 if successful, else -> error block |
Inserts content inside the element most recently opened with SWI eXML_StartElement. A single short line can also be supplied directly to SWI eXML_StartElement; use this call instead for larger blocks of text, multiple lines, or any number of bytes. Content may contain line feed and/or carriage return characters, and these are preserved (unlike in HTML).
Content may only be written once an element is open; writing content without an open element produces the Error_eXML_FileNotOpen error.
The following places content inside a nested element:
<document>
<page>
<frame id="1">This is content</frame>
</page>
</document>
| R0 | = | pointer to workspace |
| R1 | = | pointer to the attribute name |
| R2 | = | pointer to the attribute value |
| R0 | = | pointer to an attribute array, or 0 if unsuccessful |
An attribute takes the form name="value", and
an element may carry any number of them. Rather than building an
attribute array
by hand, call this SWI once for each attribute to be added to the pending
stack, then pass the returned pointer to
SWI eXML_StartElement,
SWI eXML_Open, or
SWI eXML_Declaration. The stack is cleared
automatically once the element is created, so it rarely needs to be cleared
explicitly (see SWI eXML_ClearAttributes).
SYS "eXML_StartElement",xml%,"document" id$ = "1" value$ = "date and time" SYS "eXML_Attribute",xml%,"id",id$ TO attr% SYS "eXML_Attribute",xml%,"name",value$ TO attr% SYS "eXML_StartElement",xml%,"field",attr%,"Hello" SYS "eXML_EndElement",xml%,"document"
produces:
<document> <field id="1" name="date and time">Hello</field> </document>
Note that the field element does not need an explicit
SWI eXML_EndElement call, since it was given a
content string and so is closed automatically.
| R0 | = | pointer to workspace |
| R1 | = | pointer to the declaration name |
| R2 | = | pointer to an attribute array, or 0 |
| R5 | = | flags |
| R0 | = | pointer to an attribute array, or 0 if unsuccessful |
Declarations are typically used near the start of a document; the most
common is the <?xml?> declaration, which must be
present for a document to be considered valid. This call produces output
similar to SWI eXML_StartElement, but the line
begins with <? and ends with ?>.
Declarations do not need to be (and cannot be) closed with
SWI eXML_EndElement, and are not considered part
of the document body.
The content and processing of a declaration is entirely up to the application reading it; eXML places no further interpretation on it.
<?xat doctype="status" version="4.0"?>
| R0 | = | pointer to workspace |
| R1 | = | pointer to the comment text |
| R2 | = | length of the string in R1, or 0 to use the entire string up to a control character |
| R0 | = | 0 if successful, else Corrupted |
Writes a comment to the output document, in the standard form
<!-- comment text -->.
SYS "eXML_Comment",xml%,"this is a comment" SYS "eXML_Comment",xml%,"this is a comment",17
| R0 | = | pointer to workspace | ||||||||||||||||||||||||
| R1 | = | pointer to the document-type string | ||||||||||||||||||||||||
| R2 | = | pointer to an identifier string (optional, PUBLIC only) | ||||||||||||||||||||||||
| R3 | = | pointer to a URL string | ||||||||||||||||||||||||
| R5 | = |
Flags:
| ||||||||||||||||||||||||
| R0 | = | 0 if successful, else -> error block |
Writes a DOCTYPE line at the start of a document, after the
<?xml?> declaration but before the first
element. A DTD describes the structure of a document, and the possible
values for each attribute, so that a validation tool can check the document
against it; it is also used by the
XML File Dispatcher
to identify a document's type when it is double-clicked.
There are three kinds of DTD. A SYSTEM DTD is stored locally, and needs only a relative URL to the DTD file in R3. A PUBLIC DTD is stored externally (for example on the internet); it needs a URL in R3 and an identifier string in R2 describing the publisher, format and language of the document, and is selected by setting bit 4 of the flags. An in-line DTD embeds its information directly in the DOCTYPE declaration, but this is not currently supported by eXML.
The document-type in R1 identifies the kind of document, eg.
svg; where an application uses several document
types, including the application's own name as a prefix, eg.
artworks:svg, allows an unlimited number of distinct
types to be created without clashing with anyone else's.
The identifier in R2 (PUBLIC DTDs only) should describe the publisher, the
document format and version, and the language, separated by double
forward-slashes, eg.
-//XAT//CableNews Edition 4.0//EN. A leading
- indicates a publisher that is not ISO certified; a
leading + indicates one that is. A SYSTEM DTD has no
identifier; if one is supplied it is ignored, so that XML checkers and
browsers do not report an error over it.
The URL in R3 is not used by eXML's own parser: it exists purely for an
external XML validation tool. For a SYSTEM DTD, use a relative path, eg.
edition.dtd or
files/edition.dtd; for a PUBLIC DTD, use an absolute
URL, eg.
http://www.xat.nl/dtd/cablenews/edition.dtd. In both
cases the filename should carry a .dtd extension, and
the path should be given in URL form rather than RISC OS pathname form.
SYS "eXML_DTD",xml%,"edition",,"files/edition.dtd"
produces a SYSTEM DTD:
<!DOCTYPE edition SYSTEM "files/edition.dtd">
whereas:
id$ = "-//XAT//Edition 4.0//EN" url$ = "http://www.xat.nl/dtd/edition.dtd" SYS "eXML_DTD",xml%,"edition",id$,url$,,(1 << 4)
produces a PUBLIC DTD:
<!DOCTYPE edition PUBLIC "-//XAT//Edition 4.0//EN" "http://www.xat.nl/dtd/edition.dtd">
| R0 | = | pointer to workspace |
| R1 | = | pointer to the data |
| R2 | = | number of bytes to insert |
| R5 | = | flags |
| R0 | = | 0 if successful, else -> error block |
Inserts a Processing Instruction into the output document, used to pass
information directly to whatever XML processor reads the document (for
example, the parser inside your own application). Processing instructions
are not considered part of the document body, and their syntax is not
checked by eXML. Only the opening (<?) and closing
(?>) delimiters are added; the data in R1 is
inserted between them exactly as supplied.
$data% = "xat:impression validation extra" size% = LEN($data%) + 1 SYS "eXML_PI",xml%,data%,size%
produces:
<?xat:impression validation extra?>
| R0 | = | pointer to workspace |
| R1 | = | pointer to the data |
| R2 | = | number of bytes to insert |
| R5 | = | flags |
| R0 | = | 0 if successful, else -> error block |
The XML standard allows raw data - including reserved characters such as
< and > - to be inserted
into a document without escaping, inside a CDATA section. The only
sequence a CDATA section cannot contain is ]]>,
which marks its end. A CDATA section is opened with
<![CDATA[ and closed with
]]>.
Reading CDATA sections is not currently supported by eXML.
SWI Calls: Universal Calls
| R0 | = | pointer to the original string |
| R1 | = | pointer to the destination, somewhere in the caller's own workspace |
| R2 | = | required string terminator, eg. 0 or 13 |
| R1 | = | pointer to the terminating character written into the destination string |
| R2 | = | length of the destination string, excluding its terminator |
This call is not really specific to XML, but is provided because it is useful for building structures (such as large menus) from data received elsewhere, for example a WIMP message. It copies a string terminated by any control character into the caller's own buffer, applying whichever terminator the caller's software requires.
The following, in BBC BASIC, extracts the document-type and path from a message block:
DIM mes 256 DIM par% 256 ... SYS "eXML_GetString",mes!40,par%,13 doctype$ = $par% SYS "eXML_GetString",mes+44,par%,13 path$ = $par%
There are many ways to express a colour in an XML document - some inherited from HTML, some specific to XML - and converting between them can be tedious, especially when the user should be free to choose their preferred colour model. SWI eXML_ColourOp converts between these formats, and reads or writes colours directly. Every reason code operates on a colour data block held in the caller's own memory (except reason codes 3 and 4, which use the module's own internal workspace instead - see below). R0 selects the operation; the meaning of the other registers depends on the reason code.
It is recommended that colour be expressed as an attribute on an existing
element rather than as an element of its own, eg.
<background colour="rgb(255,255,255)"/>
rather than
<background><colour value="rgb(255,255,255)"/></background>.
| R1 | = | pointer to a colour data block, or 0 to find the required size | ||||||||
| R5 | = |
Flags:
| ||||||||
| R2 | = | size of the colour data block, in bytes, when R1 was 0 on entry |
Before a colour data block is used with any other reason code, it should be set to a defined state by calling this reason code with a pointer to it in R1. To find the required size of the block, call with a null pointer in R1; the size is returned in R2. The size should always be requested first, since it may grow in a future version of the module.
| R1 | = | pointer to a colour data block |
| R2 | = | pointer to a text string containing a colour definition |
| R5 | = | flags; 0 for now |
| R0 | preserved | |
Parses the colour definition string in R2, in any of the formats described under colour data block, and updates the block pointed to by R1 with the resulting colour, colour model, and format.
| R1 | = | pointer to a colour data block | ||||||||||||
| R2 | = | pointer to a buffer to hold the resulting string | ||||||||||||
| R5 | = |
Flags:
| ||||||||||||
Formats the colour held in the data block pointed to by R1 as text, in either the colour's original format or the format requested in bits 8-15 of the flags, and writes it to the buffer in R2. This is typically used directly to produce XML output.
SYS "eXML_ColourOp",0,0 TO ,,size% DIM block% size% DIM string% 256 SYS "eXML_ColourOp",0,block% SYS "eXML_ColourOp",2,block%,string%,,,13 OR (2 << 8) PRINT $string% SYS "eXML_ColourOp",1,block%,string% PRINT ~block%!12
| R1 | = | pointer to a formatted colour text string |
| R0 | = | the colour, as a RISC OS colour value |
Equivalent to Read a colour from a text string into a colour data block, but uses the module's own internal workspace for the conversion rather than a caller-supplied colour data block, so no block needs to be created first.
SYS "eXML_ColourOp",3,"rgb(255,127,64)" TO colour%
| R1 | = | colour value, in RISC OS format |
| R5 | = | flags, as for SWI eXML_ColourOp 2 |
| R0 | = | pointer to the resulting string |
Equivalent to Write a colour data block out as a formatted text string, but uses the module's own internal workspace for the conversion rather than a caller-supplied colour data block.
SYS "eXML_ColourOp",4,&4682FF00,,,,&020D TO colour$ PRINT colour$
produces:
rgb(255,130,70)
| R0 | = |
Format to read (or, when writing, to produce):
| ||||||||||||||||||||
| R1 | = |
When writing (not yet implemented), further flags select what is written:
| ||||||||||||||||||||
| R0 | = |
|
The official XML standard specifies dates in reverse (year-month-date)
order, separated by dashes, eg. 2014-06-31, optionally
followed by a time, separated by an optional space and the letter
T, eg. 2014-06-31 T14:23:01. The
part following T gives hours, minutes and seconds,
each separated by a colon.
Reading is implemented: this call reads an existing date (and optional time) from a text string, either in a specific format selected in R0, or automatically (format 0), in which case XML and EUR dates are assumed to be separated by dashes and USA dates by slashes. Writing a date is not yet implemented.
| R0 | = | pointer to the source string |
| R1 | = | pointer to the destination buffer |
| R0 | = | preserved |
| R1 | = | pointer to the terminating NULL in the destination buffer |
Copies a NULL-terminated string from the source in R0 to the destination in
R1, translating any of the reserved characters
(<, >,
&, ',
") into the corresponding XML escape sequence
(&lt;, &gt;,
&amp;, &apos;,
&quot;).
Not implemented.
The eXML module distributed as version 0.43 (13 Jun 2020) - one point
release after this API document - also occupies four further SWI numbers
in its decoding table which this specification does not describe:
eXML_GetStack (&576D9),
eXML_ConvertToUTF8 (&576DA),
eXML_DecodeBlock (&576E2), and
eXML_Debug (&576E3). Their names are known from
the module's own SWI decoding table, but their calling conventions are not
published anywhere, so they are not documented as full SWI entries here.
XML File Dispatcher
Most files on RISC OS carry a filetype, which lets an application recognise
its own files and respond when the Filer reports that one has been
double-clicked; if no application claims a filetype, the Filer falls back
to a system variable such as Alias$@RunType_FFF to
launch something suitable.
XML makes it easy to create many new file formats, but RISC OS filetypes are a scarce resource (numbers &000 to &FFF only), and typically only one filetype will be allocated to a given application, which must then inspect each file's contents to work out how to use it. By default, an XML file with no more specific filetype allocated to it should use RISC OS filetype &F80. Although every such file then looks identical to the Filer, its document-type can still be recovered from its DOCTYPE line, eg.
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE MyDoc SYSTEM "mydoc.dtd">
<document>
<chapter id="1">
<page id="1">
<frame>Hello world...</frame>
</page>
</chapter>
</document>
Here, the document-type (referred to below as
<doctype>) is MyDoc. A
document-type may be any length and contain any printable XML character
except a space; conventionally it uses only letters and, perhaps, numbers,
eg. artworks, svg,
cablenews, config. An
application handling several distinct document-types of its own may prefix
them with a family name and a colon, eg.
artworks:config, but should only do this for a format
that is intended to remain private, rather than public - see also
XML File Dispatcher: Naming Conventions below.
For any of this to work, the file must carry RISC OS filetype &F80. Double-clicking such a file is intercepted by eXML, which extracts the document-type from its DOCTYPE line and then either broadcasts a WIMP message, or launches a suitable application, as described below.
XML File Dispatcher: System Variables
eXML uses system variables of the following form to decide what to launch when an XML file is opened:
An application which wants to be launched automatically whenever the user
double-clicks a file of a given <doctype>
should set this variable, replacing <doctype>
with the actual document-type it handles, to a command that launches it
with the file's pathname in its command tail:
Set XML$@Run_svg Run <ArtWorks$Dir>.!Run %%**0
Note the doubled %, which is required to escape the
literal % that follows it. Whenever the user
double-clicks an XML file (RISC OS filetype &F80) with document-type
svg, and ArtWorks is not already running, eXML
launches it as configured, substituting the file's pathname for
%*0 in the command tail.
If no XML$@Run_<doctype> variable exists for a
file's document-type, and no running application claims the
message described below,
the file cannot be opened automatically. Every other system variable
beginning XML$@ is reserved for future use by eXML.
XML File Dispatcher: Messages
Before falling back to the XML$@Run_<doctype>
system variable, the dispatcher broadcasts a User_Message (18) of type
, in a format similar to the
standard Message_DataOpen (5) broadcast by the Filer
when any file is double-clicked. The only difference is that the field at
offset +40 holds a pointer to a string giving the document-type, rather
than the filetype.
XML File Dispatcher: Naming Conventions
Choose a document-type carefully, to avoid clashing with another
application's document-type when a file is loaded automatically. Prefer a
name qualified by your own application or company, such as
MyApp:MyDoc or MyCompany:MyDoc,
over an unqualified name such as MyDoc. Some
examples: artworks, xat:database,
dbman:users, xat:dbman:database.
Error Messages
Returned by any call that writes content, elements, or other output when no file (or buffer) is currently open for writing, for example calling SWI eXML_Content before SWI eXML_Open or SWI eXML_OpenBuffer.
Returned, for example, by SWI eXML_EndElement when there is no corresponding open element left to close.
Returned by SWI eXML_EndElement when trying to close an element after every currently open element has already been closed.
Returned by SWI eXML_EndElement when the element name given in R1 does not match the element eXML expects to be closed next, according to its internal stack of open elements - for example, closing elements in the wrong (non-nested) order.
Returned by SWI eXML_Parse (as the Encoding return code) when a document requests an encoding outside the set listed under Encodings, or one that is not yet implemented in the running version of the module.
Returned when a document-type cannot be established from a DOCTYPE declaration, for example when dispatching a double-clicked XML file, or when decoding a malformed DTD line with SWI eXML_DecodeDTD.
Returned when the
XML File
Dispatcher cannot dispatch a double-clicked XML file - for
example, no application claimed the
message, and no matching
XML$@Run_<doctype> system variable was set.
Returned when a call is made with a workspace pointer that eXML does not recognise as having been initialised by SWI eXML_Init or SWI eXML_InitBuffer.
Returned when a call needs the workspace's Content buffer (see SWI eXML_InitBuffer), but none is available, or when a caller-supplied buffer pointer, for example to SWI eXML_OpenBuffer, is not valid.
Returned when a caller-supplied buffer, such as the Content buffer sized by SWI eXML_InitBuffer, or the buffer given to SWI eXML_OpenBuffer, is too small to hold the data eXML needs to read or write into it.
Returned by SWI eXML_ColourOp 1 (or reason 3) when the text supplied does not match any of the colour formats described under colour data block.
Returned by SWI eXML_ColourOp 1 (or reason 3) when the text supplied is recognisably one of the supported colour formats, but its syntax is malformed - for example, a missing value or an unmatched bracket.
Returned by SWI eXML_ColourOp 2 (or reason 4) when the colour format requested in the flags is one that the module does not yet support producing, such as YUV or YUV%.
Returned by SWI eXML_ColourOp 2 (or reason 4) when the stored colour value cannot be represented in the requested output format, for example because it falls outside the range that format can express.