XML Parsing is super easy with this AutoHotkey class

XML ParsingI’ve been using APIs for several years however I was never great at parsing the returned data.  In the below video maestrith, author of AHK Studio, walked me through using the XML class he wrote for parsing XML.  It leverages using XPath and is a life saver!

Here is the video where he walks me through using it

And this is the file we reviewed when doing the XML parsing

;               | Root Element XML files can have only 1 root element, this creates that.
;			 |		       | Optional Path/Filename if you want to use an xml file outside of the scripts directory
;			 |		       |
MyXML:=new XML("RootElement","lib\MyXML.xml") ;Creates or Loads the XML if the file exists
;| Stored object into the name Node
;|		          | Path of the new Element you wish to create
;|		          | 				| Associative Array of Attributes that you want to be in the new element
;|		          | 				|							| A text string that is appended to <node>HERE!</node> but be careful
;|		          | 				|							| If you want to have nodes below this current node do not give it a text value
;|		          | 				|							| 	   | Duplicate, This allows for the same named Element to be at the same level 1=Allow Duplicate
;|		          | 				|							| 	   |
Node:=MyXML.Add("My/New/Path",{Attribute:"Value",Another:"Attribute"},"Text Value",0) ;Create a new Node Element
loop, 5
  Node:=MyXML.Add("My/New/Path",{Attribute:"Value",index:A_Index,Another:"Attribute"},"hello" A_Index+10,1) ;Create a new Node Element

Top:=MyXML.Add("Just_A_Node") ;Create a node on its own


;            | The node object that you created previously
;            | 	| The name for the new node
;            | 	|			| Like above an Associative Array for the Attributes
;            | 	|			|			| Text Value for the Node
;            | 	|			|			|
MyXML.Under(Top,"Node_Name",{Attribute:"Value"},"Text Value")

MyXML.Transform() ;Makes the XML Pretty
MyXML.Transform() ;Sometimes needed.

;~list:=MyXML.SN("//Path") ;Select NODES: build list which has all nodes with the nodename of Path  ;// means look everywhere
;~node:=myxml.SSN("//My/New")
;~list:=SN(node,"Path")
;~while(item:=list.item[a_index-1],obj:=xml.EA(item))
;~m(item.xml,obj.Attribute,obj.index)
;~m(MyXML.SSN("//My/New").xml)
;~m(SSN(Top,"Node_Name").xml) ;first item is ahk object.  then pass what we want from that object
;~m(SSN(Top,"Node_Name").xml) ;first item is ahk object.  then pass what we want from that object

;			     @=Attribute
;~m(MyXML.SSN("//Path[@index='4']").xml) ;//Path gets first.  If want multiple ones (and want specific one) use brackets and @index
;~m(MyXML.SSN("//Path[text()='15']").xml) ;//Path gets first.  If want multiple ones (and want specific one) use brackets and @index
;                   text() =looking for exact text values
;~m(MyXml.SSN("//Path[contains(text(),'lo11')]").xml)
;                   does matching across all path nodes thtat contain text value
                    ;~swap contains(text(),'lol1')  to contains(@Attribute,'" variable "') ;use inside double outside single quotes to pass variable



m(MyXML[]) ;Shows a MsgBox with the contents of the new node
;          1 is to "Make the XML pretty"
MyXML.Save(1) ;Saves the xml
ExitApp

Class XML{
  keep:=[]
  __New(param*){
    if(!FileExist(A_ScriptDir "\lib"))
      FileCreateDir,%A_ScriptDir%\lib
    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
    ;temp.preserveWhiteSpace:=1
    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)
  }CreateElement(doc,root){
    return doc.AppendChild(this.xml.CreateElement(root)).ParentNode
  }Add(path,att:="",text:="",dup:=0){
    p:="/",add:=(next:=this.SSN("//" path))?1:0,last:=SubStr(path,InStr(path,"/",0,0)+1)
    if(!next.xml){
      next:=this.SSN("//*")
      for a,b in StrSplit(path,"/")
        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)
    next.text:=text
    return next
  }
  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:""))
  }
  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
  }ReCreate(path,new){
    rem:=this.SSN(path),rem.ParentNode.RemoveChild(rem),new:=this.Add(new)
    return new
  }SSN(path){
    return this.xml.SelectSingleNode(path)
  }SN(path){
    return this.xml.SelectNodes(path)
  }__Get(x=""){
    return this.xml.xml
  }Get(Path,Default){
    text:=this.SSN(path).text
    return text?text:Default
  }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)
  }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,"rw"),file.seek(0),file.write(this[]),file.length(file.position)
  }EA(path,att:=""){
    list:=[]
    if(att)
      return path.NodeName?SSN(path,"@" att).text:this.SSN(path "/@" att).text
    nodes:=path.NodeName?path.SelectNodes("@*"):nodes:=this.SN(path "/@*")
    while,n:=nodes.item(A_Index-1)
      list[n.NodeName]:=n.text
    return list
}}
SSN(node,path){
  return node.SelectSingleNode(path)
}
SN(node,path){
  return node.SelectNodes(path)
}
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:="AHK Studio",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
    }
}
t(x*){
  for a,b in x{
    if((obj:=StrSplit(b,":")).1="time"){
      SetTimer,killtip,% "-" obj.2*1000
      Continue
    }
    list.=b "`n"
  }
  Tooltip,% list
  return
  killtip:
  ToolTip
  return
}

 

 

Comments are closed.