SAX-like XML Event Processing

With the release of XML Tools 2.6, it is possible to implement event-based XML processing using XML Tools. This is done by passing an AppleScript script object to the new SAX handler parameter of the parse XML command. This script object is expected to provide a series of handlers that respond to XML parsing events.

This approach is useful when you want to populate a custom data structure directly from XML data instead of extracting the data from the nested collection of XML element classes normally generated by the parse XML command.

Here is a very simple example illustrating how this works:

script EventProcessor
   property elementNames : {}

   on XMLStartElement(elementName, elementAttributes)
      -- called when an XML element begins   
      set end of elementNames to elementName
   end XMLStartElement
end script

set theXML to "<data>
    <test/>
    <test name=\"mark\">
   data in second test element
    </test>
    data in root element
</data>"

set xxx to parse XML theXML SAX handler EventProcessor
xxx's elementNames
--   Result:
--   {"data", "test", "test"}

In this example, a copy of the EventProcessor script object is passed to the parse XML command. As the parse XML command is parsing the XML data, it will call the EventProcess’s XMLStartElement handler whenever a new XML element begins. When parsing completes, the EventProcessor object is returned to AppleScript. In this particular case, the XMLStartElement handler records the name of each XML element tag.

Here is an example of an event handler object implementing all the handlers that the XML parse command can call. You need only include the handlers for events that you are interested in handling:

script AllEventHandlers

   on XMLStartElement(elementName, elementAttributes)
      -- called when a new XML element begins
      display dialog "XMLStartElement: " & elementName & ", Attributes: " & (length of elementAttributes)
   end XMLStartElement

   on XMLEndElement(elementName)
      -- called when an XML element ends
      display dialog "XMLEndElement: " & elementName
   end XMLEndElement

   on XMLCharacterData(xmlData)
      --called when there is XML data for an element
      display dialog "XMLCharacterData: " & xmlData
   end XMLCharacterData

   on XMLComment(comment)
      -- called when an XML comment is encoutered
      -- must call parse XML with comments
      display dialog "XMLComment: " & comment
   end XMLComment

   on XMLDefaultContent(xmlData)
      -- called for content outside the root element (i.e. XML declaration)
      display dialog "XMLDefaultContent: " & xmlData
   end XMLDefaultContent

   on XMLStartCData()
      -- called at the beginning of an XML CData section
      display dialog "XMLStartCData"
   end XMLStartCData

   on XMLEndCData()
      -- called at the end of an XML CData section
      display dialog "XMLEndCData"
   end XMLEndCData

   on XMLStartNamespace(prefix, uri)
      -- called when a namespace reference begins
      display dialog "XMLStartNamespace: " & prefix & ", URI: " & uri
   end XMLStartNamespace

   on XMLEndNamespace(prefix)
      -- called when a namespace reference ends
      display dialog "XMLEndNamespace: " & prefix
   end XMLEndNamespace

   on XMLProcessingInstruction(target, piData)
      -- called when an XML processing instruction is encountered
      -- must call parse XML with including processing instructions
      display dialog "XMLStartNamespace: " & target & ", Data: " & piData
   end XMLProcessingInstruction

   on XMLNotStandalone()
      -- called when XML is not standalone, and there is no DTD.  Return true to allow processing to 
      -- continue if this handler is missing, parse XML's strict standalone parameter value is used
      display dialog "XMLNotStandalone"
      return true -- allow processing to continue
   end XMLNotStandalone

   on XMLStartDocTypeDecl(docTypeName, systemID, publicID, hasInternalSubset)
      -- called at the beginning of a DOCTYPE declaration
      display dialog "XMLStartDocTypeDecl: " & docTypeName & ", systemID: " & systemID & ", ¬
      publicID: " & publicID & ", hasInternalSubset: " & hasInternalSubset
   end XMLStartDocTypeDecl

   on XMLEndDocTypeDecl()
      -- called at the end of a DOCTYPE declaration
      display dialog "XMLEndDocTypeDecl"
   end XMLEndDocTypeDecl

   on XMLExternalEntityRef(context, base, systemID, publicID)
      -- called after an external entity (DTD) has been loaded
      display dialog "XMLExternalEntityRef: " & context & ", base: " & base & ", ¬
      systemID: " & systemID & ", publicID: " & publicID
   end XMLExternalEntityRef

   on XMLUnparsedEntityDecl(entityName, base, systemID, publidID, notationName)
      display dialog "XMLUnparsedEntityDecl: " & entityName & ", base: " & base & ", ¬
      systemID: " & systemID & ", publicID: " & publicID & ", notationName: " & notationName
   end XMLUnparsedEntityDecl

   on XMLNotationDecl(notationName, base, systemID, publidID)
      display dialog "XMLNotationDecl: " & notationName & ", base: " & base & ", ¬
      systemID: " & systemID & ", publicID: " & publicID
   end XMLNotationDecl

   on XMLParseResult(errNumber, errMessage)
      -- if the parsing is aborted doe to an AppleScript error, errNumber and errMessage describe the error. Otherwise
      -- these parameters contain missing value.

      -- return the data you want parse XML to return. If this method is omitted, the entire script object is returned
      return "some data"
   end XMLParseResult
end script

NOTE: Attributes are passed to the XMLStartElement as a record where keys are the attribute name and values are the corresponding attribute value.

NOTE: If there is an error in one of the XML event handlers, parse XML will abort the parse. When this happens parse XML will return the result of XMLParseResult() handler or the script object, if XMLParseResult() is not defined, in the partial result of the error. You can extract this information using this syntax:

script SAXHandler
   property elementNames : {}

   on XMLStartElement(elementName, elementAttributes)
      -- called when an XML element begins
      set end of elementNames to elementName
      error "Error Message from SAXHandler" -- signal an error to abort parsing the rest of the XML stream
   end XMLStartElement

   on XMLParseResult()
      -- return the data you want parse XML to return.  If this method is omitted, the entire script object is returned
      return elementNames
   end XMLParseResult
end script

try
   set xxx to parse XML "<data>
    <!-- a comment -->
    <test/>
    <test name=\"mark\">
   data in second test element
    </test>
    data in root element
</data>" SAX handler SAXHandler with including processing instructions and including comments
on error errMsg partial result pr
   {errMsg, pr} -- partial result is the data returned by XMLParseResult
end try
--   Result:
--   {
--      "xmlstartelement SAX handler error: Error Message from SAXHandler", 
--      {
--         "data"
--      }
--   }

NOTE: Script Debugger’s AppleScript debugger is unable to debug XML event handlers while they are being executed by the parse XM command.

Parameters

SAX handler

(new in v2.6)
script object

When the SAX handler parameter is specified, parse XML switches to a SAX-like event-based mode of parsing where handlers in the script object specified are called in response to events as the XML data is parsed.

When this parameter is omitted, parse XML performs as it has done in the past and returns an XML document class containing a nested data structure representing the content of the parsed XML data.

strict standalone

boolean

Ignored if the event handler object implements the XMLNotStandalone handler.

expanding external entities

boolean

By default, external entity references (e.g. DTDs) are ignored since XML Tools is a non-validating XML parser. When expanding external entities is true, XML Tools uses the Mac OS URL Access facilities to access the externally referenced entity.

If the external entity exists on another machine, you must have an active internet connection.

Supported URL formats: file:///…, http://…, and ftp://…

NOTE: The XMLExternalEditityRef handler is called after the external entity has been loaded.

including comments

boolean

By default, comments in your XML data are ignored. The including comments parameter must be true in order for the event handler’s XMLComment handler to be called.

including processing instructions

boolean

By default, XML processing instructions are ignored. The including processing instructions parameter must be true in order for the event handler’s XMLProcessingInstruction handler to be called.

serializing

boolean

Ignored.

base path

string

Provides a base URL for all external entity IDs. For example, the following code uses a DTD loaded from http://www.latenightsw.com/dtds/mydtd.dtd:

parse XML "<?xml version=\"1.0\"?> 
<!DOCTYPE data SYSTEM \"mydtd.dtd\">
<data>
    <data>
    <tag/> 
</data>" base path "http://www.latenightsw.com/dtds/"</td>

preserving whitespace

boolean

By default, the parse XML command strips all leading and trailing whitespace characters and normalizes multiple whitespace characters within a string to a single space.

NOTE: The xml-space=”preserve” attribute is honored when preserving whitespace is false.
NOTE: The xml-space=”ignore” attribute is not honored when preserving whitespace is true.
NOTE: Whitespace characters in CDATA sections are never stripped.

When preserving whitespace is true, parse XML returns all XML data, including whitespace.

The parse XML command will strip whitespace according to these rules before calling the event handler’s XMLCharacterData handler.

allowing leading whitespace

boolean

The XML specification states that well formed XML documents have no leading whitespace before the declaration. However, for historical reasons, XML Tools allows XML documents to contain leading whitespace data. If allowing leading whitespace is false, XML Tools will report an error when whitespace appears at the beginning of an XML document.

NOTE: This only applies to documents that begin with a declaration. If your document does not have an XML declaration, this option is ignored.

seperate namespace URIs

boolean

Ignored.