Overview
As XML is widely used in most of the applications, there was a need to build this in a class that I can make it evolved from applications to applications. The class is part of Level Extreme .NET Framework but can be used separately as is. Once an instance of the class is created, its methods and properties become available for the required XML data manipulation.
The basic requirements of such a class is to be able to read and set specific node values, verify for the presence of a node, design it in a way that namespace support would be available, add attachments and obtain the ability to achieve other related data manipulation.
The XML file
For the purpose of this article, the following XML file will be used:
<NewDataSet> <xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true"> <xs:complexType> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="Temp"> <xs:complexType> <xs:sequence> <xs:element name="PrimaryKey" type="xs:string" minOccurs="0" /> <xs:element name="FirstName" type="xs:string" minOccurs="0" /> <xs:element name="LastName" type="xs:string" minOccurs="0" /> <xs:element name="Company" type="xs:string" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> </xs:element> </xs:schema> <Temp> <PrimaryKey>1</PrimaryKey> <FirstName>Michel</FirstName> <LastName>Fournier</LastName> <Company>Level Extreme Inc.</Company> </Temp> <Temp> <PrimaryKey>2</PrimaryKey> <FirstName>James</FirstName> <LastName>Bond</LastName> <Company>United Artist Inc.</Company> </Temp> <Temp> <PrimaryKey>3</PrimaryKey> <FirstName>King</FirstName> <LastName>Kong</LastName> <Company>From the Island Inc.</Company> </Temp> <Temp> <PrimaryKey>4</PrimaryKey> <FirstName>Super</FirstName> <LastName>Man</LastName> <Company>My Company Inc.</Company> </Temp> </NewDataSet>
Creating an instance of the class
In order to benefit of this class, we need to create an instance of it first. The following syntax can be used for that purpose:
Dim loXML As Framework.Framework.XML = New Framework.Framework.XML
Reading the XML in memory
In order to be able to start doing any data manipulation from our XML file, we need to read the XML content into memory. The LoadXML() method is used for that purpose. The following takes the content of the XML file from d:\Data.xml and loads it into our class:
Dim lcXML As String lcXML = LXFramework.FileToStr("d:\Data.xml") If Not loXML.LoadXml(lcXML) Then MessageBox.Show(LXFramework.cError) Exit Sub End If MessageBox.Show(loXML.cXml)
Note that I am using the framework function FileToStr() to read the file content into a variable. This function is as follow:
' FileToStr() VFP equivalent ' expC1 File name Public Function FileToStr(ByVal tcFileName As String) As String Dim loFile As IO.StreamReader Dim lcString As String Try loFile = New IO.StreamReader(tcFileName, System.Text.Encoding.Default) lcString = loFile.ReadToEnd() loFile.Close() Catch loError As Exception oApp.ErrorSetup(loError) lcString = "" End Try Return lcString End Function
Reading a node value
One of the first needs when manipulating the data from a XML file is the ability to read a node value. For that purpose, our class contains a method GetXMLNodeValue().
As we have more than one row, a proper syntax should be used in order to assure which employee row we want to read and set the node value from. By referencing the node structure with an ID, we can achieve that request:
If Not loXML.GetXMLNodeValue("//NewDataSet/Temp[1]/PrimaryKey") Then MessageBox.Show(LXFramework.cError) Exit Sub End If MessageBox.Show(loXML.cNodeInnerText)
In this call, we wanted to get the primary key of the first row. If we would want to get the first name of the 3rd row, the following can be used:
If Not loXML.GetXMLNodeValue("//NewDataSet/Temp[3]/FirstName") Then MessageBox.Show(LXFramework.cError) Exit Sub End If MessageBox.Show(loXML.cNodeInnerText)
Setting a node value
The ability to set a node value goes the same way as reading it. For that purpose, our class contains a methods SetXMLNodeValue().
A similar syntax can be used to set a node value of a specific row. To change the row #2 Company node value to "MI6 Inc.", we would do the following:
If Not loXML.SetXMLNodeValue("//NewDataSet/Temp[2]/Company", "MI6 Inc.") Then MessageBox.Show(LXFramework.cError) Exit Sub End If If Not loXML.GetXMLNodeValue("//NewDataSet/Temp[2]/Company") Then MessageBox.Show(LXFramework.cError) Exit Sub End If MessageBox.Show(loXML.cNodeInnerText)
Saving the XML to a file
Once our changes have been applied to the XML, we can then save it to a file. The following function can be used for that purpose:
' Save the new XML file LXFramework.CreateFile(loXML.cXml, "d:\DataUpdated.xml")
' Create a file ' expC1 String ' expC2 File Public Function CreateFile(ByVal tcString As String, ByVal tcFile As String) As Boolean Dim llSuccess As Boolean Dim loFile As IO.StreamWriter llSuccess = False Try loFile = New IO.StreamWriter(tcFile) loFile.WriteLine(tcString) loFile.Close() llSuccess = True Catch loError As Exception oApp.ErrorSetup(loError) End Try Return llSuccess End Function
Working with namespaces
Most of the XML files you will use contain namespaces. When present, we need to consider a referenced syntax to make sure our node is well found and processed.
Lets assume we have the following XML:
<EmployeeRecords xmlns="http://www.levelextreme.com/Management"> <Temp> <PrimaryKey>1</PrimaryKey> <FirstName>Michel</FirstName> <LastName>Fournier</LastName> <Company>Level Extreme Inc.</Company> </Temp> <Temp> <PrimaryKey>2</PrimaryKey> <FirstName>James</FirstName> <LastName>Bond</LastName> <Company>United Artist Inc.</Company> </Temp> </EmployeeRecords>
If Not loXML.GetXMLNodeValue("//EmployeeRecords/Temp[1]/PrimaryKey") Then MessageBox.Show(LXFramework.cError) Exit Sub End If
The proper syntax in this case would be:
loXML.AddNamespace("http://www.levelextreme.com/Management", "ns") If Not loXML.GetXMLNodeValue("//ns:EmployeeRecords/ns:Temp[1]/ns:PrimaryKey") Then MessageBox.Show(LXFramework.cError) Exit Sub End If
Adding an attachment
One of the uses of working with XML file is the ability to embed file attachments into it. This is great as it allows to embed one or multiple files into the actual XML. Our class includes a method AddXMLAttachment() which is used for that purpose. This method accepts the node as well as the file name for the parameters. With that, the class can include an attachment to the related node. This assumes the node already exists.
<EmployeeRecords> <Temp> <PrimaryKey>1</PrimaryKey> <FirstName>Michel</FirstName> <LastName>Fournier</LastName> <Company>Level Extreme Inc.</Company> <ExcelSpreadsheet></ExcelSpreadsheet> </Temp> </EmployeeRecords>
If Not loXML.AddXMLAttachment("//EmployeeRecords/Temp/ExcelSpreadsheet", _ "d:\Excel.xls") Then MessageBox.Show(LXFramework.cError) Exit Sub End If ' Save the new XML file LXFramework.CreateFile(loXML.cXml, "d:\DataUpdated.xml")
Note that I only show a portion of the XML as the attachment is very long.
Reading an attachment
In order to extract an attachment from a XML file, we can use the GetXMLAttachement() method. This method returns the string of the attachment. You can then save that string to a file and you should be able to work with the file in its original format.
Following our last example, to retrieve the attachment, we would do this:
If Not loXML.GetXMLAttachment("//EmployeeRecords/Temp/ExcelSpreadsheet") Then MessageBox.Show(LXFramework.cError) Exit Sub End If ' Save the attachement LXFramework.CreateFile(loXML.cAttachment, "d:\ExcelFromAttachement.xls")
Conclusion
This class should help you get started if you don't have anything yet as far as manipulating a XML content. The class also includes other methods such as the ability to add an attachment directly from a string, to verify if a node exists and for various other needs.
Of course, this class is only a beginning point for XML processing. I use it as part of the Level Extreme .NET Framework.
Here is the code of the class. It can be adjusted to avoid the use of the framework by simply removing the New() method and adjusting where needed.
Imports System.IO Imports System.XML Namespace Framework Public Class XML Public cAttachment As String = "" Public cNodeInnerText As String = "" Public oApp As Framework.App Public cXml As String = "" Public oXmlDocument As XmlDocument = New XmlDocument Public oXmlElement As XmlElement Public oXmlNameSpaceManager As XmlNamespaceManager Public oXmlNode As XmlNode Public oXmlNodeList As XmlNodeList Public cXmlNameSpaceManager As String = "" Public cXmlNameSpaceManagerPrefix As String = "" Public Sub New(ByVal toApplication As Framework.App) oApp = toApplication End Sub ' Add a tag in the XML ' expC1 Tag ' expC2 Value Public Function AddTag(ByVal tcTag As String, ByVal tcValue As String) As String Dim lcXML As String Dim lcValue As String lcXML = "" lcValue = tcValue lcValue = Trim(lcValue) lcValue = oApp.StrTran(lcValue, "&", "&") lcValue = oApp.StrTran(lcValue, "'", "'") lcValue = oApp.StrTran(lcValue, """", """) lcValue = oApp.StrTran(lcValue, "<", "<") lcValue = oApp.StrTran(lcValue, ">", ">") lcXML = "<" + tcTag + ">" + lcValue + "</" + tcTag + ">" lcXML = lcXML + oApp.cCR Return lcXML End Function ' Add an element in the XML ' expC1 Element Public Function AddElement(ByVal tcElement As String) As String Dim lcXML As String lcXML = "<" + tcElement + ">" lcXML = lcXML + oApp.cCR Return lcXML End Function ' Close an element in the XML ' expC1 Element Public Function CloseElement(ByVal tcElement As String) As String Dim lcXML As String lcXML = "</" + tcElement + ">" lcXML = lcXML + oApp.cCR Return lcXML End Function ' Convert an XML string, which is a VFP CursorToXml(), into a DataSet ' expC1 String Public Function ImportXML(ByVal tcXML As String) As DataSet Dim lcString As New StringReader(tcXML) Dim loData As New DataSet loData.ReadXml(lcString) Return loData End Function ' Load the Xml ' expC1 Xml Public Function LoadXml(ByVal tcXml As String) As Boolean Try oXmlDocument.LoadXml(tcXml) Catch loError As Exception oApp.ErrorSetup(loError) Return False End Try cXml = oXmlDocument.OuterXml Return True End Function ' Add a namespace ' expC1 Uri ' expC2 Namespace Public Function AddNamespace(ByVal tcUri As String, _ ByVal tcNamespace As String) As Boolean Try oXmlNameSpaceManager = New XmlNamespaceManager(oXmlDocument.NameTable) oXmlNameSpaceManager.AddNamespace(tcNamespace, tcUri) Catch loError As Exception oApp.ErrorSetup(loError) Return False End Try Return True End Function ' Get a XML node value ' expC1 Node Public Function GetXMLNodeValue(ByVal tcNode As String) As Boolean ' Select the node If oXmlNameSpaceManager Is Nothing Then oXmlNode = oXmlDocument.SelectSingleNode(tcNode) Else oXmlNode = oXmlDocument.SelectSingleNode(tcNode, oXmlNameSpaceManager) End If ' See if we obtained the node If oXmlNode Is Nothing Then oApp.cError = "The node " + tcNode + " does not exist." Return False End If Try cNodeInnerText = oXmlNode.InnerText Catch loError As Exception oApp.ErrorSetup(loError) Return False End Try Return True End Function ' Get a XML node list ' expC1 Node Public Function GetXMLNodeList(ByVal tcNode As String) As Boolean ' Select the node If oXmlNameSpaceManager Is Nothing Then oXmlNodeList = oXmlDocument.SelectNodes(tcNode) Else oXmlNodeList = oXmlDocument.SelectNodes(tcNode, oXmlNameSpaceManager) End If ' See if we obtained the node If oXmlNodeList Is Nothing Then oApp.cError = "The node " + tcNode + " does not exist." Return False End If Try oXmlNodeList = oXmlDocument.SelectNodes(tcNode) Catch loError As Exception oApp.ErrorSetup(loError) Return False End Try Return True End Function ' See if a node exists ' expC1 Node Public Function IsXMLNode(ByVal tcNode As String) As Boolean ' Select the node If oXmlNameSpaceManager Is Nothing Then oXmlNode = oXmlDocument.SelectSingleNode(tcNode) Else oXmlNode = oXmlDocument.SelectSingleNode(tcNode, oXmlNameSpaceManager) End If ' See if we obtained the node If oXmlNode Is Nothing Then Return False End If Return True End Function ' Add a XML attachment ' expC1 Node ' expC2 File Public Function AddXMLAttachment(ByVal tcNode As String, _ ByVal tcFile As String) As Boolean Dim lcXml As String ' Select the node If oXmlNameSpaceManager Is Nothing Then oXmlNode = oXmlDocument.SelectSingleNode(tcNode) Else oXmlNode = oXmlDocument.SelectSingleNode(tcNode, oXmlNameSpaceManager) End If ' See if we obtained the node If oXmlNode Is Nothing Then oApp.cError = "The node " + tcNode + " does not exist." Return False End If ' Add the file lcXml = oApp.FileToStr(tcFile) ' If we had an error If lcXml.Length = 0 Then Return False End If oXmlNode.InnerText = _ Convert.ToBase64String(System.Text.Encoding.Default.GetBytes(lcXml)) cXML = oXmlDocument.OuterXml Return True End Function ' Add a XML attachment from a string ' expC1 Node ' expC2 String Public Function AddXMLAttachmentFromString(ByVal tcNode As String, _ ByVal tcString As String) As Boolean Dim lcXml As String ' Select the node If oXmlNameSpaceManager Is Nothing Then oXmlNode = oXmlDocument.SelectSingleNode(tcNode) Else oXmlNode = oXmlDocument.SelectSingleNode(tcNode, oXmlNameSpaceManager) End If ' See if we obtained the node If oXmlNode Is Nothing Then oApp.cError = "The node " + tcNode + " does not exist." Return False End If ' Add the file lcXml = tcString oXmlNode.InnerText = _ Convert.ToBase64String(System.Text.Encoding.Default.GetBytes(lcXml)) cXML = oXmlDocument.OuterXml Return True End Function ' Get a XML attachment ' expC1 Node Public Function GetXMLAttachment(ByVal tcNode As String) As Boolean ' Select the node If oXmlNameSpaceManager Is Nothing Then oXmlNode = oXmlDocument.SelectSingleNode(tcNode) Else oXmlNode = oXmlDocument.SelectSingleNode(tcNode, oXmlNameSpaceManager) End If ' See if we obtained the node If oXmlNode Is Nothing Then oApp.cError = "The node " + tcNode + " does not exist." Return False End If ' Get the file cAttachment = _ System.Text.Encoding.Default.GetString(Convert.FromBase64String(oXmlNode.InnerText)) Return True End Function ' Set a XML node value ' expC1 Node ' expC2 Value Public Function SetXMLNodeValue(ByVal tcNode As String, _ ByVal tcValue As String) As Boolean ' Select the node If oXmlNameSpaceManager Is Nothing Then oXmlNode = oXmlDocument.SelectSingleNode(tcNode) Else oXmlNode = oXmlDocument.SelectSingleNode(tcNode, oXmlNameSpaceManager) End If ' See if we obtained the node If oXmlNode Is Nothing Then oApp.cError = "The node " + tcNode + " does not exist." Return False End If Try oXmlNode.InnerText = tcValue Catch loError As Exception oApp.ErrorSetup(loError) Return False End Try cXML = oXmlDocument.OuterXml Return True End Function ' Add an element ' expC1 Node ' expC2 Element Public Function AddXMLElement(ByVal tcNode As String, _ ByVal tcElement As String) As Boolean ' Create the node Try oXmlElement = oXmlDocument.CreateElement(tcElement) Catch loError As Exception oApp.ErrorSetup(loError) Return False End Try ' Select the node If oXmlNameSpaceManager Is Nothing Then oXmlNode = oXmlDocument.SelectSingleNode(tcNode) Else oXmlNode = oXmlDocument.SelectSingleNode(tcNode, oXmlNameSpaceManager) End If ' See if we obtained the node If oXmlNode Is Nothing Then oApp.cError = "The node " + tcNode + " does not exist." Return False End If ' Add the node oXmlNode.AppendChild(oXmlElement) cXML = oXmlDocument.OuterXml Return True End Function End Class End Namespace