Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

XML - Build, parse XML


  • Please log in to reply
45 replies to this topic
Sidola
  • Members
  • 76 posts
  • Last active: Apr 07 2015 08:34 AM
  • Joined: 20 Jun 2012

Thanks, that got me back on track again. I had no idea I could use the parameters and functions from MSDN site directly in the AHK code.

 

It helped a bit knowing that. :p



maestrith
  • Members
  • 786 posts
  • Last active: Apr 11 2018 02:18 PM
  • Joined: 17 Sep 2005

Cool stuff :) glad to see some of my code helping to create something useful :)


Please try out my scripts: AHK Studio Basic GUI Creator Menu Creator Donations


grayatrox
  • Members
  • 54 posts
  • Last active:
  • Joined: 26 May 2009

I noticed that you haven't added support for reading XML documents with an embedded DTD

Simply add:

, this.setProperty("ProhibitDTD", false)
, this.doc.resolveExternals := false
, this.doc.validateOnParse := false

after:

, this.setProperty("SelectionLanguage", "XPath")

(Line 138 on the github version of your class)

 

I recommend that you add options to change these somewhere, as people may need some of these options switched to true.

 

To test this, add

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root SYSTEM "test.dtd" >

to the top of an XML document that is to be read.

 

 

 

For information on what these properties do, go to How to evaluate Xpath when DOCTYPE present in XML declaration.

 



RHCP
  • Members
  • 1228 posts
  • Last active: Apr 08 2017 06:17 PM
  • Joined: 29 May 2006

Thanks for this awesome wrapper! It's just what I need, but I still can't work out how to use it properly :( I've never used XML before and it's very very confusing - xml, not your wrapper.

 

I need to take the data out of an XML and insert it into an AHK object, which pretty much reconstructs it with all its elements.

<Catalog>
    <CUnit default="1" id="Alex">
        <Name value="Alex Lamb"/>
        <Description value="Person"/>
        <Radius value="3"/>
        <LifeStart value="40"/>
    </CUnit>
    <CUnit default="1" id="Chris">
        <Name value="Chris Brown"/>
        <Description value="Person"/>
        <Radius value="5"/>
        <LifeStart value="30"/>
	</CUnit>
    <CUnit default="1" id="Bender">
        <Name value="bender rodriguez"/>
        <Description value="Robot"/>
        <Radius value="7"/>
        <LifeStart value="60"/>
	</CUnit>
</Catalog>

So the resulting object would look something like this:

; convert this data into an object 'units'
units
1 (first index) 		
		"Name value" := "Alex Lamb"
		"Description value" := "Person"
		"LifeStart value" := 40
2 (second index)
		"Name value" := "Chris Brown"
		"Description value" := "Person"
		"LifeStart value" := 30		

But I can't work out how to iterate it.

 

 

What commands should I be looking at? 

 

Many thanks.

 



Coco
  • Members
  • 697 posts
  • Last active: Oct 31 2015 07:26 PM
  • Joined: 27 Jul 2012

@RHCP - Can you try this:

#Include <XML>

src =
(
<Catalog>
    <CUnit default="1" id="Alex">
        <Name value="Alex Lamb"/>
        <Description value="Person"/>
        <Radius value="3"/>
        <LifeStart value="40"/>
    </CUnit>
    <CUnit default="1" id="Chris">
        <Name value="Chris Brown"/>
        <Description value="Person"/>
        <Radius value="5"/>
        <LifeStart value="30"/>
	</CUnit>
    <CUnit default="1" id="Bender">
        <Name value="bender rodriguez"/>
        <Description value="Robot"/>
        <Radius value="7"/>
        <LifeStart value="60"/>
	</CUnit>
</Catalog>
)

x := new XML(src)

units := []
Loop, % (CUnit:=x.selectNodes("//CUnit")).length {
		k := CUnit.item((i:=A_Index)-1)
		e := []
		Loop, % (elem:=k.selectNodes("*")).length {
			a := elem.item(A_Index-1)
			if ((nn:=a.nodeName) = "Radius")
				continue
			e[nn . " value"] := a.getAttribute("value")
		}
	units[i] := e
}

for k, v in units
	for a, b in v
		MsgBox, % a " = " b
return


maestrith
  • Members
  • 786 posts
  • Last active: Apr 11 2018 02:18 PM
  • Joined: 17 Sep 2005
#SingleInstance,Force
catalog=
(
<Catalog>
<CUnit default="1" id="Alex">
<Name value="Alex Lamb"/>
<Description value="Person"/>
<Radius value="3"/>
<LifeStart value="40"/>
</CUnit>
<CUnit default="1" id="Chris">
<Name value="Chris Brown"/>
<Description value="Person"/>
<Radius value="5"/>
<LifeStart value="30"/>
</CUnit>
<CUnit default="1" id="Bender">
<Name value="bender rodriguez"/>
<Description value="Robot"/>
<Radius value="7"/>
<LifeStart value="60"/>
</CUnit>
</Catalog>
)
temp:=new xml(catalog)
nodes:=temp.sn("//*"),units:=[],count:=0
while,nn:=nodes.item(A_Index-1){
	if (nn.nodename="CUnit")
	count++
	easy:=xml.easy(nn)
	for a,b in easy
	units[count,nn.nodename]:=b
}
/*
	;This is how you would iterate the object (units)
	for count,info in units
	for description,value in info
	m("Count : " count,description " = " value)
*/
number=2 ;<---Change this to get a different unit number.
m(units[number].name " is the name of unit number " number,""
,units[number].description " is the description of unit number " number,""
,units[number].lifestart " is the starting life value for unit number " number,""
,units[number].radius " is the radius of unit number " number,""
,units[number].CUnit " is unit " number "'s ID")
return
m(x*){
	for a,b in x
	list.=b "`n"
	MsgBox,% list
}
class xml{
	__New(param*){
		root:=param.1,file:=param.2
		file:=file?file:root ".xml"
		temp:=ComObjCreate("MSXML2.DOMDocument"),temp.setProperty("SelectionLanguage","XPath")
		this.xml:=temp
		ifexist %file%
		temp.load(file),this.xml:=temp
		else if !RegExMatch(param.1,"<"){
			this.xml:=this.CreateElement(temp,root)
			this.file:=file
		}
		else if RegExMatch(param.1,"<")
		this.xml.loadxml(param.1)
	}
	CreateElement(doc,root){
		return doc.AppendChild(this.xml.CreateElement(root)).parentnode
	}
	add(info){
		path:=info.path,p:="/",dup:=this.ssn("//" path)?1:0
		if next:=this.ssn("//" path)?this.ssn("//" path):this.ssn("//*")
		Loop,Parse,path,/
		last:=A_LoopField,p.="/" last,next:=this.ssn(p)?this.ssn(p):next.appendchild(this.xml.CreateElement(last))
		if (info.dup&&dup)
		next:=next.parentnode.appendchild(this.xml.CreateElement(last))
		for a,b in info.att
		next.SetAttribute(a,b)
		if info.text!=""
		next.text:=info.text
		return next
	}
	find(info){
		if info.att.1&&info.text
		return m("You can only search by either the attribut or the text, not both")
		search:=info.path?"//" info.path:"//*"
		for a,b in info.att
		search.="[@" a "='" b "']"
		if info.text
		search.="[text()='" info.text "']"
		current:=this.ssn(search)
		return current
	}
	under(info){
		new:=info.under.appendchild(this.xml.createelement(info.node))
		for a,b in info.att
		new.SetAttribute(a,b)
		new.text:=info.text
		return new
	}
	ssn(node){
		return this.xml.SelectSingleNode(node)
	}
	sn(node){
		return this.xml.SelectNodes(node)
	}
	__Get(x=""){
		return this.xml.xml
	}
	transform(){
		static
		if !IsObject(xsl){
			xsl:=ComObjCreate("MSXML2.DOMDocument")
			style=
			(
			<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
			<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
			<xsl:template match="@*|node()">
			<xsl:copy>
			<xsl:apply-templates select="@*|node()"/>
			<xsl:for-each select="@*">
			<xsl:text></xsl:text>
			</xsl:for-each>
			</xsl:copy>
			</xsl:template>
			</xsl:stylesheet>
			)
			xsl.loadXML(style),style:=null
		}
		this.xml.transformNodeToObject(xsl,this.xml)
	}
	save(){
		this.xml.save(this.file)
	}
	easy(node){
		x:=node.SelectNodes("@*"),ret:=[]
		while,xx:=x.item(A_Index-1)
		ret[xx.nodename]:=xx.text
		return ret
	}
}
ssn(node,path){
	return node.SelectSingleNode(path)
}
sn(node,path){
	return node.SelectNodes(path)
}

Please try out my scripts: AHK Studio Basic GUI Creator Menu Creator Donations


RHCP
  • Members
  • 1228 posts
  • Last active: Apr 08 2017 06:17 PM
  • Joined: 29 May 2006

Thank you both very much! Those examples work perfectly and I'm starting to understand it. 

 

I really appreciate it. Cheers.



Coco
  • Members
  • 697 posts
  • Last active: Oct 31 2015 07:26 PM
  • Joined: 27 Jul 2012

I am currently using a mini version of this class (found here: https://github.com/c...oHotkey-XConfig) , which is more geared towards users who  are using XML to store settings/configuration. Node manipulation relies heavily on XPath expressions rather than DOM objects. See usage below:

#Include <XConfig> ; Class XConfig

src := "
(
<ROOT>
 <Element att='value'/>
 <Element name='Hello World'>
  <Child>Some Text</Child>
  <Child>Another Text</Child>
 </Element>
 <Element><![CDATA[This is a CDATASection]]></Element>
 <Element><!--This is a comment--></Element>
</ROOT>
)"

cfg := new XConfig(src)

; Get the value of the 'att' attribute of the first 'Element' node
MsgBox, % cfg["//Element[1]/@att"]

; Get the text of the first 'Child' element node of the second 'Element' element node
; By default if the XPath expression resolves to an element node, its text(if any) will be returned
MsgBox, % cfg["//Element[2]/Child[1]"]
; OR - this time the second 'Child' element node (notice the XPath expression resolves to a text node)
MsgBox, % cfg["//Element[2]/Child[2]/text()"]

; Change the value of the 'name' attribute of the second 'Element' element node
cfg["//Element[2]/@name"] := "New value"
; Change the text of the second 'Child' element node
cfg["//Element/Child[2]/text()"] := "The quick brown fox jumps over the lazy dog"

; Add a node (user can add the following nodeTypes = element, attribute, comment, cdtatasection, text)
; Add an element
cfg.__Add("/ROOT", "Element")

; User can specifiy an XML string - this allows the user to append or insert a document fragment
cfg.__Add("//Element[1]", "<Child att='An attribute value'>Text value</Child>")
; Add a CDATA section to last 'Element' element node
cfg.__Add("//Element[5]", "<![CDATA[Added CDATA section]]>")

/*
Returns the 'xml' property of the selected node specified by the XPath expression.
User can specify any XML Dom property as the second parameter during a __Get operation
*/
MsgBox, % cfg["//Element[1]", "xml"]


; or to add an attribute, specify an object
cfg.__Add("//Element[3]", {name:"AutoHotkey"})

MsgBox, % cfg.root.xml
return


Coco
  • Members
  • 697 posts
  • Last active: Oct 31 2015 07:26 PM
  • Joined: 27 Jul 2012

-- deleted double post --



Wade Hatler
  • Members
  • 40 posts
  • Last active: Nov 06 2014 10:46 PM
  • Joined: 28 Sep 2004

This is excellent.  Just put it to use right today.



docterry
  • Members
  • 2 posts
  • Last active: Jul 19 2014 12:24 AM
  • Joined: 20 Jan 2014

After struggling with XML in AHK for quite a while now, this script works!

 

However for the life of me, I have not been able to parse Visio VDX (XML) files. I can load the file. x.new XML(filename) loads and I can display the XML with x.viewXML(). But trying to select data with x.selectNodes("//Master") always returns a null.

 

Help!

 

I can't attach the file as it is too large (180k), but would be happy to send it to a kind soul who would be willing to help out a noobie.

 

Thanks in advance.

 

 



maestrith
  • Members
  • 786 posts
  • Last active: Apr 11 2018 02:18 PM
  • Joined: 17 Sep 2005

After struggling with XML in AHK for quite a while now, this script works!

 

However for the life of me, I have not been able to parse Visio VDX (XML) files. I can load the file. x.new XML(filename) loads and I can display the XML with x.viewXML(). But trying to select data with x.selectNodes("//Master") always returns a null.

 

Help!

 

I can't attach the file as it is too large (180k), but would be happy to send it to a kind soul who would be willing to help out a noobie.

 

Thanks in advance.

I would be willing to give it a go.  Send it to maestrith@gmail.com with the heading xml and what you would like parsed from it.


Please try out my scripts: AHK Studio Basic GUI Creator Menu Creator Donations


DigiDon
  • Members
  • 9 posts
  • Last active: Jan 10 2016 01:30 PM
  • Joined: 28 Feb 2014

Hi everyone!

 

Thank you Coco for your useful XML builder library and the hint to XConfig library.

I'm currently writing a software in AHK but was stuck by INI's limits so I had to switch to XML, from which I understood...nothing!

So after many tests and failures and thanks to your help I'm am able to put and retrive many of my program parameters.

 

But I'm still stuck for some procedures tha were not documented in your exemples and I would really need some help here as I really don't understand how XML commands are working in AHK.

I primilarly want to:

 

1. Delete everything inside a node but WITHOUT deleting the node itself and its parameters:

For now I have found a workaround of saving attributes, then deleting the node using

n := x.selectSingleNode("//Element_1")
    n.ParentNode.RemoveChild(n)

(which by the way wasn't documented so I found out this command worked with much surprised! Does this work thanks to your library or are many other commands like that already implemented in ahk? If yes how do I know which ones?)

...and then recreating the node using XConfig command, but it is not very sexy.. How could I do better?

2nd question is: Is there a delete-by-xpath function already implemented in your or Xconfig Library? If yes it wasn't documented and I couldn't figure it out by myself, so could you please explain?

 

 

2. USING multiple nodes object:

I was trying to use your command

n:=x.getChildren("//Node", "element")

I understand that I get multiple nodes inside the 'n' object but how am I supposed to deal with this new oject?

For exemple I tried to deleted all children of a node to solve 1. problem but couldn't figure out how to delete every element contained in n..

How to get the list/number of found elements and modify, select, delete (or whatever) ONE of the element it returns?

 

3. Compare two XML files to add missing elements to one of these XML (one is reference, the other is compared to it):

I am indeed trying to set up multiple language settings in my program. I decided I would use different language XML files for it. Default is english and is updated along the application. Users will be able to download/create other language XML file settings in which every text section of the english file will be translated.

Problem is: if I make an update of my software with new texts (let's say new GUIs or whatever) then new elements or nodes will be added to the XML english language file. SO far so good. BUT IF the user is using another XML language file that was previously working OK then some texts (i.e. some elements and nodes) will be missing and my program will crash.

 

My XML file is formated this way:

<ROOT>
      <Tray>
            <About>About</About>
            <Help>Help</Help>
            <Check_for_Update>Check for Update</Check_for_Update>
            <Display_MENU>Display MENU</Display_MENU>
            <Reload_Pgrm>Reload Pgrm</Reload_Pgrm>
            <Exit_Pgrm>Exit Pgrm</Exit_Pgrm>
      </Tray>
      <Menu>
            <Create_Note>Create Note</Create_Note>
                                            ...
      </Menu>
      <GUI id="1">
            <Title>Title of the GUI</Title>
      </GUI>
                      ...
</ROOT>

Guessed solution: I was thus thinking of comparing the default XML english language file to the currently-used user-selected XML language file at program startup and try to find each node and elements of the default language file (not speaking about text inside of course). If an element/node is missing then my program would automaticaly insert the element WITH TEXT to the user-defined language file. Thus every non-translated text would be inserted into the user-defined language file allowing fast further translation update AND no error would be thrown by the program AND no blank text would appear in the program (at worst english version if not yet translated).

 

My planned instructions were something like that:

loop each of default XML file elements, try to find it in the user language file by asking for the text inside the same XPath. If text is blank then it doesn't exist so I create the node and add the english text inside using XConfig library. If text is not blank then the element exists and I move to the next element (next iteration).

 

It seemed perfect but I think I will reach a wall very soon if I try to program it with my very limited xml skills.

 

Could you or someone else give me a hand here?

 

Thank you very much!! Love AHK :)



maestrith
  • Members
  • 786 posts
  • Last active: Apr 11 2018 02:18 PM
  • Joined: 17 Sep 2005

Hi everyone!

 

Thank you Coco for your useful XML builder library and the hint to XConfig library.

I'm currently writing a software in AHK but was stuck by INI's limits so I had to switch to XML, from which I understood...nothing!

So after many tests and failures and thanks to your help I'm am able to put and retrive many of my program parameters.

 

But I'm still stuck for some procedures tha were not documented in your exemples and I would really need some help here as I really don't understand how XML commands are working in AHK.

I primilarly want to:

 

1. Delete everything inside a node but WITHOUT deleting the node itself and its parameters:

For now I have found a workaround of saving attributes, then deleting the node using

n := x.selectSingleNode("//Element_1")
    n.ParentNode.RemoveChild(n)

2nd question is: Is there a delete-by-xpath function already implemented in your or Xconfig Library? If yes it wasn't documented and I couldn't figure it out by myself, so could you please explain?

 

 

2. USING multiple nodes object:

I was trying to use your command

n:=x.getChildren("//Node", "element")

3. Compare two XML files to add missing elements to one of these XML (one is reference, the other is compared to it):

I am indeed trying to set up multiple language settings in my program. I decided I would use different language XML files for it. Default is english and is updated along the application. Users will be able to download/create other language XML file settings in which every text section of the english file will be translated.

Problem is: if I make an update of my software with new texts (let's say new GUIs or whatever) then new elements or nodes will be added to the XML english language file. SO far so good. BUT IF the user is using another XML language file that was previously working OK then some texts (i.e. some elements and nodes) will be missing and my program will crash.

 

My XML file is formated this way:

<ROOT>
      <Tray>
            <About>About</About>
            <Help>Help</Help>
            <Check_for_Update>Check for Update</Check_for_Update>
            <Display_MENU>Display MENU</Display_MENU>
            <Reload_Pgrm>Reload Pgrm</Reload_Pgrm>
            <Exit_Pgrm>Exit Pgrm</Exit_Pgrm>
      </Tray>
      <Menu>
            <Create_Note>Create Note</Create_Note>
                                            ...
      </Menu>
      <GUI id="1">
            <Title>Title of the GUI</Title>
      </GUI>
                      ...
</ROOT>

1. The return value for n is a node.  with that you can do anything that you find on the internet that concerns with XML.  selectsinglenode, selectnodes, etc.

2.

while,Node:=n.item[a_index-1]{
  msgbox % node.xml
}

3. This answer is not something that I could help with in a quick message.  you would have to have 2 different xml objects open, and then read the new source and compare it to the destination.


Please try out my scripts: AHK Studio Basic GUI Creator Menu Creator Donations


DigiDon
  • Members
  • 9 posts
  • Last active: Jan 10 2016 01:30 PM
  • Joined: 28 Feb 2014

Hi maestrith, thanks for replying.

 

1. Ok that's cool, I'll try to work on it

2. Unfortunately I tried to use your command which could have been a start for my procedure but it didn't work..

I tried:

XMLLanguageFile2 := new xml(XML_Language_Path)
    n:=XMLLanguageFile2.getChildren("//Tray", "text")   ;OR n:=XMLLanguageFile2.getChildren("/ROOT", "element")
    msgbox % n.xml    -->Empty msgbox
    while,Node:=n.item[A_Index-1]{
    msgbox % Node.xml --> No msgbox at all
    }

By the way, there is this viewXML() function in Coco's library but I couldn't get it to work, even when running his examples. I can't see transofrmations before saving, a lil' annoying. I thought maybe it was because I was not running the script as admin but I tried out and it didn't work either.

 

Other functions such as

    XMLLanguageFile2.transformXML()
    XMLLanguageFile2.saveXML()

are working well yet...

 

EDIT: I tried :

    t:= XMLLanguageFile2.getElementsByTagName("*")
    while,Node:=t.item[A_Index]{
    msgbox % Node.xml
    }

and it worked! It's kindda weird some functions from Coco's library seem not to like me but I will try going further with these commands from MSDN. With that and XConfig library and some efforts I should be able to solve my problem. Keep you informed but don't hesitate to give me some tricks of yours or indications ;). Thanks :)

 

3. Yep I know and I guess it shouldn't be that difficult once I'll be able to loop through all elements (=descendants) of the root.