Jump to content

[XML UDF] appendChild() in _XML_SelectNodes() loop?


Go to solution Solved by TheXman,

Recommended Posts

Hello,

Using mLipok's XML.au3, I can't figure out how to append an XML sub-tree to each node returned by _XML_SelectNodes().

The following code isn't "sticky", in that the main tree isn't affected after exiting the loop:

;new, sub-tree to add to main tree
Local $oXmlSub = _XML_CreateDOMDocument(Default)
_XML_LoadXml($oXmlSub, "<Style><width>6</width></Style>")
Local $oStyle = $oXmlSub.selectSingleNode("//Style")

;$oXmlMain = whole tree
Local $oNodesColl = _XML_SelectNodes($oXmlMain, "//Placemark")
For $oNodeEnum In $oNodesColl
    $oNodeEnum.appendChild($oStyle)
    ConsoleWrite($oNodeEnum.xml & @CRLF) ;OK!
Next

;Changes are gone :-/
ConsoleWrite(_XML_TIDY($oXmlMain) & @CRLF)

Is there a command I should call in the MSXML COM so that appendChild() is applied to the main tree?

Thank you.

Link to comment
Share on other sites

Can you provide sample data?  The sample data should only be large enough to accurately depict the actual data.  Also, it would help if you showed what the expected result should be based on the data that you supply.

Link to comment
Share on other sites

Here goes:

FROM

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
 <Document>
  <Placemark>
   <name>some name</name>
   <ExtendedData>
    <Data name="name">
     <value>blah</value>
    </Data>
   </ExtendedData>
   <LineString>
    <coordinates>2.317561,48.815215,76.5 2.317577,48.815242,76.5</coordinates>
   </LineString>
  </Placemark>
  <Placemark>
   <name>some name</name>
   <ExtendedData>
    <Data name="name">
     <value>blah</value>
    </Data>
   </ExtendedData>
   <LineString>
    <coordinates>2.317561,48.815215,76.5 2.317577,48.815242,76.5</coordinates>
   </LineString>
  </Placemark>
 </Document>
</kml>

TO

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
 <Document>
  <Placemark>
   <name>some name</name>
   <ExtendedData>
    <Data name="name">
     <value>blah</value>
    </Data>
   </ExtendedData>
   <LineString>
    <coordinates>2.317561,48.815215,76.5 2.317577,48.815242,76.5</coordinates>
   </LineString>
   <Style>
    <width>6</width>
   </Style>
  </Placemark>
  <Placemark>
   <name>some name</name>
   <ExtendedData>
    <Data name="name">
     <value>blah</value>
    </Data>
   </ExtendedData>
   <LineString>
    <coordinates>2.317561,48.815215,76.5 2.317577,48.815242,76.5</coordinates>
   </LineString>
   <Style>
    <width>6</width>
   </Style>
  </Placemark>
 </Document>
</kml>


 

Link to comment
Share on other sites

  • Solution

I think the script below does what you have requested.  Note that KML files usually have a namespace, as you have posted in your sample data.  The script below shows how to retrieve information using a namespace and also how to add a node with the correct namespace.  If you have questions about the script, feel free to ask. 

As you will probably notice, the only XML.au3 UDF function that I used was _XML_Tidy().  The rest of the DOM processing used the MSXML objects themselves.

 

Example script:

#AutoIt3Wrapper_AU3Check_Parameters=-w 3 -w 4 -w 5 -w 6 -d

#include <Constants.au3>
#include <XML\XML.au3> ;Modify path as needed


Const $KML_FILE     = @ScriptDir & "\Sample.kml"
Const $KML_NEW_FILE = @ScriptDir & "\Sample-New.kml"


kml_create_and_append_new_node_example()

Func kml_create_and_append_new_node_example()
    Local $oComErr = ObjEvent("AutoIt.Error", "com_error_handler")
    #forceref $oComErr

    Local $oXmlDoc  = Null, _
          $oNodes   = Null, _
          $oNewNode = Null


    ;Create an XML document object
    $oXmlDoc = ObjCreate("Msxml2.DOMDocument.6.0")

    With $oXmlDoc
        ;Load XML document
        .load($KML_FILE)
        If .parseError.errorCode Then Exit MsgBox($MB_ICONERROR + $MB_TOPMOST, "XML PARSING ERROR", .parseError.reason)

        ;Set document properties
        .setProperty("SelectionLanguage"  , "XPath")
        .setProperty("SelectionNamespaces", "xmlns:a='http://www.opengis.net/kml/2.2'") ;KML Namespace

        ;Create a new element node with child element(s)
        ; <Style>
        ;   <Width>6</Width>
        ; </Style>
        $oNewNode = .createNode(1, "Style", "http://www.opengis.net/kml/2.2")
        $oNewNode.appendChild(.createNode(1, "Width", "http://www.opengis.net/kml/2.2")).text = "6"

        ConsoleWrite("New node: " & @CRLF & $oNewNode.xml & @CRLF & @CRLF)

        ;Select all Placemark nodes
        $oNodes = .selectNodes('//a:Placemark')
        If $oNodes.Length = 0 Then Return MsgBox($MB_ICONWARNING, "Warning", "No nodes found.")

        ;For each Placemark node
        For $oNode in $oNodes
            ;Append the new node
            $oNode.appendChild($oNewNode.cloneNode(True))
        Next

        ;Save the updated file
        If FileExists($KML_NEW_FILE) Then FileDelete($KML_NEW_FILE)
        FileWrite($KML_NEW_FILE, _XML_Tidy($oXmlDoc, "UTF-8"))

        ;Open XML file in default xml file viewer
        ShellExecute($KML_NEW_FILE)
    EndWith
EndFunc

Func com_error_handler($oError)
    With $oError
        ConsoleWrite(@CRLF & "COM ERROR DETECTED!" & @CRLF)
        ConsoleWrite("  Error ScriptLine....... " & .scriptline & @CRLF)
        ConsoleWrite("  Error Number........... " & StringFormat("0x%08x (%i)", .number, .number) & @CRLF)
        ConsoleWrite("  Error WinDescription... " & StringStripWS(.windescription, $STR_STRIPTRAILING) & @CRLF)
        ConsoleWrite("  Error RetCode.......... " & StringFormat("0x%08x (%i)", .retcode, .retcode) & @CRLF)
        ConsoleWrite("  Error Description...... " & StringStripWS(.description   , $STR_STRIPTRAILING) & @CRLF)
    EndWith
    Exit
EndFunc

Console output:

New node: 
<Style xmlns="http://www.opengis.net/kml/2.2"><Width>6</Width></Style>

Sample-New.kml file generated by the example script:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
	<Document>
		<Placemark>
			<name>some name</name>
			<ExtendedData>
				<Data name="name">
					<value>blah</value>
				</Data>
			</ExtendedData>
			<LineString>
				<coordinates>2.317561,48.815215,76.5 2.317577,48.815242,76.5</coordinates>
			</LineString>
			<Style>
				<Width>6</Width>
			</Style>
		</Placemark>
		<Placemark>
			<name>some name</name>
			<ExtendedData>
				<Data name="name">
					<value>blah</value>
				</Data>
			</ExtendedData>
			<LineString>
				<coordinates>2.317561,48.815215,76.5 2.317577,48.815242,76.5</coordinates>
			</LineString>
			<Style>
				<Width>6</Width>
			</Style>
		</Placemark>
	</Document>
</kml>

 

Edited by TheXman
Link to comment
Share on other sites

Thanks mucho.

I have some questions:

1. In case it has many sub-elements, is there a way to create a new node by importing data from another tree in one go like I tried to do, instead of adding sub-elements one by one?

$oNewNode = .createNode(1, "Style", "http://www.opengis.net/kml/2.2")
$oNewNode.appendChild(.createNode(1, "Width", "http://www.opengis.net/kml/2.2")).text = "6"

2. Working with MSXML through COM doesn't look much harder than through the UDF, in which case why bother, especially since it's incomplete, and development stopped in 2017?

3. I notice you did use the UDF for _XML_Tidy(), though: Is it because MSXML provides no way to tidy up?

4. Is there a book/site you would recommend besides MS's to learn to use MSXML?
 

Edited by littlebigman
Link to comment
Share on other sites

 

  1. If you are asking can you use an existing XML element (and all of its children) to append or insert somewhere else in the XML structure, then yes.  Instead of dynamically building the structure, you can just select the single node, clone it, modify it as necessary, and then append or insert it the same way as in the example.  The key is that you need to clone the node.  You can't use one object and try to append it in several places.
     
  2. I think I've mention this to you before in a previous post.  Personally, I don't use the XML UDF.  I guess for some it may make some tasks a little easier but it is missing some functionality, has a few bugs/issues (some I have pointed out years ago that still are not fixed), and for the most part, just adds a lot of unnecessary overhead to the use of the COM objects themselves.
     
  3. Obviously, _XML_Tidy() was not necessary.  I only used it to make the output more easily readable for anyone who ran the script. There are plenty of ways to pretty-print XML.  I prefer to use a much more feature-rich command line utility (xmlstarlet) to do my XML formatting.
     
  4. As far as site/documentation that will help you learn how to use and implement XML DOM methods and properties, I would start at the source, MS XML DOM Reference.  Another very good site for all things WWW, is W3Schools Online Web Tutorials and References.  In terms of XML-related information, here is the link to the main XML page.  There you can learn about XML in general, the XML DOM and how to navigate it, XPath, XSLT, and much more.  Plus, many of the tutorials allow you to interactively try and test the information you are learning.  It is a VERY good site for learning W3 stuff.

On a side note as it relates to KML styles, are you aware that you can define the styles once and reference them in your Placemarks by using a <styleUrl> tag?  Defining the styles once makes it much easier to maintain the KML files if you need or want to change a style later.  Creating duplicate <style> nodes throughout a document is a maintenance nightmare.  Here is a sample on the Google Developer's Site that shows what I mean.

Edited by TheXman
Link to comment
Share on other sites

Link to comment
Share on other sites

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
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...