Jump to content

Recommended Posts

Posted (edited)

Hello,

As a quick way to get started, is there a "hello world" that shows how to…
1. open and parse an XML file
2. Search one/all elements/attributes that match a given string
3. Edit/add/remove an element
4. Save the tree into a new file
?

Thank you.

---

Edit: What am I doing wrong? I'm looping through all <trkpt> nodes, but either i'm using XPath incorrectly, or _XML_SelectSingleNode() doesn't like it:

Source XML:

<?xml version="1.0" encoding="UTF-8"?>
<gpx>
  <metadata>
    <name>Some name</name>
  </metadata>
  <trk>
    <name>Track 1</name>
    <trkseg>
      <trkpt lat="48.81782" lon="2.24906">
        <ele>123</ele>
        <time>Dummy time</time>
      </trkpt>
      <trkpt lat="48.81784" lon="2.24906">
        <ele>456</ele>
      </trkpt>
    </trkseg>
  </trk>
  <trk>
    <name>Track 2</name>
    <trkseg>
      <trkpt lat="48.81782" lon="2.24906">
        <ele>321</ele>
      </trkpt>
      <trkpt lat="48.81784" lon="2.24906">
        <ele>654</ele>
      </trkpt>
    </trkseg>
  </trk>
</gpx>

AutoIt code:

$node = '//trkpt'
;_XML_SelectNodes: Returns $oNodes_coll - Nodes collection, and set @extended = $oNodes_coll.length
$oNodesColl = _XML_SelectNodes($xml, $node)
For $x = 1 To $oNodesColl.length
    ;No way to grab nodes directly from $oNodesColl instead of calling _XML_SelectSingleNode?
    $query = $node & '[' & $x & ']'
    ConsoleWrite(StringFormat("Index : " & $x & @CRLF & "Query: %s" & @CRLF,$query))
    $oNode_Selected_SingleOne = _XML_SelectSingleNode($xml, $query)
    If not @error Then
        ConsoleWrite('Lat is ' & _XML_GetNodeAttributeValue($oNode_Selected_SingleOne, 'lat') & @CRLF)
    Else
        ConsoleWrite('Error:' & @error & @CRLF & 'EXT:' & @extended & @CRLF & XML_My_ErrorParser(@error, @extended))
    EndIf
Next
$aNodesColl = _XML_Array_GetNodesProperties($oNodesColl)
_ArrayDisplay($aNodesColl, 'XPath=' & $oNodesColl.expr)

Console:

;~ Index : 1
;~ Query: //trkpt[1]
;~ Lat is 48.81782
;~ Index : 2
;~ Query: //trkpt[2]
;~ Lat is 48.81784
;~ Index : 3
;~ Query: //trkpt[3]
;~ Error:18
;~ EXT:0
;~ @error description:
;~ $XML_ERR_NONODESMATCH=18
;~ No nodes match the XPath expression

;~ @extended description:
;~ $XML_EXT_DEFAULT=0
;~ Default - Do not return any additional information
;~ Index : 4
;~ Query: //trkpt[4]
;~ Error:18
;~ EXT:0
;~ @error description:
;~ $XML_ERR_NONODESMATCH=18
;~ No nodes match the XPath expression

;~ @extended description:
;~ $XML_EXT_DEFAULT=0
;~ Default - Do not return any additional information

ArrayDisplay:

image.thumb.png.93b6c423fb8ea89baf051239476458ef.png

--

Edit: It looks safer to first convert the collection to an array, and work with the latter:

$node = '//trkpt/*' ;Selects all the child element nodes of the bookstore element
$oNodesColl = _XML_SelectNodes($xml, $node)
$aNodesColl = _XML_Array_GetNodesProperties($oNodesColl)
Local Enum $nodeName, $nodeTypeString,$nodeValue,$nodeText,$nodeDataType,$nodeXml,$nodeAttributes
_ArrayDisplay($aNodesColl, 'XPath=' & $oNodesColl.expr)
For $x = 1 to UBound($aNodesColl, $UBOUND_ROWS)-1
    ConsoleWrite(StringFormat("%s=%s" & @CRLF,$aNodesColl[$x][$nodeName],$aNodesColl[$x][$nodeText]))
Next
Edited by littlebigman
  • 1 month later...
Posted (edited)

(I can't figure out how to scroll down below the code above, so will reply instead.)

Using that code isn't the right way to loop through each node, since it mixes everything in the array.

However, the following fails. For some reason, the first two nodes (out of four) pass muster, but it crashes on the third and fourth.

Is that because the last two have a different parent (trk>trkseg)? But then, isn't "//trkpt" supposed to grab all those nodes, regardless of where they live in the tree?

$node = '//trkpt'
$number_nodes = _XML_GetNodesCount($xml,$node)
For $x = 1 To $number_nodes
    $current = StringFormat("%s[%s]",$node,$x)
    ConsoleWrite($current & @CRLF)
    $oNode = _XML_SelectSingleNode($xml, $current)
    If not @error Then
        ;.text = grabs everything
        ;$sStrippedValue = StringStripWS($oNode.text,$STR_STRIPLEADING + $STR_STRIPTRAILING)
        $lat = _XML_GetNodeAttributeValue($oNode, 'lat')
        ConsoleWrite(StringFormat("Lat is %s" & @CRLF,$lat))
    Else
        ConsoleWrite('Error:' & @error & @CRLF & 'EXT:' & @extended & @CRLF & XML_My_ErrorParser(@error, @extended))
    EndIf
Next

Here's the output:

//trkpt[1]
Lat is 48.81782
//trkpt[2]
Lat is 48.81784
//trkpt[3]
Error:18
EXT:0
@error description:
$XML_ERR_NONODESMATCH=18
No nodes match the XPath expression

@extended description:
$XML_EXT_DEFAULT=0
Default - Do not return any additional information
//trkpt[4]
Error:18
EXT:0
@error description:
$XML_ERR_NONODESMATCH=18
No nodes match the XPath expression

@extended description:
$XML_EXT_DEFAULT=0
Default - Do not return any additional information

xidel works as expected:

c:\> xidel.exe test.track.gpx -se "//trkpt" --output-node-format xml

<trkpt lat="48.81782" lon="2.24906">
        <ele>123</ele>
        <time>Dummy time</time>
      </trkpt>
<trkpt lat="48.81784" lon="2.24906">
        <ele>456</ele>
      </trkpt>
<trkpt lat="48.81782" lon="2.24906">
        <ele>321</ele>
      </trkpt>
<trkpt lat="48.81784" lon="2.24906">
        <ele>654</ele>
      </trkpt>

---

Edit: Looks better…

$node = '//trkpt'
For $current in _XML_SelectNodes($xml, $node)
    $oNode = _XML_SelectSingleNode($current,".") ;IMPORTANT!
    ConsoleWrite(StringStripWS($oNode.text,$STR_STRIPLEADING + $STR_STRIPTRAILING) & @CRLF)
    $lat = _XML_GetNodeAttributeValue($current, 'lat')
    ConsoleWrite(StringFormat("Lat is %s" & @CRLF,$lat))
    ConsoleWrite("=============" & @CRLF)
Next

 

Edited by littlebigman
  • 3 months later...
Posted (edited)

What's the right way to add a new node to a tree?

I need to add a "name" node under /kml/Document:

$oXmlDocIn = _XML_CreateDOMDocument()
If @error Then Exit ConsoleWrite('Error:' & @error & @CRLF & 'EXT:' & @extended & @CRLF & XML_My_ErrorParser(@error, @extended))

$xml = _XML_Load($oXmlDocIn, $XML_FILE)
If @error Then Exit ConsoleWrite('Error:' & @error & @CRLF & 'EXT:' & @extended & @CRLF & XML_My_ErrorParser(@error, @extended))

Switch $szExt
    Case ".kml"
        ;MsgBox($MB_OK,"Case",".kml")
        Local $oNode = _XML_SelectSingleNode($xml, "/kml/Document/name")
        If @error Then
            MsgBox($MB_OK,"/kml/name","Not found")
            ;BAD Local $iResult = _XML_InsertChildNode($xml, "/kml/Document", "name", 0,$szFName)
            Local $iResult = _XML_InsertChildNode($xml, "/kml/Document", "name", 0,$szFName)
            ;BAD Local $iResult = _XML_InsertChildNode($xml, "/kml/Document", "name",,$szFName)
            If @error Then
                MsgBox($MB_ICONERROR, 'Failed creating node:', XML_My_ErrorParser(@error))
;~                 $XML_ERR_EMPTYCOLLECTION=21
;~                 Collections of objects was empty
            Else
                ConsoleWrite(_XML_TIDY($oXMLDocIn) & @CRLF)
            EndIf
        Else
            MsgBox($MB_OK,"/kml/name","Found")
            ;Find out to edit existing node's value
        EndIf
    Case Else
        MsgBox($MB_OK,"Case","Other")
EndSwitch

Thank you.

--

Edit: Could it be due to namespaces?

--

Edit: Yes, indeed. I found no better way than using a regex to remove namespaces from the input file.
 

;kludge
Local $sOutput = StringRegExpReplace(FileRead($XML_FILE), "(xmlns:?[^=]*=[""][^""]*[""])", "")

Local $oXmlDocIn = _XML_CreateDOMDocument(Default)
If @error Then Exit ConsoleWrite('Error:' & @error & @CRLF & 'EXT:' & @extended & @CRLF & XML_My_ErrorParser(@error, @extended))

;Local $xml = _XML_LoadXml($oXMLDocIn, $sFileRead)
;from string
_XML_LoadXml($oXmlDocIn, $sOutput)
If @error Then Exit ConsoleWrite('Error:' & @error & @CRLF & 'EXT:' & @extended & @CRLF & XML_My_ErrorParser(@error, @extended))

ConsoleWrite(_XML_TIDY($oXmlDocIn) & @CRLF)

 

Edited by littlebigman
  • 2 weeks later...
Posted (edited)

Does someone know how to inject a whole XML sequence in one go?

Local Const $LS = "<Style><color>00FF00</color><width>3</width></Style>"
;~ After insert/append:
;~ <parent>
;~   <Style>
;~     <color>00FF00</color>
;~     <width>3</width>
;~   </Style>
;~ </parent>

;BAD _XML_InsertChildNode($oXmlDocInTMP,"//Placemark",$LS,0,"blah")

;Try child DOM object
;NOTHING
Local $oXmlDocInTMP_LS = _XML_CreateDOMDocument(Default)
_XML_LoadXml($oXmlDocInTMP_LS, $LS)
_XML_InsertChildNode($oXmlDocInTMP,"//Placemark",_XML_TIDY($oXmlDocInTMP_LS, "UTF-8"),0,"blah")
ConsoleWrite(_XML_TIDY($oXmlDocInTMP, "UTF-8"))

;create then insert?

---

Edit: Getting closer… but still NOK.

Since the UDF provides no _XML_CreateNode() or _XML_CreateChild(), I used _XML_CreateChildWAttr() with a useless attribute. It works, but, half-surprisingly, since the data is part of the node's text/value/string, HTML brackets are turned into text instead of actual XML :

Local Const $LS = "<Style><LineStyle><color>FF0000FF</color><width>6</width></LineStyle></Style>"

Local $aAttributeList[1][2] = [['First', '1']] ; Just to keep _XML_CreateChildWAttr() happy

_XML_CreateChildWAttr($oXmlDocInTMP, "//Placemark", 'Style', $aAttributeList, "<color>00FF00</color><width>3</width>")
ConsoleWrite(_XML_TIDY($oXmlDocInTMP) & @CRLF)

OUTPUT:

Quote

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<kml>
...
  <Style First="1">&lt;color&gt;00FF00&lt;/color&gt;&lt;width&gt;3&lt;/width&gt;</Style>
        </Placemark>
  <Placemark>
...
 </Document>
</kml>

---

Edit: Yeah! Apparently, you need to use the COM object directly to use its append() method:

Local $oXmlDocInTMP_LS = _XML_CreateDOMDocument(Default)
_XML_LoadXml($oXmlDocInTMP_LS, "<Style><LineStyle><color>FF0000FF</color><width>6</width></LineStyle></Style>")
Local $oParent = _XML_SelectSingleNode($oXmlDocInTMP, "//Placemark[1]")
$oParent.appendChild($oXmlDocInTMP_LS.selectSingleNode("//Style"))
ConsoleWrite(_XML_TIDY($oXmlDocInTMP) & @CRLF)
_XML_SaveToFile($oXmlDocInTMP, "test.outout.kml")

 

---

Edit: For some reason, in the loop, appendChild() only adds the child to the last node

;new, sub-tree to add to main tree
Local $oXmlDocInTMP_LS = _XML_CreateDOMDocument(Default)
_XML_LoadXml($oXmlDocInTMP_LS, "<Style><LineStyle><color>FF0000FF</color><width>6</width></LineStyle></Style>")
;pointer to root node in tree
Local $oStyle = $oXmlDocInTMP_LS.selectSingleNode("//Style")

;oXmlDocInTMP = main tree
Local $oNodesColl = _XML_SelectNodes($oXmlDocInTMP, "//Placemark")
For $oNodeEnum In $oNodesColl
    ;Why is child only added to last node?
    $oNodeEnum.appendChild($oStyle)
Next

ConsoleWrite(_XML_TIDY($oXmlDocInTMP) & @CRLF)

Could it be the for/in/next loop doesn't work with elements from an XML tree?

Edited by littlebigman

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...