XML functions

Post your working scripts, libraries and tools for AHK v1.1 and older
DigiDon
Posts: 178
Joined: 19 May 2014, 04:55
Contact:

XML functions

25 May 2017, 13:00

EDIT 28.05 : Change names, added functions, added warning and explanations
Hello all !

Even though it seems there is no much hype anymore around XML I will still share a few things in case some would to handle some of their configuration in XML or for other purposes.

I am using a lot XML libraries by Coco visible on the old forum, and I thought first sharing the info could be useful:
https://autohotkey.com/board/topic/8919 ... parse-xml/
and XConfig
https://github.com/c...oHotkey-XConfig)

and here are a few functions I have coded to help me do some specific useful tasks.
I hope someone can be interested :P

XMLFCT_GetXPathToNode(Node) : get the Xpath from a node
(No dependancy)
Adapted from
https://stackoverflow.com/questions/241 ... e-instance
PS : not sure about the conversion of GetXPathToNode(((XmlAttribute)node).OwnerElement) "/@" node.Name

Code: Select all

;XMLFCT_GetXPathToNode
;**********************************
;Returns the Xpath from an XML node
XMLFCT_GetXPathToNode(XMLnode)
{
    ; if (node.NodeType == XmlNodeType.Attribute)
	; tooltip % "NodeTypeString " node.NodeTypeString " NodeName " node.NodeName
    if (XMLnode.NodeTypeString = "attribute")
    {
        ; // attributes have an OwnerElement, not a ParentNode; also they have             
        ; // to be matched by name, not found by position             
        ; return GetXPathToNode(((XmlAttribute)node).OwnerElement) "/@" node.Name
        return XMLFCT_GetXPathToNode(XMLnode.OwnerElement) "/@" XMLnode.NodeName
    }
    ; if IsObject(node.ParentNode)
    if (XMLnode.ParentNode="")
    {
        ; // the only node with no parent is the root node, which has no path
        return
    }

    ; // Get the Index
    indexInParent = 1
    siblingNode := XMLnode.PreviousSibling
    ; // Loop thru all Siblings
    ; while IsObject(siblingNode)
    while (siblingNode!="")
    {
        ; // Increase the Index if the Sibling has the same Name
        if (siblingNode.NodeName = XMLnode.NodeName)
        {
			; msgbox % "Sibling has same name " node.NodeName
            indexInParent++
        }
        siblingNode := siblingNode.PreviousSibling
    }

    ; // the path to a node is the path to its parent, plus "/node()[n]", where n is its position among its siblings.         
    return XMLFCT_GetXPathToNode(XMLnode.ParentNode) "/" XMLnode.NodeName "[" indexInParent "]"
}
And I'm adding two small related functions to get the Parent XPath from an element Xpath
And to get the Xpath without the last position from a XPath of a node which was including its position.
An example of their use will be demonstrated in the XMLFCT_CompareXMLbyElementNamesAndPosition function toward the end of this post.

Code: Select all

;XMLFCT_GetParentPathfromXpath
;Returns the Parent XPath from a Node XPath
XMLFCT_GetParentPathfromXpath(Lcl_Xpath) {
	FoundPos := InStr(Lcl_Xpath, "/", false, 0, 1)
	NewStr := SubStr(Lcl_Xpath, 1, FoundPos - 1)
	return NewStr
}

;XMLFCT_DeletePosfromXpath
;Returns a Node XPath without its position (stopping at its name)
XMLFCT_DeletePosfromXpath(Lcl_Xpath) {
	FoundPos := InStr(Lcl_Xpath, "[", false, 0, 1)
	NewStr := SubStr(Lcl_Xpath, 1, FoundPos - 1)
	return NewStr
}
WARNING AND PRECISIONS
PS : For the following functions, please understand that these functions have to be tweaked the way you want to compare the XML files.
There is no chance of having a one-suits-all solution.
So : The following functions were intending to compare XML element names from 2 files which have unique combinations of Parent/Child names. If an element is not recognized by its parent and own name then the whole element (and its children/text) will be copied from the default to the compared file. If the element was present (same parent and same name) then nothing more will be compared/added : meaning the text/attributes of these elements can be different and will stay the same.
The use case here was to compare two XML language files : the default one and a custom one. If the custom one had a missing node, then the one from default file would be copied so that even though the translation would not be there, there would still be the text in default language to avoid errors and missing texts.
Other cases:
But if you have some elements with same names in the same files, you will probably have to use a different mechanism that will grab the position of elements in addition to the names. But in this case the elements will have to be organized in the same order in the default and compared files. You can see my attempt in my next post : XMLFCT_CompareXMLbyElementNamesAndPosition().
If you want to compare attributes/text you will have to add these comparisons to the functions.


XMLFCT_XMLFCT_PuttingDefaultXML(DefaultXmlStrngOrObject,ByRef ExistingXMLObject, ExistingXMLPathToSave)
Compare a Default XML String or Object to an Existing XML Object and add absent nodes to the ExistingXML.
Dependancy to XConfig for transform and save. Could be other library/function

Code: Select all

; PuttingDefaultXML
;**************************
;Compare a Default XML String or Object to an Existing XML Object and add absent nodes to the ExistingXML. 
;Save back the Existing XML to ExistingXMLPathToSave
XMLFCT_PuttingDefaultXML(DefaultXmlStrngOrObject,ByRef ExistingXMLObject, ExistingXMLPathToSave) 
{
	if (ExistingXMLPathToSave=DefaultXmlStrngOrObject)
		return
	DefaultXMLObject := new Xconfig(DefaultXmlStrngOrObject)
	;msgbox Start_PuttingDefaultXML
	;------------ nodes--------------
	n:=DefaultXMLObject.SelectNodes("//*")
	count=0 ; how many changes

	while,Node:=n.item[A_Index-1]
		{
		Node_Name:=Node.nodeName
		Node_ParentName:=Node.ParentNode.nodeName
		
		if Node_Name=ROOT
			continue
			
		t := ExistingXMLObject.selectSingleNode("//" . Node_ParentName . "/" . Node_Name)
		if !IsObject(t) ;If absent from the 2nd file
			{
			; msgbox % """//" . Node_ParentName . "/" . Node_Name . """ is absent in " . ExistingXMLPathToSave
			count++
			AbsentNode:=Node.nodeName
			p:=Node.cloneNode(true)
			y:=ExistingXMLObject.selectSingleNode("//" . Node_ParentName)
			y.appendChild(p)
			;You can enable following line to add an "added" attribute when the node has been added by the function
			;ExistingXMLObject.selectSingleNode("//" . Node_ParentName . "/" . Node_Name).setAttribute("added","1")
			}
		}
	If count<>0
		{
		ExistingXMLObject.transformXML()
		ExistingXMLObject.saveXML()
		}
}
StripIfNotInDefaultXML(DefaultXmlStrngOrObject,ByRef ExistingXMLObject, ExistingXMLPathToSave)
Compare a Default XML String or Object to an Existing XML Object and add absent nodes to the ExistingXML.
Dependancy to XConfig for transform and save. Could be other library/function

Code: Select all

; StripIfNotInDefaultXML
;**************************
;Compare a Default XML String or Object to an Existing XML Object and Strip/delete nodes which were not in the Default XML. 
;Save back the Existing XML to ExistingXMLPathToSave
XMLFCT_StripIfNotInDefaultXML(DefaultXmlStrngOrObject,ByRef ExistingXMLObject, ExistingXMLPathToSave) 
{
	if (ExistingXMLPathToSave=DefaultXmlStrngOrObject)
		return
	DefaultXMLObject := new xml(DefaultXmlStrngOrObject)
	;------------ nodes--------------
	n:=ExistingXMLObject.SelectNodes("//*")
	count=0 ; how many changes
	while,Node:=n.item[A_Index-1]
	{
		Node_Name:=Node.nodeName
		Node_ParentName:=Node.ParentNode.nodeName
		
		if Node_Name=ROOT
			continue
		
		t := DefaultXMLObject.selectSingleNode("//" . Node_ParentName . "/" . Node_Name)
		if !IsObject(t) ;If absent from the 2nd file
			{
			count++
			; msgbox % """//" . Node_ParentName . "/" . Node_Name . """ is absent from your default XML file and so is deleted from the Existing XML
			Node.ParentNode.RemoveChild(Node)

			}
	}
	If count<>0
		{
		ExistingXMLObject.transformXML()
		ExistingXMLObject.saveXML()
		}
}
Cheers ! :P
Last edited by DigiDon on 28 May 2017, 07:29, edited 3 times in total.
EverFastAccess : Take Notes on anything the Fast way: Attach notes, Set reminders & Speed up research in 1 gesture - AHK topic
AHK Dynamic Obfuscator L - Protect your AHK code by Obfuscation - AHK topic
QuickModules for Outlook : Sort Outlook emails very quickly to multiple folders - AHK topic
Coding takes lots of time and efforts. If I have helped you or if you enjoy one of my free projects, please consider a small donation :thumbup:
Sorry I am working hard at the moment at a new job and can't commit on delays of answers & updates
DigiDon
Posts: 178
Joined: 19 May 2014, 04:55
Contact:

Re: XML functions

28 May 2017, 07:22

This is my attempt to write a function that will compare two XML elements by name and position and add the default ones if missing from the existing file - respecting their default position.
Thus allowing comparison when several nodes share the same names. The limitation is that the two XML files should have the same order of their elements.

XMLFCT_CompareXMLbyElementNamesAndPosition
Compare an ExistingXMLObject to a DefaultXmlStrngOrObject
Look DefaultXmlStrngOrObject elements and check if they exist by name and position in the compared file
Add these elements (with text and attributes) to the compared file if they were not existing - respecting the original positions
Otherwise leave them like that (not changing attributes and texts even if different)
Dependancies : XConfig, GetXPathToNode, GetParentPathfromXpath
PS : the XMLnode parameter should be left empty when calling the function. It is only used for internal recursion

Code: Select all

;XMLFCT_CompareXMLbyElementNamesAndPosition
;**********************************
;Compare an ExistingXMLObject to a DefaultXmlStrngOrObject
;Look DefaultXmlStrngOrObject elements and check if they exist by name and position in the compared file
;Add these elements (with text and attributes) to the compared file if they were not existing - respecting the original positions
;Otherwise leave them like that (not changing attributes and texts even if different)
;PS : the XMLnode parameter should be left empty when calling the function. It is only used for internal recursion
XMLFCT_CompareXMLbyElementNamesAndPosition(DefaultXmlStrngOrObject,ByRef ExistingXMLObject, ExistingXMLPathToSave,XMLnode="ROOT")
{
static count, positioninparent
	if (XMLnode="")
		return
	else if (XMLnode="ROOT")
		{
		DefaultXMLObject := new Xconfig(DefaultXmlStrngOrObject)
		count=0 ; how many changes
		XMLnode:=DefaultXMLObject.SelectSingleNode("/ROOT")
		}
		
	Node_Path:=XMLFCT_GetXPathToNode(XMLnode)
	Node_ParentPath:=XMLFCT_GetParentPathfromXpath(Node_Path)
	
	; msgbox % "we look " Node_Path
	
	t := ExistingXMLObject.selectSingleNode(Node_Path)
	if !IsObject(t) ;If absent from the 2nd file we add it
		{
		count++
		p:=XMLnode.cloneNode(true)
		
		; msgbox % "select previous " XMLFCT_DeletePosfromXpath(Node_ParentPath) . "/*[" positioninparent "]"
		ynext:=ExistingXMLObject.selectSingleNode(XMLFCT_DeletePosfromXpath(Node_ParentPath) . "/*[" positioninparent "]")
		y:=ExistingXMLObject.selectSingleNode(Node_ParentPath)
		if (ynext="")
			ExistingXMLObject.addElement(p, y)
			else
		ExistingXMLObject.insertElement(p, ynext)
		if (Node_Path!="/ROOT[1]")
			{
			; msgbox %Node_Path% added to %Node_ParentPath% count %count% positioninparent %positioninparent% going to next sibling
			ExistingXMLObject.saveXML(ExistingXMLPathToSave)
			; msgbox saved %ExistingXMLPathToSave% and we go to next sibling
			return
			}
		}
	else if (IsObject(t) && (XMLnode.FirstChild!="") && XMLnode.FirstChild.NodeTypeString="element") ;if present and has children we look through children element
		{
		; msgbox %Node_Path% exist and has children so we look them
		childrenNodes:=XMLnode.childNodes
		while,ChildrenNode:=childrenNodes.item[A_Index-1]
			{
			positioninparent:=A_Index
			ChildrenNodeName:=ChildrenNode.NodeName
			; msgbox % "looking Children of " Node_Path " " ChildrenNodeName " " A_Index " out of " childrenNodes.Length
			XMLFCT_CompareXMLbyElementNamesAndPosition(DefaultXmlStrngOrObject,ExistingXMLObject,ExistingXMLPathToSave,ChildrenNode)
			}
		if (Node_Path!="/ROOT[1]")
			{
			; msgbox no more children we go too next sibling
			;until none and we go back to next sibling
			return
			}
		}
	else ;if no children we go to next sibling
		{
		; msgbox %Node_Path% exist but has no children so we go back to next sibling  
		return
		}
	If count<>0
		{
		ExistingXMLObject.transformXML()
		ExistingXMLObject.saveXML(ExistingXMLPathToSave)
		; msgbox saved %ExistingXMLPathToSave%
		ExistingXMLObject := new XConfig(ExistingXMLPathToSave)
		}
}
EverFastAccess : Take Notes on anything the Fast way: Attach notes, Set reminders & Speed up research in 1 gesture - AHK topic
AHK Dynamic Obfuscator L - Protect your AHK code by Obfuscation - AHK topic
QuickModules for Outlook : Sort Outlook emails very quickly to multiple folders - AHK topic
Coding takes lots of time and efforts. If I have helped you or if you enjoy one of my free projects, please consider a small donation :thumbup:
Sorry I am working hard at the moment at a new job and can't commit on delays of answers & updates

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: kaka2 and 104 guests