×

XML

Extensible Markup Language (XML) is a simple, very flexible text format derived from SGML (ISO 8879). Originally designed to meet the challenges of large-scale electronic publishing, XML is also playing an increasingly important role in the exchange of a wide variety of data on the Web and elsewhere.

XML is a markup language similar to HTML, but without predefined tags to use. Instead, you define your own tags designed specifically for your needs. This is a powerful way to store data in a format that can be stored, searched, and shared. Most importantly, since the fundamental format of XML is standardized, if you share or transmit XML across systems or platforms, either locally or over the internet, the recipient can still parse the data due to the standardized XML syntax.

Comparison between XML and HTML:

For more information regarding XML:

The following documentation details how XML content can be read and written using the described Omni Automation classes and methods.

 

XML.Document Class

Instances of the XML.Document class are created to hold XML data, enabling it to be parsed and edited. XML documents can be created from existing XML content, or constructed in sections by adding XM elements.

Class Functions

The XML.Document class functions can be used to create new XML documents from XML data read from files, the clipboard, or network servers.

Constructors

Creating empty instances of the XML.Document that can then be populated with XML data as needed.

Instance Functions

The functions that can be called on an instance of the XML.Document class:

Properties

The properties of an instance of the XML.Document class.

Q: What is a DTD?
A DTD is a Document Type Definition. A DTD defines the structure and the legal elements and attributes of an XML document.

XML.Document.Configuration Class

Supporting class for creating an instance of the XML.Document clasd.

The values of these properties can be set on the newly created XML.Document.Configuration instance:

XML.WhitespaceBehavior Class

Used in the creation of an instance of XML.Document determines how whitespace is handled.

XML.WhitespaceBehavior.Type Class

XML.WhitespaceBehavior.Type.Auto


<?xml version=\"1.0\" encoding=\"UTF-8\"?> <main-element><make>Chevy</make><model>Bolt</model><color>Red</color></main-element>
XML.WhitespaceBehavior.Type.Ignore


<?xml version=\"1.0\" encoding=\"UTF-8\"?> <main-element> <make>Chevy</make> <model>Bolt</model> <color>Red</color> </main-element>
XML.WhitespaceBehavior.Type.Preserve


<?xml version=\"1.0\" encoding=\"UTF-8\"?> <main-element><make>Chevy</make><model>Bolt</model><color>Red</color></main-element>

StringEncoding Class

These are the properties of the StringEncoding class, whose value is expressed as dot-extensions of the parent class, such as: StringEncoding.UTF8

Create New XML Document


keys = ["make", "model", "color"] values = ["Chevy", "Bolt", "Red"] config = new XML.Document.Configuration() behavior = new XML.WhitespaceBehavior(XML.WhitespaceBehavior.Type.Ignore) config.whitespaceBehavior = behavior doc = new XML.Document("main-element", config) keys.forEach((key, index) => { doc.addElement(key, function(){ doc.appendString(values[index]) }) }) doc.xmlData().toString()

The resulting XML textual data:

Resulting XML Textual Data


<?xml version="1.0" encoding="UTF-8"?> <main-element> <make>Chevy</make> <model>Bolt</model> <color>Red</color> </main-element>

Hers’s a variation of the previous script that creates an XML document that is saved to disk:

Create/Save XML File


(async () => { // CREATE XML DOCUMENT keys = ["make", "model", "color"] values = ["Chevy", "Bolt", "Red"] config = new XML.Document.Configuration() behavior = new XML.WhitespaceBehavior(XML.WhitespaceBehavior.Type.Ignore) config.whitespaceBehavior = behavior doc = new XML.Document("main-element", config) keys.forEach((key, index) => { doc.addElement(key, function(){ doc.appendString(values[index]) }) }) // SAVE XML DOCUMENT TO DISK wrapper = FileWrapper.withContents("Car-Data.xml", doc.xmlData()) filesaver = new FileSaver() urlObj = await filesaver.show(wrapper) new Alert("FILE URL", urlObj.string).show() })();

The following example uses the documentsDirectory property of the URL class to write XML data to a file in the app’s default folder without requiring user-approval.

Writing XML Data to File without Picker


keys = ["name", "id", "rate"] values = ["Samantha", "com.apple.speech.synthesis.voice.samantha.premium", "0.45"] config = new XML.Document.Configuration() behavior = new XML.WhitespaceBehavior(XML.WhitespaceBehavior.Type.Ignore) config.whitespaceBehavior = behavior doc = new XML.Document("main-element", config) keys.forEach((key, index) => { doc.addElement(key, function(){ doc.appendString(values[index]) }) }) wrapper = FileWrapper.withContents("voice-data.xml", doc.xmlData()) folderURL = URL.documentsDirectory fileURL = folderURL.appendingPathComponent("voice-data.xml") wrapper.write(fileURL, [FileWrapper.WritingOptions.Atomic], null)
Quicklook window for XML file

And here's how to read the XML data from the previous example:

Reading XML Data from File


folderURL = URL.documentsDirectory fileURL = folderURL.appendingPathComponent("voice-data.xml") fileURL.fetch(function(data){ cdoc = XML.Document.fromData(data) voiceName = doc.rootElement.firstChildNamed("name").stringContents voiceID = doc.rootElement.firstChildNamed("id").stringContents voiceRate = doc.rootElement.firstChildNamed("rate").stringContents console.log("Voice Name: ", voiceName) console.log("Voice ID: ", voiceID) console.log("Voice Rate: ", voiceRate) }, function(err){ new Alert(err.name, err.message).show() })
 

XML.Element Class

The XML.Element class is used to describe the fundamental component of XML data, the XML Element. As mentioned previously, XML elements, unlike those of HTML, are not predefined but are structured, stored, and retrieved in a consistent ways.

Constructors

Creating an instance of an XML.Element uses the standard new item constructor:

Instance Properties

Each instance of the XML.Element class has specific properties:

Instance Functions

Here are the functions that can be used on an instance of the XML.Element class:

Example: Read/Parse XML

The following examples use the fetch() method to retrieve textual weather data from NOAA (National Oceanic and Atmospheric Administration) in XML format that can be parsed for specific data.

In the final example, the current temperature at the San Francisco International Airport is retrieved and displayed in an alert. URLs for other NOAA weather stations can be found in an XML document posted by NOAA.

weather alert dialog

Retrieve XML

The first step in processing the XML weather data is to retrieve the data from the NOAA internet server using the fetch() function of the URL class:

Retrieve XML Data


url = URL.fromString('https://w1.weather.gov/xml/current_obs/KSFO.xml') url.fetch(data => { console.log(data.toString()) })

Converting the retrieved data to string results in XML textual data similar to the following.

NOTE: the opening and closing tags or the individual XML elements are highlighted in red, and any element attributes are colored green. The rootElement in the example data is named current_observation and contains all of the other informational elements.

Resulting XML Data


<?xml version="1.0" encoding="ISO-8859-1"?> <?xml-stylesheet href="latest_ob.xsl" type="text/xsl"?> <current_observation version="1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.weather.gov/view/current_observation.xsd"> <credit>NOAA's National Weather Service</credit> <credit_URL>https://weather.gov/</credit_URL> <image> <url>https://weather.gov/images/xml_logo.gif</url> <title>NOAA's National Weather Service</title> <link>https://www.weather.gov</link> </image> <suggested_pickup>15 minutes after the hour</suggested_pickup> <suggested_pickup_period>60</suggested_pickup_period> <location>San Francisco, San Francisco International Airport, CA</location> <station_id>KSFO</station_id> <latitude>37.61961</latitude> <longitude>-122.36558</longitude> <observation_time>Last Updated on Feb 14 2022, 2:56 pm PST</observation_time> <observation_time_rfc822>Mon, 14 Feb 2022 14:56:00 -0800</observation_time_rfc822> <weather>Mostly Cloudy</weather> <temperature_string>53.0 F (11.7 C)</temperature_string> <temp_f>53.0</temp_f> <temp_c>11.7</temp_c> <relative_humidity>80</relative_humidity> <wind_string>West at 18.4 MPH (16 KT)</wind_string> <wind_dir>West</wind_dir> <wind_degrees>290</wind_degrees> <wind_mph>18.4</wind_mph> <wind_kt>16</wind_kt> <pressure_string>1018.7 mb</pressure_string> <pressure_mb>1018.7</pressure_mb> <pressure_in>30.08</pressure_in> <dewpoint_string>46.9 F (8.3 C)</dewpoint_string> <dewpoint_f>46.9</dewpoint_f> <dewpoint_c>8.3</dewpoint_c> <windchill_string>48 F (9 C)</windchill_string> <windchill_f>48</windchill_f> <windchill_c>9</windchill_c> <visibility_mi>10.00</visibility_mi> <icon_url_base>https://forecast.weather.gov/images/wtf/small/</icon_url_base> <two_day_history_url>https://www.weather.gov/data/obhistory/KSFO.html</two_day_history_url> <icon_url_name>bkn.png</icon_url_name> <ob_url>https://www.weather.gov/data/METAR/KSFO.1.txt</ob_url> <disclaimer_url>https://www.weather.gov/disclaimer.html</disclaimer_url> <copyright_url>https://www.weather.gov/disclaimer.html</copyright_url> <privacy_policy_url>https://www.weather.gov/notice.html</privacy_policy_url> </current_observation>

Parsing XML: Element Names

In parsing the XML data, it is useful to first retrieve the names of the various XML elements, so you can a better idea of the what data is available to be retrieved.

This is accomplished by using the fromData(…) function of the XML.Document class to create an new XML.Document instance, and then calling the apply() function on the XML document’s rootElement to “walk” the document tree to retrieve the value of the name property of each found instance of the XML.Element class:

Parsing XML Data for Element Names


targetURL = URL.fromString( 'https://w1.weather.gov/xml/current_obs/KSFO.xml' ) targetURL.fetch(data => { doc = XML.Document.fromData(data) var elementNames = new Array() doc.rootElement.apply(item => { if (item instanceof XML.Element) { elementNames.push(item.name) } }) console.log(JSON.stringify(elementNames)) })

The result of the previous script will be an array of the names of the various XML elements:

Names of the XML Elements


//--> ["current_observation","credit","credit_URL","image","url","title","link","suggested_pickup","suggested_pickup_period","location","station_id","latitude","longitude","observation_time","observation_time_rfc822","weather","temperature_string","temp_f","temp_c","relative_humidity","wind_string","wind_dir","wind_degrees","wind_mph","wind_kt","pressure_string","pressure_mb","pressure_in","dewpoint_string","dewpoint_f","dewpoint_c","windchill_string","windchill_f","windchill_c","visibility_mi","icon_url_base","two_day_history_url","icon_url_name","ob_url","disclaimer_url","copyright_url","privacy_policy_url"]

Retrieve, Parse, Display

Now that the names of the retrieved XML elements have been collected, you can choose the elements whose values you wish to retrieve and display to the user in an alert dialog. The value of an instance of the XML.Element class can be retrieved using the firstChildNamed(…) function and then getting the value of the element’s stringContents property:

Retrieve, Parse, and Display


url = URL.fromString( 'https://w1.weather.gov/xml/current_obs/KSFO.xml' ) url.fetch(data => { doc = XML.Document.fromData(data) loc = doc.rootElement.firstChildNamed("location").stringContents keys = ["weather", "temperature_string", "wind_string"] titles = ["Weather", "Temperature", "Wind"] strings = keys.map((key, index) => { value = doc.rootElement.firstChildNamed(key).stringContents return `${titles[index]}: ${value}` }) alertContent = strings.join("\n") new Alert(loc, alertContent).show() })
weather alert dialog

More Parsing Concepts

In the previous NOAA example, the individual locations of the NOAA XML data are listed as "station" elements (of the Station XML Index)(DOWNLOAD ZIP) with property keys and values:

A “station” Element


<station> <station_id>KAKR</station_id> <state>OH</state> <station_name>Akron, Akron Fulton International Airport</station_name> <latitude>41.0375</latitude> <longitude>-81.46417</longitude> <html_url>https://w1.weather.gov/data/obhistory/KAKR.html</html_url> <rss_url>https://weather.gov/xml/current_obs/KAKR.rss</rss_url> <xml_url>https://weather.gov/xml/current_obs/KAKR.xml</xml_url> </station>

The value of the xml_url property is a link to the forecast data for the specified station:

Forecast Data Link


<xml_url>https://weather.gov/xml/current_obs/KLXN.xml</xml_url>

The “station” elements of the NOAA XML data can be identified by the value of their state property. For example, here's a script that retrieves the ID and NAME values for all of the Ohio (OH) locations from the source XML:

Retrieve Locations (stations) for Ohio (OH)


var locations = new Array() targetURL = URL.fromString('https://w1.weather.gov/xml/current_obs/index.xml') targetURL.fetch(data => { doc = XML.Document.fromData(data) doc.rootElement.apply(item => { if (item instanceof XML.Element && item.name === "station"){ if(item.firstChildNamed("state").stringContents === "OH"){ id = item.firstChildNamed("station_id").stringContents name = item.firstChildNamed("station_name").stringContents locations.push({"id":id,"name":name}) } } }) console.log(locations) })

And the resulting pairs of Ohio locations:

Ohio Locations (ID and NAME)


[{"id":"KAKR","name":"Akron, Akron Fulton International Airport"},{"id":"KAOH","name":"Lima, Lima Allen County Airport"},{"id":"KBJJ","name":"Wooster, Wayne County Airport"},{"id":"KBKL","name":"Cleveland, Burke Lakefront Airport"},{"id":"KCAK","name":"Akron Canton Regional Airport"},{"id":"KCGF","name":"Cleveland / Cuyahoga"},{"id":"KCLE","name":"Cleveland Hopkins International Airport"},{"id":"KCMH","name":"John Glenn Columbus International Airport"},{"id":"KDAY","name":"Dayton, Cox Dayton International Airport"},{"id":"KDFI","name":"Defiance, Defiance Memorial Airport"},{"id":"KFDY","name":"Findlay, Findlay Airport"},{"id":"KFFO","name":"Dayton / Wright-Patterson Air Force Base"},{"id":"KHAO","name":"Butler County Regional Airport"},{"id":"KHZY","name":"Ashtabula - Northeast Ohio Regional Airport"},{"id":"KILN","name":"Wilmington, Airborne Airpark Airport"},{"id":"KLCK","name":"Rickenbacker Air National Guard Base"},{"id":"KLHQ","name":"Lancaster, Fairfield County Airport"},{"id":"KLNN","name":"Willoughby"},{"id":"KLPR","name":"Lorain / Elyria, Lorain County Regional Airport"},{"id":"KLUK","name":"Cincinnati, Cincinnati Municipal Airport Lunken Field"},{"id":"KMFD","name":"Mansfield - Mansfield Lahm Regional Airport"},{"id":"KMGY","name":"Dayton, Dayton-Wright Brothers Airport"},{"id":"KMNN","name":"Marion, Marion Municipal Airport"},{"id":"KOSU","name":"Columbus, Ohio State University Airport"},{"id":"KPHD","name":"New Philadelphia, Harry Clever Field"},{"id":"KSGH","name":"Springfield, Springfield-Beckley Municipal Airport"},{"id":"KTDZ","name":"Toledo - Toledo Executive Airport"},{"id":"KTOL","name":"Toledo - Toledo Express Airport"},{"id":"KTZR","name":"Columbus, Bolton Field Airport"},{"id":"KUNI","name":"OHIO U/ATHEN-ALBANY"},{"id":"KVTA","name":"Newark, Newark Heath Airport"},{"id":"KYNG","name":"Youngstown, Youngstown-Warren Regional Airport"},{"id":"KZZV","name":"Zanesville, Zanesville Municipal Airport"},{"id":"KAXV","name":"Neil Armstrong Airport"},{"id":"KMWO","name":"Hook Field Municipal Airport"},{"id":"K4I3","name":"Knox County Airfield"},{"id":"KI68","name":"Warren County Airport"},{"id":"KPOV","name":"Portage County Airport"},{"id":"KDLZ","name":"Delaware Municipal Airport"},{"id":"KMRT","name":"Marysville Union County Airport"},{"id":"KVES","name":"Versailles Darke County Airport"},{"id":"KPCW","name":"Port Clinton Carl R Keller Field Airport"},{"id":"KI69","name":"Batavia Clermont County Airport"},{"id":"KUSE","name":"Wauseon Fulton County Airport"},{"id":"KRZT","name":"Chillicothe Ross County Airport"},{"id":"KI67","name":"Cincinnati W Airport"},{"id":"KUYF","name":"London Madison County Airport"},{"id":"KI19","name":"Lewis A. Jackson Regional Airport"},{"id":"KOWX","name":"Ottawa Putnam County Airport"},{"id":"KCDI","name":"Cambridge Municipal Airport"},{"id":"KI23","name":"Fayette County Airport"},{"id":"KPMH","name":"Greater Portsmouth Regional Airport"},{"id":"KOXD","name":"Miami University Airport"},{"id":"KI74","name":"Urbana Grimes Field"},{"id":"KI43","name":"James A Rhodes Airport"},{"id":"KVNW","name":"Van Wert County Airport"}]

Writing From and To a Data Object

The following scripts demonstrate how to write to file from a JavaScript object, and how to convert the contents of an XML file onto a JavaScript object.

JavaScript Data Object to XML File


dataObj = { "name":"Samantha", "id":"com.apple.speech.synthesis.voice.samantha.premium", "rate":"0.45" } config = new XML.Document.Configuration() behavior = new XML.WhitespaceBehavior(XML.WhitespaceBehavior.Type.Ignore) config.whitespaceBehavior = behavior doc = new XML.Document("main-element", config) keys = Object.keys(dataObj) keys.forEach((key, index) => { doc.addElement(key, function(){ doc.appendString(dataObj[key]) }) }) wrapper = FileWrapper.withContents("voice-data.xml", doc.xmlData()) folderURL = URL.documentsDirectory fileURL = folderURL.appendingPathComponent("voice-data.xml") wrapper.write(fileURL, [FileWrapper.WritingOptions.Atomic], null)
XML to Data Object using Fetch()


folderURL = URL.documentsDirectory fileURL = folderURL.appendingPathComponent("voice-data.xml") dataObj = fileURL.fetch(function(data){ cdoc = XML.Document.fromData(data) dataObj = new Object() cdoc.rootElement.children.forEach(element => { if(element.name){ dataObj[element.name] = element.stringContents } }) // LOG DATA ITEMS keys = Object.keys(dataObj) keys.forEach(key => { console.log(key + ":", dataObj[key]) }) }, function(err){ new Alert(err.name, err.message).show() })
XML to Data Object using Asynchronous Fetch()


(async () => { try { folderURL = URL.documentsDirectory fileURL = folderURL.appendingPathComponent("voice-data.xml") request = URL.FetchRequest.fromString(fileURL.string) request.method = 'GET' request.cache = "no-cache" response = await request.fetch() responseCode = response.statusCode if (responseCode >= 200 && responseCode < 300){ console.log(response.bodyString) xmlDoc = XML.Document.fromData(response.bodyData) dataObj = new Object() xmlDoc.rootElement.children.forEach(element => { if(element.name){ dataObj[element.name] = element.stringContents } }) // LOG DATA ITEMS keys = Object.keys(dataObj) keys.forEach(key => { console.log(key + ":", dataObj[key]) }) } else { throw new Error(`The request responded with ${responseCode} error.`) } } catch(err){ new Alert(err.name, err.message).show() } })();

Similar processes as functions:

Function: Store Data Object as XML


function storeDataAsXML(dataObj, xmlFileName){ try { config = new XML.Document.Configuration() behavior = new XML.WhitespaceBehavior(XML.WhitespaceBehavior.Type.Ignore) config.whitespaceBehavior = behavior doc = new XML.Document("main-element", config) keys = Object.keys(dataObj) keys.forEach((key, index) => { doc.addElement(key, function(){ doc.appendString(dataObj[key]) }) }) wrapper = FileWrapper.withContents(xmlFileName, doc.xmlData()) folderURL = URL.documentsDirectory fileURL = folderURL.appendingPathComponent(xmlFileName) result = wrapper.write(fileURL, [FileWrapper.WritingOptions.Atomic], null) return true } catch(err){ new Alert(err.name, err.message).show() } }
Function: Retrieve Stored XML Data


function retrieveStoredXMLData(xmlFileName){ folderURL = URL.documentsDirectory fileURL = folderURL.appendingPathComponent(xmlFileName) dataObj = fileURL.fetch(function(data){ cdoc = XML.Document.fromData(data) dataObj = new Object() cdoc.rootElement.children.forEach(element => { if(element.name){ dataObj[element.name] = element.stringContents } }) return dataObj }, function(err){ new Alert(err.name, err.message).show() }) }