littlebigman Posted April 16, 2023 Posted April 16, 2023 (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: -- 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 June 1, 2023 by littlebigman
littlebigman Posted June 1, 2023 Posted June 1, 2023 (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 June 1, 2023 by littlebigman
littlebigman Posted September 22, 2023 Posted September 22, 2023 (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 September 23, 2023 by littlebigman
littlebigman Posted October 5, 2023 Posted October 5, 2023 (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"><color>00FF00</color><width>3</width></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 October 5, 2023 by littlebigman
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now