John McFadyen's profileJohn McFadyens Windows I...PhotosBlogGuestbookMore Tools Help

Blog


    May 05

    Custom XML Dom Class

    Technorati Tags: ,,,

    So lately a bunch of people are asking a little more about XML and XML DOM. Sometime ago when I was trying to learn how XML dom worked I threw this vbs class together to assist in reading and writing xml via the DOM. For those of you whom are not aware of the dom its the XML Document Object Model.

    Using the DOM you can traverse xml nodes read and manipulate data. Looking at this entirely from a packaging perspective its a great way to store and read package data into a package.

    So the attached script emulates the functionality in the NetFX classes, something I was not aware of prior to writing this code. So here is a bunch of vbs function (or methods for the dev's out there). Using these methods you can add / get / set and delete xml data from an XML structure. There is even a simple method to format the white space for readability purposes.

    There is a bunch of sample code at the top of the script to aid in how this should be used. If anyone is after some more specific requests in how they can pull data from xml drop a few comments and I will endeavour to answer your questions.

    The code is pretty old but its a great starting point for those of you whom are interested in learning xml.

    '============================================================================
    ' LANG:            VBScript
    ' NAME:            IpXMLClass.vbs
    ' AUTHOR:        John McFadyen (john.mcfadyen@gmail.com)
    ' VERSION:        0.1
    ' DATE:            2006-07-30
    ' Description:        Generate / edit / query an XML file
    '
    ' UPDATES:        http://www.installpac.com/scripting
    '
    ' For Options:        This script has no options and is expected to be used as a class.
    ' Feedback:        Please send feedback to john.mcfadyen@gmail.com
    '
    ' Notes:
    ' This has has only been tested with MSXML
    ' LICENSE:
    ' Copyright (c) 2004-2006, John McFadyen
    ' All rights reserved.
    '
    ' Redistribution and use in source and binary forms, with or without
    ' modification, are permitted provided that the following conditions are met:
    '
    '  * Redistributions of source code must retain the above copyright notice,
    '    this list of conditions and the following disclaimer.
    '  * Redistributions in binary form must reproduce the above copyright notice,
    '    this list of conditions and the following disclaimer in the documentation
    '    and/or other materials provided with the distribution.
    '  * Neither the name Installpac or the names of its contributors may be used
    '    to endorse or promote products derived from this software without
    '    specific prior written permission.
    '
    ' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    ' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    ' IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    ' ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
    ' LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    ' CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    ' SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    ' INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    ' CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    ' ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    ' POSSIBILITY OF SUCH DAMAGE.
    '==========================================================
    '==========================================================

    ' Example 1 Generating a new xml document
    ' =======================================
    ' set xmlDoc = new XMLDom
    ' xmlDoc.filenameXML = "Johnny.xml"
    ' call xmldoc.CreateElement("Root")
    '    call xmldoc.CreateElement("License")
    '        call xmldoc.CreateElementString("starttime", now())
    '        call xmldoc.CreateElementString("starttime", now())
    '        call xmldoc.CreateElementString("starttime", now())
    '        call xmldoc.CreateElementString("starttime", now())
    '        call xmldoc.CreateElement("Hello")
    '        call xmldoc.CreateEndElement
    '        call xmldoc.CreateElement("Hello1")
    ' call xmldoc.close
    '
    ' Example 2 Updating a current xml
    ' ================================
    ' set xmlDoc = new XMLDom
    ' xmlDoc.filenameXML = "Y:\Projects\TestApplication_1-0_R01\TestApplication_1-0_R011New.XML"
    ' xmlDoc.ValidateXML "Y:\Projects\TestApplication_1-0_R01\TestApplication_1-0_R011.XML"
    ' xmlDoc.XMLLoader "Y:\Projects\TestApplication_1-0_R01\TestApplication_1-0_R011.XML"
    ' Note: The following line using an external object
    ' set objNode = xmlDoc.GetNode("//PACKAGESRC")
    ' xmlDoc.SetNodeValue "Test", objNode, false
    ' set objNodeLocation = xmlDoc.GetNode("//LOCATION")
    ' xmlDoc.CreateElementString "NewNode", "test"
    ' xmlDoc.close

    ' Example 3 Using an actual stylesheet
    ' =========
    ' call xmldoc1.CreateElement("Root")
    ' call xmldoc1.CreateXmlProcess("stylesheet.xsl")
    '     call xmldoc1.CreateElement("License")
    '    call xmldoc1.CreateElementString("starttime", now())
    '     call xmldoc1.CreateElementString("starttime", now())
    '    call xmldoc1.CreateElementString("starttime", now())
    '     call xmldoc1.CreateElementString("starttime", now())
    '     call xmldoc1.CreateAttribute("test", "value1")
    '    call xmldoc1.CreateAttribute("test1", "value1")
    '     call xmldoc1.CreateAttribute("test2", "value1")
    ' call xmldoc1.CreateEndElement
    ' call xmldoc1.close
    '
    '=========================================================================================================
    '=========================================================================================================
    ' Script version

    Dim strScriptVersion
    strScriptVersion = "0.1"

    '=========================================================================================================

    class XMLDom

        dim xmlfilename                        'stores the xml filename
        dim xslfilename                        'stores the xsl filename
        dim xmldom                          'stores a DOM Object
        dim xmlroot                            'stores the Root Node
        dim xmlcurrentnode                    'stores the Current Node
        dim xmlparent                        'stores the Parent Node of current node
        dim xmlelements
        dim intIndentation                    'stores the indentspacing (not required with XSL sheet applied)
        dim blnWhiteSpace

    '=========================================================================================================

        private Sub Class_Initialize
            dim xmldeclaration
            Set xmlDom = CreateObject("Microsoft.XMLDOM")
            xmlDom.preserveWhiteSpace = False
            xmlDom.loadxml "<?xml version='1.0'?>"
            'xmlDom.createProcessingInstruction("<?xml version='1.0'?>")
            set xmlcurrentnode = xmlDom
        End Sub

        private Sub Class_Terminate
            'xmldom.LoadXML TransformXML
            'if xmlFilename <> nothing then xmldom.save(xmlFilename)
        End Sub

    '=========================================================================================================

        public Property let FilenameXML(strFilename)   
            xmlFilename = strFilename
        End Property

        public Property let FilenameXSL(strFilename)   
            xslFilename = strFilename
        End Property

        public Property let Indentspacing(intIndentSpacing)
            ' Not required now we are using stylesheets
        End Property

        public Property let Whitespace(blnWhiteSpace)
            sprpWhiteSpace = blnWhiteSpace
        End Property

        public Property get Whitespace()
             sprpWhiteSpace = prpWhiteSpace
        End Property

        public Property let EmptyAttribute(blnAllowEmpty)
            prpAllowEmpty = blnWhiteSpace
        End Property

    '=========================================================================================================

        public function CreateRootElement(strRootNode)

            set xmlroot = xmldom.CreateElement(strRootNode)
            xmldom.appendchild xmlroot
            set xmlcurrentnode = xmlroot
            Set objProcessing = xmlDom.CreateProcessingInstruction("xml","version='1.0'")
            xmlDom.insertBefore objProcessing ,xmlroot
            'xmlroot.appendChild xmlDom.CreateTextNode(vbcrlf & string(indentLevel, Chr(9)))   

        end function

        sub CreateElement(strElementName)
            dim xmlNewNode
            set xmlNewNode = xmlDom.createElement(strElementName)
            xmlcurrentnode.appendchild xmlNewNode
            set xmlcurrentnode = xmlNewNode
            'xmldom.save xmlFilename
            'xmlcurrentnode.appendChild xmlDom.CreateTextNode(vbcrlf & string(indentLevel, Chr(9)))   
        end sub

        sub CreateElementString(strElementName, strElementValue)
            dim xmlNewNode
            set xmlNewNode = xmlDom.createElement(strElementName)
            if isnull(strElementValue) then strElementValue = ""
            xmlNewNode.text = strElementValue
            xmlcurrentnode.appendchild(xmlNewNode)
            'xmlcurrentnode.appendChild xmlDom.CreateTextNode(vbcrlf & string(indentLevel, Chr(9)))   
        end sub

        sub CreateElementStringNew(strElementName, strElementValue)
            dim xmlNewNode
            set xmlNewNode = xmlDom.createElement(strElementName)
            if isnull(strElementValue) then strElementValue = ""
            xmlNewNode.text = strElementValue
            xmlcurrentnode.appendchild(xmlNewNode)
            'xmlcurrentnode.appendChild xmlDom.CreateTextNode(vbcrlf & string(indentLevel, Chr(9)))   
        end sub

        sub CreateEndElement
            set xmlcurrentnode = xmlcurrentnode.parentnode
        end sub

        sub DeleteEndElement
            dim xmlEndNode
            set xmlEndNode = xmlcurrentnode
            set xmlcurrentnode = xmlcurrentnode.parentnode
            xmlcurrentnode.removechild(xmlEndNode)
        end sub

        Function RemoveElement(strNodename, objNode)
            Dim objRemoveElement    ' As MSXML2.IXMLDOMNode   
            If Not objNode Is Nothing Then
              Set objRemoveElement = objNode.SelectSingleNode(strNodename)   
              If Not objRemoveElement Is Nothing Then
                objNode.removeChild objRemoveElement
              End If
            End If
        End Function

        Public Function RemoveElements(sXPath, objNode)
            Dim objNodes            ' As MSXML2.IXMLDOMNodeList
            Dim intNodeCount        ' As Long
            Set objRemoveElements = objNode.selectNodes(sXPath)
            if not objRemoveElements is nothing then
              For intNodeCount = 0 To objRemoveElements.length - 1
                objRemoveElements(intNodeCount).parentNode.removeChild objRemoveElements(intNodeCount)
              Next
            end if
        End Function

        Public Function GetElements

            set xmlElements = xmlDom.DocumentElement
            set GetElements = xmlElements
        end function

    '=========================================================================================================

        sub CreateAttribute(strAttrName,strAttrValue)
            if isnull(strAttrValue) then strAttrValue = ""
            xmlcurrentnode.setAttribute strAttrName, strAttrValue
        end sub

        Public Function GetAttribute(strAttrName, objNode)     ' as string
            Dim objAttr                                            ' As MSXML2.IXMLDOMNode
            GetAttribute = ""
            If Not objNode Is Nothing Then
              Set objAttr = objNode.Attributes.getNamedItem(strAttrName)
              If Not objAttr Is Nothing Then
                GetAttribute = CStr(objAttr.Text)
              End If
            End If
        End Function
        Public Function SetAttribute(strAttrName, strAttrValue, objNode)                     
            Dim objAttr      ' As MSXML2.IXMLDOMNode
            If Not objNode Is Nothing Then
              If strAttrValue <> "" or AllowEmptyAttr Then
                Set objAttr = objNode.Attributes.getNamedItem(strAttrName)
                If objAttr Is Nothing Then
                  Set objAttr = CreateAttribute(strAttrName, strAttrValue, objNode)
                End If 
                objAttr.Text = strAttrValue
              Else
                RemoveAttribute strAttrName, objNode
              End If
            End If       
        End Function

        Function RemoveAttribute(strAttrName, objNode)
            If Not objNode Is Nothing Then
              objNode.Attributes.removeNamedItem strAttrName
            End If
        End Function 
    '=========================================================================================================

        Public Function HasChildNodes()
            HasChildNodes = False
            if xmlCurrentNode.HasChildNodes then HasChildNodes = True

        end function

        Public Function NodeExists(strNodeName)
            Dim objNode
            msgbox "node test"
            NodeExists = false
    '        if xmlCurrentNode.HasChildNodes then
            For each objNode in xmlCurrentNode.ChildNodes
                    msgbox objNode.Nodename
                    if objNode.Nodename = strNodename then
                        NodeExists = True
                        exit for
                    end if
                next
    '        end if
        end function

        Public Function GetChildNodes()

            'On Error Resume Next

            Dim arrNodes()
            redim preserve arrNodes(1,0)
            y = 0
            For each objNode in xmlCurrentNode.ChildNodes
                if y >= ubound(arrNodes,2) then redim preserve arrNodes(1,y)
                arrNodes(0,y) = objNode.Nodename
                arrNodes(1,y) = objNode.Text
                y = y + 1
            Next
            GetChildNodes = arrNodes

        End Function

        Public Function GetNode(sXpath)
            on error resume next
            'Note: requires GetElements to be run first
            '    : preffered to get to external object as well.
            '    : I.E. set objNode = GetNode("//Nodename")

            if not xmlelements then
                GetElements
            end if
            set xmlcurrentnode = xmlElements.selectSingleNode(sXPath)
            Set GetNode = xmlcurrentnode

        end function

        Public Function GetNodeValue(objNode, blnCData)    ' As String
            on error resume next
            if not xmlelements then
                GetElements
            end if

            set xmlcurrentnode = xmlElements.selectSingleNode(sXPath)

            If Not xmlcurrentnode Is Nothing Then
              If blnCData Then
                If xmlcurrentnode.childNodes.length > 0 Then
                  GetNodeValue = CStr(xmlcurrentnode.childNodes(0).nodeTypedValue)
                End If
              Else
                GetNodeValue = CStr(xmlcurrentnode.Text)
              End If
            End If
        End Function

        Public Function SetNodeValue(strNodevalue, objNode, blnCData)    ' As String
            on error resume next
            If Not objNode Is Nothing Then
              If blnCData Then
                If objNode.childNodes.length > 0 Then
                  objNode.childNodes(0).nodeTypedValue = sValue
                Else
                  objNode.appendChild objNode.ownerDocument.createCDATASection(strNodevalue)
                End If
              Else
                objNode.Text = strNodevalue
              End If
            End If
        End Function
    '=========================================================================================================

        sub CreateXmlProcess (strProcessInstruction)
            dim xmlProcessInstruction
            Set xmlProcessInstruction = xmlDom.createProcessingInstruction(strProcessInstruction)
            xmlDom.appendChild(xmlProcessInstruction)
        end sub

        sub CreateXSlProcess (strProcessInstruction)
            dim xmlProcessInstruction
            Set xmlProcessInstruction = xmlDom.createProcessingInstruction("xml-stylesheet", "type=""text/xsl"" href="""& strProcessInstruction & """")
            xmlDom.appendChild(xmlProcessInstruction)
        end sub

        Public Sub StartInsertAt(objParentNode)
            Set xmlcurrentnode = objParentNode
        End Sub

        function LoadXML(strxml)
            xmldom.loadXML(strxml)
        end function

        public function XMLLoader(strXML)
            xmldom.load(strXML)
        end function

        Public Function validateXML(strXML)

            Dim sReturn
            xmldom.async = 0
            xmldom.resolveExternals = 1
            xmldom.validateOnParse = 1
            If xmldom.load(strXML) Then
            Else
                If xmldom.parseError.errorCode <> 0 Then
                    strReturn = xmldom.parseError.reason & vbcrlf & _
                        xmldom.parseError.line & vbcrlf & _
                        xmldom.parseError.linepos & vbcrlf & _
                        xmldom.parseError.srcText
                End If
            End IF
            ValidateXML = strReturn

        End Function

        Public Function transformXML1(strXML,strXSL)

            Dim xslDoc
            Set xslDoc = New MSXML2.DOMDocument40

            If xmlDom.parseError.errorCode = 0 Then
                xslDom.Load strXSL
                If xslDoc.parseError.reason = "" Then
                    return = xmlDom.transformNode(xslDoc)
                Else
                    return = "Error: Stylesheet.xsl did not load. " & _
                    xslDoc.parseError.reason
                End If
            Else
                return = "Error: XML did not load. " & _
                xmlDoc.parseError.reason
            End If
            transformXML1 = return
            Set xslDoc = Nothing

        End Function

        function TransformXML
            dim objStylesheet
            set objStylesheet = CreateObject("Microsoft.XMLDOM")
            objStylesheet.async = False
            objStylesheet.loadXML ("<?xml version=""1.0"" encoding=""UTF-8""?>" & vbcrlf & _
                            "<xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform"">" & vbcrlf & _
                            "<xsl:output method=""xml"" indent=""yes"" encoding=""UTF-8"" />" & vbcrlf & _
                            "<xsl:template match=""@* | node()"">" & vbcrlf & _
                            "   <xsl:copy>" & vbcrlf & _
                            "        <xsl:apply-templates select=""@* | node()"" />" & vbcrlf & _
                            "   </xsl:copy>" & vbcrlf & _
                            "</xsl:template>" & vbcrlf & _
                            "</xsl:stylesheet>")
            'objStyleSheet.Save "c:\test.xsl"
            TransformXML = xmlDOM.transformNode(objStylesheet)
        end function

    '=========================================================================================================

        sub close()
            xmldom.LoadXML TransformXML
            xmldom.save(xmlfilename)
        end sub
    end class