XML Class

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
majstang
Posts: 90
Joined: 11 Feb 2018, 03:39

XML Class

05 Sep 2020, 15:54

Hello,

I am having some problems with the XML Class. When saving the class always appends the tree and I want it to overwrite. Tried to change this line to no avail. It still appends to the end of file leaving the old stuff in start of file.

Code: Select all

if(text!=this[])
		    file:=FileOpen(filename,"w"),file.Write(this[])
			;file:=FileOpen(filename,"rw"),file.Seek(0),file.Write(this[]),file.Length(file.Position)
Are also having some problems with Transform(), and wonder its possible to get it prettier? Its not as pretty as sXML_Pretty() produces just yet.
This is the output:

Code: Select all

<?xml version="1.0"?>
<tv>
	<programme channel="MTV" start="20200905082500 +0200" stop="20200905092500 +0200">
		<title lang="en">Ghosted: Love gone missing</title>
		<sub-title lang="en">Mutiny on the Bounty</sub-title>
		<desc lang="en">Blablabla</desc>
		<date>2020</date>
		<episode-num system="onscreen">S21E1</episode-num>
		<category lang="en">series</category>
		<star-rating system="imdb"><value>6.2/10</value></star-rating>
		<review type="url">http://www.imdb.com/title/tt2272918</review></programme>
</tv>
and this is what im looking for:

Code: Select all

<?xml version="1.0"?>
<tv>
	<programme channel="MTV" start="20200905082500 +0200" stop="20200905092500 +0200">
		<title lang="en">Ghosted: Love gone missing</title>
		<sub-title lang="en">Mutiny on the Bounty</sub-title>
		<desc lang="en">Blablabla</desc>
		<date>2020</date>
		<episode-num system="onscreen">S21E1</episode-num>
		<category lang="en">series</category>
		<star-rating system="imdb">
		   <value>6.2/10</value>
		</star-rating>
		<review type="url">http://www.imdb.com/title/tt2272918</review>
    </programme>
</tv>
I also tried to circumvent the alfanumerical sorting of the attributes, but that is not possible. Using these lines:

Code: Select all

Top := XMLDoc.Add("programme",@XmlType (propOrder={start:"20200905082500 +0200",stop:"20200905092500 +0200",channel:"MTV"}),,0)
;Top := XMLDoc.Add("programme",propOrder={start:"20200905082500 +0200",stop:"20200905092500 +0200",channel:"MTV"},,0)
;Top := XMLDoc.Add("programme",{start:"20200905082500 +0200",stop:"20200905092500 +0200",channel:"MTV"},,0) ;works but attributes are sorted alfanumerical
Last question. How do I declare Encoding type in the prolog section when saving file?

XML Class with sample code:

Code: Select all

XMLDoc:=new XML("tv", "guide.xml")
Top := XMLDoc.Add("programme",{start:"20200905082500 +0200",stop:"20200905092500 +0200",channel:"MTV"},,0)
XMLDoc.Under(Top,"title",{lang:"en"},"Ghosted: Love gone missing")
XMLDoc.Under(Top,"sub-title",{lang:"en"},"Mutiny on the Bounty")
XMLDoc.Under(Top,"desc",{lang:"en"},"Blablabla")
XMLDoc.Under(Top,"date",,"2020")
XMLDoc.Under(Top,"episode-num",{system:"onscreen"},"S21E1")
XMLDoc.Under(Top,"category",{lang:"en"},"series")
Rating := XMLDoc.Under(Top,"star-rating",{system:"imdb"})
XMLDoc.Under(Rating,"value",,"6.2/10")
XMLDoc.Under(Top,"review",{type:"url"},"http://www.imdb.com/title/tt2272918")

XMLDoc.Transform(2)
XMLDoc.Save()

Class XML{
	keep:=[]
	__Get(x=""){
		return this.XML.xml
	}__New(param*){
		;temp.preserveWhiteSpace:=1
		root:=param.1,file:=param.2,file:=file?file:root ".xml",temp:=ComObjCreate("MSXML2.DOMDocument"),temp.SetProperty("SelectionLanguage","XPath"),this.xml:=temp,this.file:=file,XML.keep[root]:=this
		if(FileExist(file)){
			FileRead,info,%file%
			if(info=""){
				this.xml:=this.CreateElement(temp,root)
				FileDelete,%file%
			}else
				temp.LoadXML(info),this.xml:=temp
		}else
			this.xml:=this.CreateElement(temp,root)
	}Add(XPath,att:="",text:="",dup:=0){
		p:="/",add:=(next:=this.SSN("//" XPath))?1:0,last:=SubStr(XPath,InStr(XPath,"/",0,0)+1)
		if(!next.xml){
			next:=this.SSN("//*")
			for a,b in StrSplit(XPath,"/")
				p.="/" b,next:=(x:=this.SSN(p))?x:next.AppendChild(this.XML.CreateElement(b))
		}if(dup&&add)
			next:=next.ParentNode.AppendChild(this.XML.CreateElement(last))
		for a,b in att
			next.SetAttribute(a,b)
		if(text!="")
			next.text:=text
		return next
	}CreateElement(doc,root){
		return doc.AppendChild(this.XML.CreateElement(root)).ParentNode
	}EA(XPath,att:=""){
		list:=[]
		if(att)
			return XPath.NodeName?SSN(XPath,"@" att).text:this.SSN(XPath "/@" att).text
		nodes:=XPath.NodeName?XPath.SelectNodes("@*"):nodes:=this.SN(XPath "/@*")
		while(nn:=nodes.item[A_Index-1])
			list[nn.NodeName]:=nn.text
		return list
	}Find(info*){
		static last:=[]
		doc:=info.1.NodeName?info.1:this.xml
		if(info.1.NodeName)
			node:=info.2,find:=info.3,return:=info.4!=""?"SelectNodes":"SelectSingleNode",search:=info.4
		else
			node:=info.1,find:=info.2,return:=info.3!=""?"SelectNodes":"SelectSingleNode",search:=info.3
		if(InStr(info.2,"descendant"))
			last.1:=info.1,last.2:=info.2,last.3:=info.3,last.4:=info.4
		if(InStr(find,"'"))
			return doc[return](node "[.=concat('" RegExReplace(find,"'","'," Chr(34) "'" Chr(34) ",'") "')]/.." (search?"/" search:""))
		else
			return doc[return](node "[.='" find "']/.." (search?"/" search:""))
	}Get(XPath,Default){
		text:=this.SSN(XPath).text
		return text?text:Default
	}Language(Language:="XSLPattern"){
		this.XML.SetProperty("SelectionLanguage",Language)
	}ReCreate(XPath,new){
		rem:=this.SSN(XPath),rem.ParentNode.RemoveChild(rem),new:=this.Add(new)
		return new
	}Save(x*){
		if(x.1=1)
			this.Transform()
		if(this.XML.SelectSingleNode("*").xml="")
			return m("Errors happened while trying to save " this.file ". Reverting to old version of the XML")
		filename:=this.file?this.file:x.1.1,ff:=FileOpen(filename,0),text:=ff.Read(ff.length),ff.Close()
		if(!this[])
			return m("Error saving the " this.file " XML.  Please get in touch with maestrith if this happens often")
		if(text!=this[])
		    file:=FileOpen(filename,"w"),file.Write(this[])
			;file:=FileOpen(filename,"rw"),file.Seek(0),file.Write(this[]),file.Length(file.Position)
	}SSN(XPath){
		return this.XML.SelectSingleNode(XPath)
	}SN(XPath){
		return this.XML.SelectNodes(XPath)
	}Transform(){
		static
		if(!IsObject(xsl))
			xsl:=ComObjCreate("MSXML2.DOMDocument"),xsl.loadXML("<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>`n<xsl:apply-templates select=""@*|node()""/><xsl:for-each select=""@*""><xsl:text></xsl:text></xsl:for-each></xsl:copy>`n</xsl:template>`n</xsl:stylesheet>"),style:=null
		this.XML.TransformNodeToObject(xsl,this.xml)
	}Under(under,node,att:="",text:="",list:=""){
		new:=under.AppendChild(this.XML.CreateElement(node)),new.text:=text
		for a,b in att
			new.SetAttribute(a,b)
		for a,b in StrSplit(list,",")
			new.SetAttribute(b,att[b])
		return new
	}
}SSN(node,XPath){
	return node.SelectSingleNode(XPath)
}SN(node,XPath){
	return node.SelectNodes(XPath)
}m(x*){
	active:=WinActive("A")
	ControlGetFocus,Focus,A
	ControlGet,hwnd,hwnd,,%Focus%,ahk_id%active%
	static list:={btn:{oc:1,ari:2,ync:3,yn:4,rc:5,ctc:6},ico:{"x":16,"?":32,"!":48,"i":64}},msg:=[],msgbox
	list.title:="XML Class",list.def:=0,list.time:=0,value:=0,msgbox:=1,txt:=""
	for a,b in x
		obj:=StrSplit(b,":"),(vv:=List[obj.1,obj.2])?(value+=vv):(list[obj.1]!="")?(List[obj.1]:=obj.2):txt.=b "`n"
	msg:={option:value+262144+(list.def?(list.def-1)*256:0),title:list.title,time:list.time,txt:txt}
	Sleep,120
	MsgBox,% msg.option,% msg.title,% msg.txt,% msg.time
	msgbox:=0
	for a,b in {OK:value?"OK":"",Yes:"YES",No:"NO",Cancel:"CANCEL",Retry:"RETRY"}
		IfMsgBox,%a%
		{
			WinActivate,ahk_id%active%
			ControlFocus,%Focus%,ahk_id%active%
			return b
		}
}
User avatar
maestrith
Posts: 825
Joined: 16 Oct 2013, 13:52

Re: XML Class

06 Sep 2020, 07:29

If you want to both store data and write a totally new XML to be written you need to have 2 XML files. The class that I wrote will just add to what you already have so you do not lose data. You can either timestamp the data you want removed and then remove it when you save, or create a second XML file for the new data and then overwrite the old file when closing the app. Send me another PM if you would like to meet up in Discord, Hangouts, Skype, or some other form of voice chat to discuss possible solutions since I am not 100% sure what you are doing with this application.
John H Wilson III 05/29/51 - 03/01/2020. You will be missed.AHK Studio OSDGUI Creator
Donations
Discord
All code is done on a 64 bit Windows 10 PC Running AutoHotkey x32
majstang
Posts: 90
Joined: 11 Feb 2018, 03:39

Re: XML Class

06 Sep 2020, 11:37

Hi maestrith,

I did find Cocos XML class to be more suited for my needs, so I will go with that. With Cocos class I nearly managed to fix all problems from post 1, but still havent found a way to use the @XmlType Annotation to Define Schema Element Ordering to prevent the alfabetical sorting of attributes. https://docs.oracle.com/cd/E19879-01/819-3669/bnbdb/index.html
To fix that one I will probably get my hands on an autohotkey converted c++ @XmlType function, which surely doesnt exist.
Maybe this can be converted to AHK and used successfully in the class?

Code: Select all

@XmlType(propOrder={"title", "releaseYear"})
public class Movie {
    public int releaseYear;
    public String title;
}
Had some peculiarities with Cocos xml class. The encoding="UTF-8" declaration in the prolog is visible if viewing XML document with x.viewXML(), but is gone after FileOpen() writing to the file. I know Im not playing by the XML rules by using the FileOpen save, but it enables me to at least save the content to a file and use the best pretty function Ive found so far...sXML_Pretty().

Cocos class with my tree building:

Code: Select all

try
	; create an XMLDOMDocument object
	; set its top-level node
	x := new xml("<tv/>")
catch pe ; catch parsing error(if any)
	MsgBox, 16, PARSE ERROR
	, % "Exception thrown!!`n`nWhat: " pe.What "`nFile: " pe.File
	. "`nLine: " pe.Line "`nMessage: " pe.Message "`nExtra: " pe.Extra

; check if top-level node exists
; in this case, 'root'
if x.documentElement {
	x.addElement("programme", "tv", {start:"20200905082500 +0200",stop:"20200905092500 +0200",channel:"MTV"})
	;Top := XMLDoc.Add("programme",@XmlType (propOrder={start:"20200905082500 +0200",stop:"20200905092500 +0200",channel:"MTV"}),,0)
    ;Top := XMLDoc.Add("programme",propOrder={start:"20200905082500 +0200",stop:"20200905092500 +0200",channel:"MTV"},,0)
	
	x.addElement("title", "//programme", {lang: "en"}, "Ghosted: Love gone missing")
	x.addElement("sub-title", "//programme", {lang: "en"}, "Mutiny on the Bounty")
	x.addElement("desc", "//programme", {lang: "en"}, "Blablabla")
	x.addElement("date", "//programme", "2020")
	x.addElement("episode-num", "//programme", {system: "onscreen"}, "S21E1")
	x.addElement("category", "//programme", {lang: "en"}, "series")
	x.addElement("star-rating", "//programme", {system: "imdb"})
	x.addElement("value", "//star-rating", "6.2/10")
	x.addElement("review", "//programme", {type: "url"}, "http://www.imdb.com/title/tt2272918")

	; transform document using internal stylesheet
	x.transformXML()
	
	; view XML document
	x.viewXML()
	
 If (File := FileOpen("C:\test\guide.xml", "w", "UTF-8")) {
   File.Write(sXML_Pretty(x.XML))
   File.Close()
 }
}
exitapp

/*
___________________________________________________________________________________
***THE FOLLOWING METHOD(s) ARE USING SIMILAR ROUTINES AND ARE CALLED DYNAMICALLY.
THEY REDIRECT TO INTERNAL METHOD(s), THUS, DOCUMENTATION IS STATED HERE***
____________________________________________________________________________________
METHOD(s): addElement/insertElement
DEFINITION: Appends or inserts an element node
PARAMETER(s):
    element -   Tag name(string or an IXMLDOMNode object)
    ps      -   If the user is calling the "addElement" method,
                this contains the parent node. Otherwise, if it's
                the "insertElement" method, this contains the
                reference node, the element will be inserted to
                the left of this node.
                Can be an XPath expression or an  IXMLDOMNode object
    prm*    -   Attributes and text. see Remarks
USAGE:
    e := xmlObj.addElement("Node", "//Parent", {name: "Element"}, "Text")
    e := xmlObj.insertElement("Node", "//Sibling", {name: "Element"}, {att: "Value"})
RETURN VALUE: An object. Returns the new element node
REMARKS:
    FOR THE 'PRM' PARAMETER:
    To assign/associate an attribute(s), specify an object with the 'key'
    as the attribute name and the 'value' as the attribute value.
    Specify multiple arrays if you want to retain order, internally
    it does a for-loop thus keys are re-arranged in the output.
	
    To add a text node, specify a string.
	
    Since this is a variadic parameter, the 'text' node must be specified last.
	
    E.G.:
    (Attribute and text)
    x.addElement("Node", "//Parent", {name: "Element"}, "Text")
    (Multiple attributes and a text node)
    x.addElement("Node", "//Parent", {name: "Element"}, {att: "value"}, "Text")
    (Text only)
    x.addElement("Node", "//Parent", "Text")
    (Attribute(s) only, note that 1 object[with multiple members] is passed)
    x.addElement("Node", "//Parent", {name: "Element", att: "Value"})
____________________________________________________________________________________
METHOD(s): addChild/insertChild
DEFINITION: Appends or inserts a new child node as the last child of the node.
PARAMETER(s):
    ps      -   If the user is calling the "addChild" method,
                this contains the parent node. Otherwise, if it's
                the "insertChild" method, this contains the
                reference node, the element will be inserted to
                the left of this node.
                can be an XPath expression or an IXMLDOMNode object
    type    -   The type of node to append.
                A value that uniquely identifies the node type.
                Specify the 'nodeType'(http://msdn.microsoft.com/en-us/library/ms753745(v=vs.85).aspx)
                or 'nodeTypeString'(http://msdn.microsoft.com/en-us/library/ms757895(v=vs.85).aspx)
                property of the node to be appended. A short-hand way is to use an
                acronym(see remarks).
                NODE TYPES: http://msdn.microsoft.com/en-us/library/ms766473(v=vs.85).aspx
    params* -   value(s) is dependent on the type of node to be appended. See remarks.
USAGE:
    xmlObj.addChild("//Node", 1, "Tag_Name") ; 'nodeType'
    xmlObj.addChild("//Parent", "element", "Tag_Name") ; 'nodeTypeString'
    xmlObj.addChild("//Parent", "e", "Tag_Name") ; Short-hand(acronym)
RETURN VALUE:   If successful this method returns an object representing the
                newly added node, otherwise it returns 'false'
REMARKS:
    =======================================================================
    NODE TYPES
    [nodeType, nodeTypeString, Short-hand]
    1       element                 'e'
    3       text                    't'
    4       cdatasection            'cds'
    5       entityreference         'er'
    6       entity                  'en'
    7       processinginstruction   'pi'
    8       comment                 'c'
    10      documenttype            'dt'
    11      documentfragment        'df'
    12      notation                'n'
    =======================================================================
    PARAMS* Value
    element             A string specifying the name for the new element node.
                        The name is case-sensitive.
    text                String specifying the value to be supplied to the new
                        text object's nodeValue property.
    cdatasection        same as 'text'
    entityreference     A string specifying the name of the entity referenced.
    entity
        params[1]       The name of the entity.
        params[2]       (Optional) The namespace URI. If specified, the node
                        is created in the context of the namespaceURI parameter
                        with the prefix specified on the node name. If the name
                        parameter does not have a prefix, this is treated as
                        the default namespace.
    processinginstruction
        params[1]       A string specifying the target part of the processing instruction.
        params[2]       A string specifying the rest of the processing instruction
                        preceding the closing ?> characters.
    comment             same as 'text'
    documenttype        same as 'entity'
    documentfragment    No parameters
    notation            same as 'entity'
    =======================================================================
____________________________________________________________________________________
*/

class xml
{
	
	/*
	METHOD: __New
	DEFINITION: Constructor for the class
	PARAMETER(s):
	    source  -  can be a string containing XML string or a 
	               filepattern specifying the location of an XML resource.
	               Omit to start off with an empty document.
	USAGE: Use the 'new' keyword
	    xmlDoc := new xml("<root><child/></root>") ; XML string
	    xmlDoc := new xml("MyFile.xml") ; XML file
	RETURN VALUE: A derived object
	REMARKS: Throws an exception
	*/
	
	__New(src:="") {
		static MSXML := "MSXML2.DOMDocument" (A_OSVersion ~= "WIN_(7|8)" ? ".6.0" : "")
		
		; Create object to store "file" property
		; this is to make sure that __Set() is always called everytime
		; there's an attempt to alter the property's value
		ObjInsert(this, "_", []) ; ByPass __Set()/__Call()?
		
		; Create IXMLDOMDocument object
		this.doc := ComObjCreate(MSXML)
		, this.setProperty("SelectionLanguage", "XPath")
		
		if src {
			if (src ~= "s)^<.*>$")  ; XML string
				this.loadXML(src)
			else if (src ~= "[^<>:""/\\|?*]+\.[^<>:""/\\|?*\s]+$") {
				if FileExist(src) ; Path/URL to XML file/resource
					this.load(src)
				this.file := ""
			} else throw Exception("The parameter '" src "' is neither an XML file "
				. " nor a string containing XML string.`n", -1, src)
			
			; Get last parsing error
			if (pe := this.parseError).errorCode {
				m := pe.url ? ["file", "load"] : ["string", "loadXML"]
				
				; throw exception
				throw Exception("Invalid XML " m.1 ".`nError Code: " pe.errorCode
				. "`nFilePos: " pe.filePos "`nLine: " pe.line "`nLinePos: " pe.linePos
				. "`nReason: " pe.reason "`nSource Text: " pe.srcText
				. "`nURL: " pe.url "`n", -1, m.2)
			}
			
			if (this.file <> false)
				this.file := src
		}
		
	}
	
	__Delete() {
		ObjRelease(Object(this.doc)) ; Is this necessary??
		OutputDebug, % "Object freed."
	}
	
	__Set(property, value) {
		if (property ~= "i)^(doc|file)$") { ; Class property
			if (property = "file") {
				if (value ~= "(^$|[^<>:""/\\|?*]+\.[^<>:""/\\|?*\s]+$)")
					return this._[property] := value
				else return false
			}
		} else { ; XML DOM property
			try return (this.doc)[property] := value
			catch
				return false
		}
	}
	
	__Get(property) {
		if !ObjHasKey(this, property) { ; Redundant??
			if (property = "file")
				return this._.HasKey(property)
					? this._[property]
					: false
			else {
				try return (this.doc)[property]
				catch
					; Allow user to select a node by providing an
					; XPath expression as key (short-hand way)
					; e.g. xmlObj["//Element/Child"] is equivalent
					; to xmlObj.selectSingleNode("//Element/Child")
					try return this.selectSingleNode(property)
			}
		}
	}
	
	__Call(method, params*) {
		static BF := "i)^(Insert|Remove|(Min|Max)Index|(Set|Get)Capacity"
			. "|GetAddress|_NewEnum|HasKey|Clone)$"
		
		if !ObjHasKey(xml, method) {
			if RegExMatch(method, "iJ)^(add|insert)((?P<_>E)lement|(?P<_>C)hild)$", m)
				return this["addInsert" m_](method, params*)
			else {
				try return (this.doc)[method](params*)
				catch e
					if !(method ~= BF)
						throw e
			}
		}
	}
	
	/*
	METHOD: rename
	DEFINITION: Renames an element node
	PARAMETER(s):
	    node    -   the element node to be renamed
	                can be an XPath expression or an IXMLDOMNode object
	    newName -   A string, the new name of the element node
	USAGE:
	    node := xmlObj.rename("//Node", "New_Name")
	RETURN VALUE:   An object
	*/
	
	rename(node, newName) {
		new := this.createElement(newName)
		, old := IsObject(node) ? node : this.selectSingleNode(node)
		
		while old.hasChildNodes()
			new.appendChild(old.firstChild)
		
		while (old.attributes.length > 0)
			new.setAttributeNode(old.removeAttributeNode(old.attributes[0]))
		
		old.parentNode.replaceChild(new, old)
		
		return new
	}
	
	/*
	METHOD: setAtt
	DEFINITION: Sets the attribute for an element node
	PARAMETER(s):
	    element -   the element node
	                can be an XPath expression or an IXMLDOMNode object
	    att     -   Attribute nodes to be associated with the element node
	                Specify an associative array with the key as the 'attribute name'
	                and the value as the 'attribute value'.
	USAGE:
	    xmlObj.setAtt("//Node", {name: "Name", age: "20"}) or
	    xmlObj.setAtt("//Node", {name: "Name"}, {age: "20"})
	RETURN VALUE: None
	*/
	
	setAtt(element, att*) {
		e := IsObject(element) ? element : this.selectSingleNode(element)
		
		for a, b in att {
			if !IsObject(b)
				continue
			for x, y in b
				e.setAttribute(x, y)
		}
	}
	
	/*
	METHOD: getAtt
	DEFINITION: Gets the value of the attribute.
	PARAMETER(s):
	    element -   the element node
	                can be an XPath expression or an IXMLDOMNode object
	    name    -   A string specifying the name of the attribute to return.
	USAGE:
	    xmlObj.getAtt("//Node", "name")
	RETURN VALUE:   The string that contains the attribute value.
	*/
	
	getAtt(element, name) {
		e := IsObject(element) ? element : this.selectSingleNode(element)
		return e.getAttribute(name)
	}
	
	/*
	METHOD: setText
	DEFINITION: Sets the text of an element node
	PARAMETER(s):
	    element -   the element node
	                can be an XPath expression or an IXMLDOMNode object
	    text    -   A string specifying the 'nodeValue' property of the child
	                'text' node associated with the specified 'element' node.
	                If blank the 'text' node(if any) will be removed.
	    idx     -   The index of the particular 'text' node whose value
	                will be set. Defaults to the first 'text' node.
	                *Though not a best-practice, it is very possible
	                to append multiple 'text' nodes to an element.
	                This parameter is for those cases only.
	USAGE:
	    xmlObj.setText("//Node", "New Text")
	    xmlObj.setText("//Node", "") ; removes/clears the 'text' node
	RETURN VALUE:   If the 'text' node exists and its value is altered
	                or cleared this method returns 'true'. Otherwise
	                this method creates a 'text' node and sets it value
	                to the one specified and then it returns an object
	                representing the newly added 'text' node
	*/
	
	setText(element, text:="", idx:=1) {
		e := IsObject(element) ? element : this.selectSingleNode(element)
		
		if (t := this.getChild(e, "t", idx)) {
			text <> "" ? t.nodeValue := text : e.removeChild(t)
			return true
		} else {
			t := this.createTextNode(text)
			, c := this.getChild(e, "", idx)
			return c ? e.insertBefore(t, c) : e.appendChild(t)
		}
	}
	
	/*
	METHOD: getText
	DEFINITION: Gets the 'nodeValue' property of the child 'text' node
	            of the specified element node
	PARAMETER(s):
	    element -   the element node
	                can be an XPath expression or an IXMLDOMNode object
	    idx     -   The index of the particular 'text' node whose value
	                will be retrieved . Defaults to the first 'text' node.
	                *Though not a best-practice, it is very possible
	                to append multiple 'text' nodes to an element.
	                This parameter is for those cases only. 
	USAGE:
	    xmlObj.getText("//Node")
	RETURN VALUE:   The string that contains the value
	*/
	
	getText(element, idx:=1) {
		e := IsObject(element) ? element : this.selectSingleNode(element)
		t := this.getChild(e, "t", idx)
			return t.text
	}
	
	/*
	METHOD: getChild
	DEFINITION: Gets the child node(of the specifiied 'type')
	            of the specified node.
	PARAMETER(s):
	    node    -   the parent node
	                can be an XPath expression or an IXMLDOMNode object
	    type    -   The type of node to get
	                see 'addChild' method for value(s)
	    idx     -   The index of the child node to be retrieved
	                Defaults to the first of its type 
	USAGE:
	    xmlObj.getChild("//Node", "element") ; 1st 'element' child
	    xmlObj.getChild("//Node", "element", 2) ; 2nd 'element' child
	RETURN VALUE:   An object representing the child node
	*/
	
	getChild(node, type:="element", idx:=1) {
		if !idx
			return
		n := IsObject(node) ? node : this.selectSingleNode(node)
		if !type
			return n.childNodes.item(idx-1)
		cn := this.getChildren(n, type)
		return ObjHasKey(cn, idx) ? cn[idx] : (idx<0 ? cn[cn.MaxIndex()+idx+1] : false)
	}
	
	/*
	METHOD: getChildren
	DEFINITION: Returns a list of children(of the specified 'type')
	            of the specified node
	PARAMETER(s):
	    node    -   the parent node
	                can be an XPath expression or an IXMLDOMNode object
	    type    -   The type of node to get
	                see 'addChild' method for value(s) 
	USAGE:
	    xmlObj.getChildren("//Node", "element")
	    xmlObj.getChildren("//Node", "text")
	RETURN VALUE:   An object containing a list of children
	                Each item in the list is an IXMLDOMNode object
	*/
	
	getChildren(node, type:="element") {
		static nts := {e:"element", t:"text", cds:"cdatasection"
		, er:"entityreference", en:"entity", pi:"processinginstruction"
		, c:"comment", dt:"documenttype", df:"documentfragment"
		, n:"notation"} ; nodeTypeString
		
		static _NT := "nodeType" , _NTS := "nodeTypeString"
		
		n := IsObject(node) ? node : this.selectSingleNode(node)
		if !n.hasChildNodes()
			return false
		cn := n.childNodes
		
		if (type ~= "^(1($|0|1|2)|[3-8])$")
			nType := _NT
		else if (type ~= "^[[:alpha:]]+$") {
			if (t := (StrLen(type)<=3))
				nType := ObjHasKey(nts, type) ? _NTS : false
			else {
				for a, b in nts
					continue
				until (nType := (type = b) ? _NTS : false)
			}
		} else return false
		
		if !nType
			return false
		
		c := []
		Loop, % cn.length {
			if (cn.item(A_Index-1)[nType] == (t ? nts[type] : type))
				c[(i := i ? i : 1)] := cn.item(A_Index-1), i+=1
		}
		return c
	}
	
	saveXML() {
		if this.file
			return this.save(this.file)
	}
	
	transformXML() {
		this.transformNodeToObject(this.style(), this.doc)
	}
	
	toEntity(ByRef str) {
		static e := [["&", "&amp;"], ["<", "&lt;"], [">", "&gt;"], ["'", "&apos;"], ["""", "&quot;"]]
		
		for a, b in e
			str := RegExReplace(str, b.1, b.2)
		return !(str ~= "s)([<>'""]|&(?!(amp|lt|gt|apos|quot);))")
	}
	
	toChar(ByRef str) {
		static e := [["<", "&lt;"], [">", "&gt;"], ["'", "&apos;"], ["""", "&quot;"], ["&", "&amp;"]]
		
		for a, b in e
			str := RegExReplace(str, b.2, b.1)
		return true
	}
	
	viewXML(ie:=true) {
		static dir := (FileExist(A_ScriptDir) ? A_ScriptDir : A_Temp)
		static _v := []
		
		dhw := A_DetectHiddenWindows
		DetectHiddenWindows, On
		
		if !this.documentElement
			return
		if WinExist("ahk_id " _v[this].hwnd)
			return
		if ie {
			this.save((f := dir "\tempXML_" A_TickCount ".xml"))
			if !FileExist(f)
				return
		} else (f := this.xml)
		
		if (hwnd := this._view(f)) {
			_v[this] := {hwnd: hwnd, res:f}
			WinWaitClose, % "ahk_id " hwnd
			ObjRemove(_v, this)
		}
		if (ie ? FileExist(f) : false)
			FileDelete, % f
		DetectHiddenWindows, % dhw
	}
	
	style(){
		static xsl
		
		if !IsObject(xsl) {
			RegExMatch(ComObjType(this.doc, "Name"), "IXMLDOMDocument\K(?:\d|$)", m)
			MSXML := "MSXML2.DOMDocument" (m < 3 ? "" : ".6.0")
			xsl := ComObjCreate(MSXML)
			style =
			(LTrim
			<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
		}
		return xsl
	}
	
	; ###################################
	;  INTERNAL METHOD(s) - DO NOT USE
	; ###################################
	
	addInsertE(m, en, pr:="", prm*) {
		n := pr
			? (IsObject(pr) ? pr : this.selectSingleNode(pr))
			: (m = "addElement" ? this.doc : false)
		e := IsObject(en) ? en : this.createElement(en)
		
		if prm.1 {
			if !IsObject(prm[prm.maxIndex()]) {
				t := prm.Remove(prm.maxIndex())
				if prm.1
					this.setAtt(e, prm*)
				if (t <> "")
					e.text := t
			} else this.setAtt(e, prm*)
		}
		
		if (m = "addElement")
			return n.appendChild(e)
		if (m = "insertElement")
			return n ? n.parentNode.insertBefore(e, n) : n
		
	}
	
	addInsertC(m, pr, type:="element", prm*) {
		static ntm := {1:"createElement", 3:"createTextNode", 4:"createCDATASection"
		, 5: "createEntityReference", 6:"createNode", 7:"createProcessingInstruction"
		, 8:"createComment", 10:"createNode", 11:"createDocumentFragment"
		, 12:"createNode"}
		
		static nt := {element:1, text:3, cdatasection:4, entityreference:5, entity:6
		, processinginstruction:7, comment:8, documenttype:10, documentfragment:11
		, notation:12, e:1, t:3, cds:4, er:5, en:6, pi:7, c:8, dt:10, df:11, n:12}
		
		n := IsObject(pr) ? pr : this.selectSingleNode(pr)
		
		if (type ~= "^(1($|0|1|2)|[3-8])$")
			_m := ntm[t := type]
		else if (type ~= "^[[:alpha:]]+$")
			_m := ntm[t := nt[type]]
		else return false
		
		if !_m
			return false
		
		if (_m == "createNode")
			_n := this[_m](t, prm*)
		else (_n := this[_m](prm*))
		
		if (m = "addChild")
			return _n ? n.appendChild(_n) : false
		if (m = "insertChild")
			return _n ? n.parentNode.insertBefore(_n, n) : false
		
	}
	
	_view(p*) {
		static _v := []
		
		if !ObjHasKey(_v, p.1) {
			if (p.1 ~= "^(\Q" A_ScriptDir "\E|\Q" A_Temp "\E)\\tempXML_\d+\.xml$")
				f := true
			else if (p.1 ~= "s)^<.*>$")
				f := false
			else return
			Gui, New
			Gui, +LastFound +LabelviewXML +Resize
			hwnd := WinExist()
			_v[hwnd] := {button:"", res: (f ? p.1 : false), xv:""}
			Gui, Margin, 0, 12
			Gui, Font, s10, Consolas
			Gui, Color, % f ? "" : 0xFFFFFF
			Gui, Add, % f ? "ActiveX" : "Edit"
			, % "y0 w600 h400 HwndhXV" (f ? "" : " HScroll -Wrap ReadOnly T8")
			, % f ? "Shell.Explorer" : p.1
			if f {
				GuiControlGet, IE,, % hXV
				IE.Navigate((_v[hwnd].res))
			}
			Gui, Add, Button, y+12 x+-100 w88 h26 Default HwndhBtn gviewXMLClose, OK
			Gui, Show,, % A_ScriptName " - viewXML"
			if !f
				SendMessage, 0x00B1, 0, 0,, % "ahk_id " hXV ; EM_SETSEL
			_v[hwnd].xv := hXV , _v[hwnd].button := hBtn
			return hwnd
		} else {
			if (p.2 == "viewXMLClose") {
				if (_v[p.1].res ? FileExist(_v[p.1].res) : false)
					FileDelete, % _v[p.1].res
				Gui, % p.1 ":Destroy"
				_v.Remove(p.1, "")
			}
			if (p.2 == "viewXMLSize") {
				if (A_EventInfo == 1) ; Minimized, do nothing
					return
				
				DllCall("SetWindowPos", "Ptr", _v[p.1].button, "Ptr", 0
				, "UInt", (A_GuiWidth-100), "UInt", (A_GuiHeight -38) ; x|y
				, "Uint",  88, "UInt", 26 ; w|h (ignored in this case)
				, "UInt", 0x0010|0x0001|0x0004) ; SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOZORDER
				
				DllCall("SetWindowPos", "Ptr", _v[p.1].xv, "Ptr", 0
				, "UInt", 0, "UInt", 0 ; x|y (ignored in this case)
				, "Uint",  A_GuiWidth, "UInt", (A_GuiHeight-50) ; w|h
				, "UInt", 0x0010|0x0002|0x0004) ; SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER
				
			}
		}
		return
		viewXMLSize:
		viewXMLClose:
		xml._view(A_Gui, A_ThisLabel)
		return
	}
	
}
;------------------------------------------------------------------------------------------------------------------
sXML_Pretty( XML, IndentationUnit="`t" ) { ; Adds linefeeds (LF, asc 10) and indentation between XML tags.
; NOTE: If the XML does not begin with a "<?xml...?>" tag, the output will begin with a newline.
	StringLen, il, IndentationUnit
	Loop, Parse, XML, <
		If ( A_Index = 1 )
			VarSetCapacity( XML, Round( StrLen( XML ) * ( A_IsUnicode ? 2.3 : 1.15 ) ), 0 )
		Else
			Loop, Parse, A_LoopField, >, % "`t`n`r " Chr( 160 )
				If ( A_Index = 1 )
				{
					closetag := SubStr( A_LoopField, 1, 1 )
					emptytag := SubStr( A_LoopField, 0 )
					If ( closetag = "?" ) && ( emptytag = "?" )
						XML := "<" A_LoopField
					Else
					{
						If ( closetag = "/" )
						{
							StringTrimRight, indent, indent, il
							If ( priortag )
								XML .= "`n" indent
						}
						Else
						{
							XML .= "`n" indent
							If ( emptytag != "/" ) && ( closetag != "!" )
								indent .= IndentationUnit
						}
						XML .= "<" A_LoopField
					}
					priortag := closetag = "/" || closetag = "!" || emptytag = "/"
				}
				Else
					XML .= ">" A_LoopField
	Return XML
} ; END - sXML_Pretty( XML, IndentationUnit="`t" )
User avatar
maestrith
Posts: 825
Joined: 16 Oct 2013, 13:52

Re: XML Class

08 Sep 2020, 15:08

Glad you found a solution.
John H Wilson III 05/29/51 - 03/01/2020. You will be missed.AHK Studio OSDGUI Creator
Donations
Discord
All code is done on a 64 bit Windows 10 PC Running AutoHotkey x32

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: pgeugene and 142 guests