XML Manipulation with VBScript
XML manipulation using the WSH is achieved through the Microsoft.XMLDOM object, which comes as standard with everything since NT5 (i.e. XP, 2000, etc).
In case you're unfamiliar with DOM parsing of XML, just think of it as taking an XML document and converting into a tree representation. To use a DOM parser effectively, you need to understand how XML is structured and how nodes relate to one another. However, I'm not going to get into this here. This section assumes you already know enough about XML to get on with the job at hand.
The most important objects are the DOMDocument (the root node) and the IXMLDOMNode (any other node).
The DOMDocument
Properties
- documentElement - Returns the root element. This may not be the same as the firstChild.
- childNodes - returns a collection of IXMLDOMNode objects. Can be scanned using GetElements.
- xml - returns a Unicode text representation of the entire XML document.
Methods for writing XML
- createDocumentType(name, publicID, systemID, internalSubset) - must be added as first child.
- createElement(name) - creates an element which would normally be populated with sub-elements, attributes or text nodes.
- createTextNode(text) - creates a text node, which would then be placed in an element.
- save(destination) - saves the XML as a file.
Methods for reading XML
- getElementsByTagName(name) - returns a collection of all elements matching the name.
- load(XMLfile) - loads a complete XML file or well-formed fragment from a file or URL. Returns false if it fails.
- loadXML(XMLtext) - reads in XML (complete) or a well-formed fragment as text. Returns false if it fails.
- selectNodes(XPathExpression) - Allows you to select a node or nodes using an XPath. This makes life very easy!
- selectSingleNode(XPathExpression) - Returns only a single (or first) node matching the XPath.
The IXMLDOMNode
Properties
- attributes - returns an IXMLDomNamedNodeMap of attributes. Note: for-each is no good here!! Use length, getNamedItem(key) and item(index).
- childNodes - obvious.
- firstChild - obvious.
- lastChild - obvious.
- nextSibling - obvious, but always check if the current object is the lastChild before referencing this!
- nodeName - obvious.
- nodeType - returns integer value; one of Element (1), Text (2; contains text in NodeValue), CDATA (4), Comment (8) and Doctype (10), plus others of little consequence.
- nodeTypeString - see above.
- nodeValue - returns the contents of a Text or Comment node.
- previousSibling - obvious.
- xml - returns the well-formed xml fragment encompassed by this node.
Methods for reading
- hasChildNodes - returns a boolean.
- selectNodes - same as with DOMDocument.
- selectSingleNode - same as wtih DOMDocument.
Methods for writing
- appendChild(child)
- insertBefore(newchild, child)
- removeChild(child)
- replaceChild(newnode, oldnode)
- setAttribute(name, value)
Sample Scripts to Parse XML
Let's say we want to parse this XML:
<?xml version="1.0" encoding="UTF-8"?>
<MaintenanceConfig>
<Functions>
<Function>
<Name>Database Reindex</Name>
<Sequence>1</Sequence>
<RunDays>Monday</RunDays>
<DependenciesBefore>System_Closedown</DependenciesBefore>
<DependenciesAfter>System_Startup</DependenciesAfter>
<Suspended/>
</Function>
<Function>
<Name>System Backup</Name>
<Sequence>2</Sequence>
<RunDays>Monday Tuesday Wednesday Thursday Friday Saturday Sunday</RunDays>
<DependenciesBefore>X1_Closedown</DependenciesBefore>
<DependenciesAfter>X1_Startup</DependenciesAfter>
<ForceSuspendToday/>
</Function>
<Function>
<Name>Server Restart</Name>
<Sequence>3</Sequence>
<RunDays>Tuesday</RunDays>
<Parameters>Server1 Server2 Server3</Parameters>
<ForceRunToday/>
</Function>
</Functions>
<Dependencies>
<Dependency dependencyID="System_Closedown">
<Name>System Closedown</Name>
<Sequence>5</Sequence>
</Dependency>
<Dependency dependencyID="X1_Closedown">
<Name>X1 Closedown</Name>
<Sequence>10</Sequence>
</Dependency>
<Dependency dependencyID="X1_Startup">
<Name>X1 Startup</Name>
<Sequence>90</Sequence>
</Dependency>
<Dependency dependencyID="System_Startup">
<Name>System Startup</Name>
<Sequence>95</Sequence>
</Dependency>
</Dependencies>
<GlobalSuspend>False</GlobalSuspend>
</MaintenanceConfig>
We could parse it with a script that looks like this:
Set args = WScript.Arguments
if (args.length <> 1) then
Wscript.echo "Bad arguments. Must supply location of XML file."
Wscript.quit (1)
end if
inputFile = args.item(0)
set xmlDoc = CreateObject("Microsoft.XMLDOM")
xmlDoc.async = false
if xmlDoc.load(inputFile) then
Wscript.echo "File " & inputFile & " loaded"
else
Wscript.echo "Could not load file " & inputFile & ". Aborting."
Wscript.quit (1)
end if
set root = xmlDoc.documentElement
Wscript.echo "Root: " + root.nodeName
Wscript.echo
' Demonstrate use of XPath
xmlDoc.setProperty "SelectionLanguage", "XPath"
set globalSuspendNode = xmlDoc.selectSingleNode("//GlobalSuspend")
WScript.echo "GlobalSuspend: " & globalSuspendNode.text
const ELEMENT_TEXT = 3
' Get all the nodes called 'Function'
WScript.echo "Displaying Functions"
WScript.echo "--------------------"
WScript.echo
set objFunctionList = root.getElementsByTagName("Function")
for each func in objFunctionList
set functionNodeList = func.childNodes
for each child in functionNodeList
if child.hasChildNodes then
Wscript.echo child.nodeName & ": " & child.firstChild.nodeValue
else
Wscript.echo child.nodeName
end if
next
Wscript.echo ' blank line
next
' Get all the nodes called 'Function'
WScript.echo "Displaying Dependencies"
WScript.echo "-----------------------"
WScript.echo
set objDependencyList = root.getElementsByTagName("Dependency")
for each dependency in objDependencyList
set attribs = dependency.attributes
dependencyID = attribs.getNamedItem("dependencyID").nodeValue
WScript.echo "DependencyID: " & dependencyID
set dependencyNodeList = dependency.childNodes
for each child in dependencyNodeList
if child.hasChildNodes then
Wscript.echo child.nodeName & ": " & child.firstChild.nodeValue
else
Wscript.echo child.nodeName
end if
next
WScript.echo
next
And the output would look like this:
Microsoft (R) Windows Script Host Version 5.6 Copyright (C) Microsoft Corporation 1996-2001. All rights reserved. File c:\temp\maintenanceConfig.xml loaded Root: MaintenanceConfig GlobalSuspend: False Displaying Functions -------------------- Name: Database Reindex Sequence: 1 RunDays: Monday DependenciesBefore: System_Closedown DependenciesAfter: System_Startup Suspended Name: System Backup Sequence: 2 RunDays: Monday Tuesday Wednesday Thursday Friday Saturday Sunday DependenciesBefore: X1_Closedown DependenciesAfter: X1_Startup ForceSuspendToday Name: Server Restart Sequence: 3 RunDays: Tuesday Parameters: System1 System2 System3 ForceRunToday Displaying Dependencies ----------------------- DependencyID: System_Closedown Name: System Closedown Sequence: 5 DependencyID: X1_Closedown Name: X1 Closedown Sequence: 10 DependencyID: X1_Startup Name: X1 Startup Sequence: 90 DependencyID: System_Startup Name: System Startup Sequence: 95
And here's some alternative code, using XPath and populating a Dictionary object:
Set dictDependencies = CreateObject("Scripting.Dictionary")
dictDependencies.CompareMode = TEXT_MODE
' Get all the nodes called 'Function'
WScript.echo "Displaying Dependencies"
WScript.echo "-----------------------"
WScript.echo
set objDependencyList = root.getElementsByTagName("Dependency")
for each dependency in objDependencyList
dictDependencies.add _
dependency.selectSingleNode("@dependencyID").text, _
dependency.selectSingleNode("Sequence").text
next
for each strKey in dictDependencies.keys
WScript.echo strKey & ": " & dictDependencies.item(strKey)
next
Sample Scripts for Writing XML
This script generates an XML document from scratch:
Sub GenerateStatusReportingEvent(eventTypeCode, system, msg, completionStatus)
Dim eventDestPath, filename, destination
Dim logTime
Dim statusReportingEventNode
Dim eventTypeCodeNode, branchNumNode, logTimeNode, _
originatingSystemNode, detailsNode, completionStatusNode
eventDestPath = objShell.ExpandEnvironmentStrings("%instance_data%") _
& "\projects\POS_Branch_StatusReporting\incomingEvents"
if not objFso.FolderExists(eventDestPath) then
LogInfo "ERR : " & eventDestPath & " does not exist. Creating...", 1, 1
CreateFolderHierarchy(eventDestPath)
end if
logTime = getDateStr(now())
filename = logTime & "_" & statusReportingEventCount & ".evt"
destination = eventDestPath & "\" & filename
destination = objFSO.GetAbsolutePathName(destination)
LogInfo "INFO : Generating event with status " & completionStatus & ": " _
& destination, 1, 0
Dim strXML
strXML = "<!DOCTYPE StatusReportingEvent SYSTEM 'wrbrMaintenanceEvent.dtd'>"_
& vbCRLF &_
"<StatusReportingEvent />"
set xmlDoc = CreateObject("Microsoft.XMLDOM")
xmlDoc.validateOnParse = False
xmlDoc.async = false
if not (xmlDoc.loadXML(strXML)) then
LogInfo "ERR : Unable to load XML: " & xmlDoc.parseError.reason
set statusReportingEventNode = _
xmlDoc.appendChild(xmlDoc.createElement("StatusReportingEvent"))
xmlDoc.appendChild(xmlDoc.createProcessingInstruction("xml", "version = ""1.0"""))
else
xmlDoc.insertBefore xmlDoc.createProcessingInstruction("xml", "version = ""1.0"""), _
xmlDoc.childNodes(0)
set statusReportingEventNode = xmlDoc.childNodes(2)
end if
with statusReportingEventNode
.appendChild xmlDoc.createTextNode(vbCrLf)
set eventTypeCodeNode = .appendChild(xmlDoc.createElement("EventTypeCode"))
.appendChild xmlDoc.createTextNode(vbCrLf)
set branchNumNode = .appendChild(xmlDoc.createElement("BranchNum"))
.appendChild xmlDoc.createTextNode(vbCrLf)
set logTimeNode = .appendChild(xmlDoc.createElement("LogTime"))
.appendChild xmlDoc.createTextNode(vbCrLf)
set originatingSystemNode = .appendChild(xmlDoc.createElement("OriginatingSystem"))
.appendChild xmlDoc.createTextNode(vbCrLf)
set detailsNode = .appendChild(xmlDoc.createElement("Details"))
.appendChild xmlDoc.createTextNode(vbCrLf)
set completionStatusNode = .appendChild(xmlDoc.createElement("CompletionStatus"))
.appendChild xmlDoc.createTextNode(vbCrLf)
end with
eventTypeCodeNode.appendChild(xmlDoc.createTextNode(eventTypeCode))
branchNumNode.appendChild(xmlDoc.createTextNode(getBranchNum()))
logTimeNode.appendChild(xmlDoc.createTextNode(logTime))
originatingSystemNode.appendChild(xmlDoc.createTextNode(system))
detailsNode.appendChild(xmlDoc.createTextNode(msg))
completionStatusNode.appendChild(xmlDoc.createTextNode(completionStatus))
xmlDoc.save destination
statusReportingEventCount = CInt(statusReportingEventCount) + 1
End Sub
Actually, that's a small lie. It actually seeds the XML using a string which includes the doctype definition. That's because the DOM doesn't directly support adding this line programatically.
Here's some code that demonstrates how to replace the text in an element:
if func.selectSingleNode("Name").text = "System Backup" then
set parameterNode = func.selectSingleNode("Parameters")
LogInfo "Amending System Backup parameter to workstation 2", 0, 1
parameterNode.replaceChild xmlDoc.createTextNode("2"), parameterNode.firstChild
end if
| Just Too Good Last updated: June, 2006 (DJL) |
Drop me a line