Table of Contents

27.2 XmlWriter Class

The class XmlWriter allows you to write the content of an XML file. As soon as you open an XML file for writing, you have to write each node one after the other - from the first node to the last one in the required depth. The class XmlWriter is less flexible than the class XmlDocument according to the' Document Object Model' (DOM). However, you can use the class XmlWriter to write the content of a (new) XML file quickly and easily.

The XML lines can be written to a file immediately or stored temporarily in memory so that their contents can be used as a string. You can create an object of the class:

DIM hXMLWriter AS XmlWriter
hXMLWriter = NEW XmlWriter

27.2.0 Properties

The class XmlWriter has these properties, among others:

PropertyData typeDescription
DTD _XmlWriterDTDReturns the content of an existing (internal) document type definition section (DTD).
Data StringReturns the content of the XML stream as a string.

Table 27.2.0.1: Properties of the XmlWriter class

27.2.1 Methods

The class XmlWriter only has these methods:

MethodReturn typeDescription
Open ([ fileName As String, Indent As Boolean, Encoding As String])-Open an XML stream for writing. You must call the Open() method before all other XmlWriter methods. fileName: If you want to write directly to an XML file, fileName must be a valid file path. Set fileName to zero to get the content as a string after calling the EndDocument method (if the entire document was written). Indent: If the parameter is set to True, the file will be formatted with indentations. Encoding: By default, XmlWriter uses UTF-8 encoding to write the file. You can specify any other encoding if it is supported by the libxml2 library.
Close ()StringClose the XML stream.
Comment (sComment As String)-Insert a comment into the XML stream. The syntax corresponds to that of HTML comments.
PI (Target As String, Content As String)-Inserts process information into the XML stream. You should only do this if you are sure that the XML parser can implement it.
StartElement (TagName As String, Attributes As String[], Prefix As String, URI As String])-Starts an XML node named' TagName'. If this node contains attributes, you can insert these attributes into a string array. Each attribute must be a name-value pair, with the first string being the attribute name and the second the attribute value. Prefix is the namespace prefix and URI of the namespace URI of this node.
Element (TagName As String[, Value As String, Prefix As String, URI As String])-Inserts an XML element into the XML stream. The three parameters Value, Prefix and URI of type String are optional. TagName: Name of the element - Value: Value of the element - Prefix: Prefix for the NameSpace - URI: Specification for a link.
EndElement ()-Closes the current element with </element_name>.
Text (sText As String)-Inserts sText in an element.
CDATA (Data As String)-Inserts a CDATA section with the content' Data'.
Attribute (Name As String, Value As String[, Prefix As String, URI As String]) -Inserts attributes. The Prefix and URI parameters are optional.
Flush ()-Deletes all elements.
EndDocument ()StringRenders the XML document and returns the complete, well-formed XML document as a string.

Table 27.2.1.1: Methods of the class XmlWriter

You can define attributes in this way:

hXMLElement.SetAttribute("src", Application.Path &/ "images/augentrost.jpg\" width=\"255\" height=\"148\" old=\"Eyebright")

In my opinion, the following multiline variant is easier to read because you don't have to mask certain characters:

hXMLElement.SetAttribute("src", Application.Path &/ "images/augentrost.jpg")
hXMLElement.SetAttribute("width", "255")
hXMLElement.SetAttribute("height", "148")
hXMLElement.SetAttribute("old", "Eyebright")

CDATA areas are protected data areas in which there are no restrictions on the characters they contain. The CDATA areas always begin with the string <![CDATA[ and end with]>. In between each character is valid. Example of a valid CDATA area:

<![CDATA[hXMLElement.SetAttribute("height", "148")]]>

27.2.2 Digression: Bash script

A smart way to write the content of a CSV file to an XML file is to use a bash script developed by the author, to which you pass the path to the CSV file, the separator character (field separator) and the escape character as parameters. This is the source code of csv2xml. sh:

#!/bin/bash
 
  if [ $# -ne 3 ] # If the number of parameters (value in $#) is not equal to 3 ...
  then
    # COLOUR VALUES:
    RO="\033[31m"    # red
    NO="\033[0m"     # normal
    # echo -e "${RO}Syntax: $0 CSV-File-Path Field separator Escape character"
    echo -e "${RO}Syntax: $0 'CSV-File-Path' 'Field separator' 'Escape character'"
    exit 1
  fi
 
  # Saving the variable IFS in the variable backIFS (IFS = Internal Field Separator)
  backIFS=$IFS
 
  file_in=$1 # Parameter 1
  echo $file_in
  # Create file name for file_out (XML file)
  filebasename=${file_in%.*}
  fileextension=".xml"
  file_out=$filebasename$fileextension
  echo $file_out
 
  separator=$2 # Parameter 2
  escape=$3    # Parameter 3
 
  # 1. Read line (= list of field names) from CSV file
  read header_line < "$file_in"
  IFS=$separator
  # echo "IFS = $IFS"
  # Save the list of field names in the array 'header'. -> Operator =()
  header=($header_line)
  # echo "Number of elements in the array 'header' : "${#header[@]}
  # Output all elements in the 'header' array
  # echo "Elemente im Array 'header': "${header[*]}
  IFS='*'
  # Read in the content of the CSV file line by line and save it in the array 'content'.
  i=0
  while read content[$i]
  do
    # echo "Line $i: ${content[i]}"
    ((i++))
  done < $1
  # 1. Delete line in array 'content
  unset content[0]
  # copy array - but without the now empty first element
  content=(${content[*]})
  # Output all elements in the 'content' array
  # for record in ${content[@]}
  # do
  #   echo "LINE : "$record
  # done
  # Writing the content of the XML file
  #-------------------------------------------------------------------------------
  # XML-Prolog
  echo '<?xml version="1.0" encoding="UTF-8"?>' > $file_out
  # XML-Root-Element
  echo '<Data>' >> $file_out
  # XML-Elemente
  #..............................................................
  for record in "${content[@]}"
  do
  echo '  <Record>' >> $file_out
    index=0
    #............................................................
    IFS=$separator list=($record)
    # echo "Number of all elements in the array 'list' = "${#list[@]}
    # Output all elements in the 'list' array
    # echo ${list[*]}
    for (( c=0; c<=${#header[@]}-1; c++ ))
    do
      tag=${header[$c]#$escape}
      tag=${tag%$escape}
      value=${list[$c]#$escape}
      value=${value%$escape}
      echo '    <'${tag}'>'${value}'</'${tag}'>' >> $file_out
    done
    #............................................................
    echo '  </Record>' >> $file_out
    ((index++))
  done
  #-------------------------------------------------------------------------------
   echo '</Data>' >> $file_out
  IFS=$backIFS
  # sleep 2s
  # cat $file_out
  exit 0

Calling the executable bash script is simple and returns db. xml as an XML file:

$ ./csv2xml.sh "db.csv" "," "\""
db.csv
db.xml

27.2.3 Projects

All presented projects demonstrate different approaches and data sources to export data into an XML file.

27.2.3.1 Project 1

Project 1 (data2xml_writer) shows you how to rewrite an XML document using the class XmlWriter. The data to be exported is made available within the program in a structure (data type Struct). Among other things, this data type saves the advantage of addressing the individual data by name instead of using an anonymous index:

Daten
Figure 27.2.3.1.1: Data type Struct (1st data set)

XML
Figure 27.2.3.1.2: Section XML

Only the most important procedure WriteXML () is introduced and commented:

[1] Private Sub WriteXML()
[2]
[3]   Dim i As Integer
[4]
[5]   hXmlWriter = New XmlWriter
[6]   hXmlWriter.Open(Zero, True, "UTF-8")
[7] ' hXmlWriter.Open(xmlFilePath, True, "UTF-8")
[8]
[9]   hXmlWriter.PI(File.Name(xmlFilePath), "| version=\"" & Format(Now, "dd.mm.yyyy-hh:nn") & "\"")
[10]
[11]   hXmlWriter.StartElement("contacts")
[12]   hXmlWriter.Comment("Data-Base: Data-Array aDataSet (DataSet[])")
[13]
[14]   For i = 0 To aDataSet.Max
[15]     hXmlWriter.StartElement("contact")
[16]       hXmlWriter.Element("firstname", aDataSet[i].First name)
[17]       hXmlWriter.Element("surname", aDataSet[i].Surname)
[18]       hXmlWriter.StartElement("address")
[19]       hXmlWriter.Element("street", aDataSet[i].Street)
[20]       hXmlWriter.StartElement("residence", ["post code", aDataSet[i].POST CODE, "location", aDataSet[i].Location])
[21]     ' hXmlWriter.StartElement("residence")
[22]     ' hXmlWriter.Attribute("postcode (plz)", aDataSet[i].POST CODE (PLZ))
[23]     ' hXmlWriter.Attribute("location", aDataSet[i].Ort) ' 3-line alternative
[24]       hXmlWriter.EndElement ' residence
[25]       hXmlWriter.EndElement ' address
[26]       hXmlWriter.StartElement("birthdate")
[27]         hXmlWriter.Element("day", Day(aDataSet[i].BirthDate))
[28]         hXmlWriter.Element("month", Month(aDataSet[i].BirthDate))
[29]         hXmlWriter.Element("year", Year(aDataSet[i].BirthDate))
[30]       hXmlWriter.EndElement ' birthdate
[31]       hXmlWriter.StartElement("communication")
[32]         hXmlWriter.Element("landline", aDataSet[i].TLandline)
[33]         hXmlWriter.Element("mobile", aDataSet[i].TMobile)
[34]         hXmlWriter.StartElement("internet")
[35]           hXmlWriter.Element("email", aDataSet[i].EMail)
[36]           hXmlWriter.Element("web", aDataSet[i].Web)
[37]         hXmlWriter.EndElement ' internet
[38]       hXmlWriter.EndElement ' communication
[39]     hXmlWriter.EndElement ' contact
[40]   Next
[41]
[42]   hXmlWriter.EndElement ' contacts
[43]   hXmlWriter.EndDocument
[44]
[45]   txaXML.Text = hXmlWriter.Data
[46]   File.Save(xmlFilePath, hXmlWriter.Data)
[47]
[48] End

Comment:

27.2.3.2 Project 2

In project 2 (dbdata2xml_writer) data from a (SQLite3) database table is read out via an (internal) filter and then displayed. The selected data sets are exported to an XML file using the class XmlWriter.


Figure 27.2.3.2.1: Display of the selected data sets


Figure 27.2.3.2.2: Section XML

You will be introduced to the complete source code and briefly commented on:

[1] ' Gambas class file
[2]
[3] Private rDBResult As Result
[4] Private xmlDoc As XmlWriter
[5]
[6] Public Sub Form_Open()
[7]   FMain.Center
[8]   FMain.Resizable = False
[9]   FMain.Caption = ("Export: DB.Data > XML-File")
[10]
[11]   DataSource1.Connection = New Connection
[12]   DataSource1.Connection.Type = "sqlite3"
[13]   DataSource1.Connection.Host = Application.Path &/ "DataBase"
[14]   DataSource1.Connection.Name = "contacts.sqlite" ' Name of the SQLite3 database
[15]
[16]   If Not Exist(DataSource1.Connection.Host &/ DataSource1.Connection.Name) Then
[17]      Message(("<font color='red'>(No SQLite DB available.)</font><hr>The program is terminated!"))
[18]      Quit
[19]   Endif
[20]
[21]   DataSource1.Table = "contacts" ' Name of the DB
[22] ' DataSource1.Filter = "residence = \"Leipzig\"  Or residence = \"Hamburg\"" ' Filter-Example
[23]
[24]   DataBrowser1.Orientation = Align.Bottom
[25]   DataBrowser1.Grid = True
[26]   DataBrowser1.Columns = ["firstname", "surname", "residence", "postcode (plz)", "street", "birthdate", "tlandline", "tmobile", "email", "web"]
[27]   DataBrowser1.Labels = ["Firstname", "Surname", "Residence", "Postcode (Plz)", "Street", "BirthDate", "tLandline-number", "tmobile-number", "EMail-Address", "URL"]
[28]
[29]   DataBrowser1.View.Columns[0].Width = 95     ' Firstname
[30]   DataBrowser1.View.Columns[1].Width = 110    ' Surname
[31]   DataBrowser1.View.Columns[2].Width = 130    ' Residence
[32]   DataBrowser1.View.Columns[3].Width = 80     ' Postcode (Plz)
[33]   DataBrowser1.View.Columns[4].Width = 150    ' Street
[34]   DataBrowser1.View.Columns[5].Width = 140    ' BirthDate
[35]   DataBrowser1.View.Columns[6].Width = 140    ' Landline
[36]   DataBrowser1.View.Columns[7].Width = 140    ' Mobile
[37]   DataBrowser1.View.Columns[8].Width = 160    ' EMail-Address
[38]   DataBrowser1.View.Columns[9].Width = 160    ' Web-Address
[39]
[40]   DataSource1.MoveFirst ' Internal DB pointer to the first record
[41]   DataBrowser1.View.MoveTo(0, 0) ' Select the 1st line in the DataBrowser
[42]
[43]   dcFirstName.Field = "firstname" ' DB-Field (gb.db.form)
[44]   dcLastName.Field = "surname
[45]   dcPLZ.Field = "plz"
[46]   dcResidence.Field = "residence"
[47]   dcStreet.Field = "street"
[48]   dcBedDate.Field = "birtdate"
[49]   dcPhoneLandline.Field = "tlandline"
[50]   dcPhoneMobile.Field = "tmobile"
[51]   dcEMail.Field = "email"
[52]   dcWeb.Field = "web"
[53] End
[54]
[55] Private Sub Export2XML()
[56]
[57]   Dim sSQL As String
[58]   Dim xmlFilePath As String = Application.Path &/ "db2xml.xml"
[59]
[60]   sSQL = "SELECT * FROM " & DataSource1.Table
[61] ' sSQL = "SELECT * FROM " & DataSource1.Table & " WHERE " & DataSource1.Filter
[62]
[63]   Try rDBResult = DataSource1.Connection.Exec(sSQL)
[64]
[65]   If Error Then
[66]      Message("Error!<br />" & Error.Text & "<br />" & Error.Where)
[67]      Return
[68]   Endif
[69]
[70]   If rDBResult.Count = 0 Then
[71]      Message(("The number of selected records is zero!"))
[72]      Return
[73]   Endif
[74]
[75]   xmlDoc = New XmlWriter
[76]   xmlDoc.Open(xmlFilePath, True, "UTF-8")
[77]
[78]   xmlDoc.PI(File.Name(xmlFilePath), "<PIPE> version=\"" & Format(Now, "dd.mm.yyyy-hh:nn") & "\"")
[79]   xmlDoc.Comment(("Data-base: SQLite3 database 'contacts.sqlite'"))
[80]   xmlDoc.StartElement("contacts")
[81]   For Each rDBResult
[82]       xmlDoc.StartElement("contact")
[83]       xmlDoc.Element("firstname", rDBResult["firstname"])
[84]       xmlDoc.Element("surname", rDBResult["surname"])
[85]       xmlDoc.StartElement("address")
[86]         xmlDoc.Element("street", rDBResult["street"])
[87]         xmlDoc.StartElement("residence", ["plz", rDBResult["plz"], "location", rDBResult["residence"]])
[88]       ' xmlDoc.StartElement("residence")
[89]       ' xmlDoc.Attribute("plz", rDBResult["plz"])
[90]       ' xmlDoc.Attribute("location", rDBResult["residence"]) ' 3-line alternative
[91]         xmlDoc.EndElement ' residence
[92]       xmlDoc.EndElement ' address
[93]       xmlDoc.StartElement("birthday")
[94]         xmlDoc.Element("day", Day(rDBResult!birthdate))
[95]         xmlDoc.Element("month", Month(rDBResult!birthdate))
[96]         xmlDoc.Element("year", Year(rDBResult!birthdate))
[97]       xmlDoc.EndElement ' birthday
[98]       xmlDoc.StartElement("communication")
[99]         xmlDoc.Element("landline", rDBResult["tlandline"])
[100]         xmlDoc.Element("mobile", rDBResult["tmobile"])
[101]         xmlDoc.StartElement("internet")
[102]           xmlDoc.Element("email", rDBResult["email"])
[103]           xmlDoc.Element("web", rDBResult["web"])
[104]         xmlDoc.EndElement ' internet
[105]       xmlDoc.EndElement ' communication
[106]     xmlDoc.EndElement ' contact
[107]   Next
[108]
[109]   xmlDoc.EndElement ' contacts
[110]   xmlDoc.EndDocument()
[111]
[112] End
[113]
[114] Public Sub btnDBData2XML_Click()
[115]   Export2XML()
[116]   Wait
[117]   FShowData.ShowModal()
[118] End
[119]
[120] Public Sub btnEnde_Click()
[121]   FMain.Close()
[122] End

Comment:

[1] ' Gambas class file
[2]
[3] Public Sub Form_Open()
[4]   FShowData.Resizable = True
[5]   FShowData.Caption = ("XMLData")
[6]   If Exist("db2xml.xml") Then txaXMLData.Text = File.Load("db2xml.xml")
[7]   txaXMLData.Pos = 0
[8] End

Note: Before you convert project 2, you should check whether the database management used provides a data export in XML format.

27.2.3.3.3 Project 3

In the third project, data is exported from a CSV file into an XML file using the classes XmlWriter and CsvFile (gb. utils). The class CsvFile (gb. util) can be used to read a CSV file and decode the content. Please note that the class CsvFile cannot handle all CSV formats. Therefore, the used CSV file is prepared in such a way that it complies with the rules for the format of CSV files from Jon Avrach - found on the website https://www.thoughtspot.com/blog/6-rules-creating-valid-csv-files? chapter 6.

Original
Figure 27.2.3.3.3.1: Original CSV file

Comment:

Prepare
Figure 27.2.3.3.3.2: Contents of a prepared CSV file

Only then is the data from the prepared CSV file - which is temporarily stored in a CSV file - exported to an XML file:

XML
Figure 27.2.3.3.3.3: Content of the XML file

The complete source code is displayed. Interesting are the function Prepare (fsFilePath As String) As String and the procedure WriteXML ():

[1] ' Gambas class file
[2]
[3] Private $sSeparator As String
[4] Private $sEscape As String
[5]
[6] Public hXmlWriter As XmlWriter
[7] Public sFilePath As String
[8] Public hCSVFile As CsvFile
[9]
[10] Public Sub Form_Open()
[11]   FMain.Center
[12]   FMain.Resizable = True
[13]   FMain.Caption = "CSV2XML | Gambas-Version: " & System.FullVersion
[14]
[15]   $sSeparator = ","
[16]   $sEscape = "\"" ' Alternative: Chr(34) ~> "
[17]
[18]   btnConvertAndSave.Enabled = False
[19]   btnCSVPrepare.Enabled = False
[20]
[21] End
[22]
[23] Public Sub btnCSVOpen_Click()
[24]   sFilePath = FileOpen()
[25]   If sFilePath Then
[26]      txaData.Text = File.Load(sFilePath)
[27]      btnCSVPrepare.Enabled = True
[28]      btnCSVOpen.Enabled = False
[29]   Else
[30]      Return
[31]   Endif
[32] End
[33]
[34] Public Sub btnCSVPrepare_Click()
[35]   If sFilePath Then
[36]      txaData.Text = Prepare(sFilePath)
[37]      txaData.Pos = 0
[38]      File.Save(File.Dir(sFilePath) &/ File.BaseName(sFilePath) & "_pre" & ".csv", Prepare(sFilePath))
[39]      Wait
[40]      btnCSVOpen.Enabled = False
[41]      btnConvertAndSave.Enabled = True
[42]      btnCSVPrepare.Enabled = False
[43]   Else
[44]     Return
[45]   Endif
[46] End
[47]
[48] Public Sub btnConvertAndSave_Click()
[49]   WriteXML()
[50]   RemovePrepareFile()
[51]   btnCSVOpen.Enabled = True
[52]   btnConvertAndSave.Enabled = False
[53] End
[54]
[55] Public Sub Form_Close()
[56]   RemovePrepareFile()
[57] End
[58]
[59] Public Sub btnClose_Click()
[60]   FMain.Close()
[61] End
[62]
[63] Private Function Prepare(fsFilePath As String) As String
[64]
[65]   Dim sCSVContent, sCSVChanged, sLine, sField, sNewLine As String
[66]
[67]   sCSVContent = File.Load(fsFilePath)
[68] ' Parser
[69]   For Each sLine In Split(sCSVContent, gb.NewLine, $sEscape, False, True)
[70]     sLine = Trim(sLine)
[71]     If Not sLine Then Continue ' Blank lines are ignored
[72]     sNewLine = ""
[73]     For Each sField In Split(sLine, $sSeparator, $sEscape, False, True)
[74]       If sField Not Begins $sEscape And If sField Not Ends $sEscape Then
[75]          sField = $sEscape & sField & $sEscape
[76]       Endif
[77]       If sField = $sEscape & $sEscape Or If sField = $sEscape & " " & $sEscape Then
[78]          sField = "N"
[79]       Endif
[80]       sNewLine &= sField & $sSeparator
[81]     Next
[82]      If sNewLine Ends $sSeparator Then sNewLine = Left(sNewLine, -1)
[83]      sCSVChanged &= sNewLine & gb.NewLine
[84]   Next
[85]   sCSVChanged = Left(sCSVChanged, -1) ' The end-of-line character of the *last* line is removed!
[86]
[87]   Return sCSVChanged
[88]
[89] End
[90]
[91] Private Sub WriteXML()
[92]
[93]   Dim k As Integer
[94]   Dim cLine As Collection
[95]   Dim sFieldName, sElementValue As String
[96]
[97]   hCSVFile = New CsvFile(sFilePath, $sSeparator, $sEscape)
[98]
[99]   hXmlWriter = New XmlWriter
[100]   hXmlWriter.Open(Zero, True, "UTF-8")
[101]
[102]   hXmlWriter.PI("Document", "version=\"" & Format(Now, "dd.mm.yyyy-hh:nn") & "\"")
[103]
[104]   hXmlWriter.StartElement("root")
[105]     hXmlWriter.Comment("DataBase: " & File.Name(sFilePath))
[106]     While Not hCSVFile.Eof
[107]       hXmlWriter.StartElement("item")
[108]       cLine = hCSVFile.Read()
[109]       k = 0
[110]       For Each sFieldName In hCSVFile.Fields
[111]         If sFieldName Begins "#" Then
[112]            If cLine[sFieldName] = Zero Then
[113]               sElementValue = "ZERO"
[114]               hXmlWriter.Element("field_" & CStr(k), sElementValue)
[115]            Else
[116]               hXmlWriter.Element("field_" & CStr(k), cLine[sFieldName])
[117]            Endif
[118]         Else
[119]            If cLine[sFieldName] Then
[120]               hXmlWriter.Element(sFieldName, cLine[sFieldName])
[121]            Else
[122]               hXmlWriter.Element(sFieldName, "ZERO")
[123]            Endif
[124]         Endif
[125]         Inc k
[126]       Next
[127]       hXmlWriter.EndElement
[128]     Wend
[129]     hXmlWriter.EndElement ' root
[130]   hXmlWriter.EndDocument
[131]
[132]   txaData.Text = hXmlWriter.Data
[133]   txaData.Pos = 0
[134]
[135]   File.Save(File.Dir(sFilePath) &/ File.BaseName(sFilePath) & ".xml", hXmlWriter.Data)
[136]
[137] End
[138]
[139] Private Function FileOpen() As String
[140]   Dialog.Title = "Import a csv-File!"
[141]   Dialog.Filter = ["*.csv", "csv-Files"]
[142]   Dialog.Path = Application.Path &/ "CSV"
[143]   If Dialog.OpenFile(False) = True Then ' Multiselect=False (Standard)
[144]      Message.Info("The opening of the csv-File was cancelled!")
[145]      Return
[146]   Else
[147]      Return Dialog.Path
[148]   Endif
[149] End
[150]
[151] Private Sub RemovePrepareFile()
[152]   If Exist(File.Dir(sFilePath) &/ File.BaseName(sFilePath) & "_pre.csv") Then
[153]      Try Kill File.Dir(sFilePath) &/ File.BaseName(sFilePath) & "_pre.csv"
[154]   Endif
[155]   Wait
[156] End

Download