Jump to content

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

[AHK_L] Save/Load Arrays


  • Please log in to reply
13 replies to this topic
trueski
  • Members
  • 121 posts
  • Last active: Jun 25 2014 09:12 PM
  • Joined: 08 Apr 2008
I created a couple functions to allow an array to be saved to a file which can then be loaded at a later time. The data is saved in xml format.

Functions
XA_Save("Array", Path) ;put variable name in quotes
XA_Load(Path) ;the name of the variable containing the array is returned

XA_Save(Array, Path) {
FileDelete, % Path
FileAppend, % "<?xml version=""1.0"" encoding=""UTF-8""?>`n<" . Array . ">`n" . XA_ArrayToXML(Array) . "`n</" . Array . ">", % Path, UTF-8
If (ErrorLevel)
	Return 1
Return 0
}
XA_Load(Path) {
  Local XMLText, XMLObj, XMLRoot, Root1, Root2
  
  If (!FileExist(Path))
	Return 1
	
  FileRead, XMLText, % Path
  
  XMLObj    := XA_LoadXML(XMLText)
  XMLObj    := XMLObj.selectSingleNode("/*")
  XMLRoot   := XMLObj.nodeName
  %XMLRoot% := XA_XMLToArray(XMLObj.childNodes)
  
  Return XMLRoot
}
XA_XMLToArray(nodes, NodeName="") {
   Obj := Object()
	
   for node in nodes
   {
      if (node.nodeName != "#text")  ;NAME
	  {
		If (node.nodeName == "Invalid_Name" && node.getAttribute("ahk") == "True")
		  NodeName := node.getAttribute("id")
		Else
		  NodeName := node.nodeName
	  }
        
      else ;VALUE
		  Obj := node.nodeValue
         
      if node.hasChildNodes
	  {
		;Same node name was used for multiple nodes
		If ((node.nextSibling.nodeName = node.nodeName || node.nodeName = node.previousSibling.nodeName) && node.nodeName != "Invalid_Name" && node.getAttribute("ahk") != "True")
		{
		  ;Create object
		  If (!node.previousSibling.nodeName)
		  {
			Obj[NodeName] := Object()
			ItemCount := 0
		  }
		  
		  ItemCount++
		  
		  ;Use the supplied ID if available
		  If (node.getAttribute("id") != "")
		    Obj[NodeName][node.getAttribute("id")] := XA_XMLToArray(node.childNodes, node.getAttribute("id"))
		
		  ;Use ItemCount if no ID was provided
		  Else
			Obj[NodeName][ItemCount] := XA_XMLToArray(node.childNodes, ItemCount)
	    }
		
		Else
		  Obj.Insert(NodeName, XA_XMLToArray(node.childNodes, NodeName))
	  }
   }
   
   return Obj
}
XA_LoadXML(ByRef data){
   o := ComObjCreate("MSXML2.DOMDocument.6.0")
   o.async := false
   o.LoadXML(data)
   return o
}
XA_ArrayToXML(theArray, tabCount=1, NodeName="") {     
    Local tabSpace, extraTabSpace, tag, val, theXML, root
	tabCount++
    tabSpace := "" 
    extraTabSpace := "" 
	
	if (!IsObject(theArray)) {
	  root := theArray
	  theArray := %theArray%
    }
	
	While (A_Index < tabCount) {
        tabSpace .= "`t" 
		extraTabSpace := tabSpace . "`t"
    } 
     
	for tag, val in theArray {
        If (!IsObject(val))
		{
		  If (XA_InvalidTag(tag))
		    theXML .= "`n" . tabSpace . "<Invalid_Name id=""" . XA_XMLEncode(tag) . """ ahk=""True"">" . XA_XMLEncode(val) . "</Invalid_Name>"
		  Else
			theXML .= "`n" . tabSpace . "<" . tag . ">" . XA_XMLEncode(val) . "</" . tag . ">"
	    }
		
		Else
		{
		  If (XA_InvalidTag(tag))
		    theXML .= "`n" . tabSpace . "<Invalid_Name id=""" . XA_XMLEncode(tag) . """ ahk=""True"">" . "`n" . XA_ArrayToXML(val, tabCount, "") . "`n" . tabSpace . "</Invalid_Name>"
		  Else
		    theXML .= "`n" . tabSpace . "<" . tag . ">" . "`n" . XA_ArrayToXML(val, tabCount, "") . "`n" . tabSpace . "</" . tag . ">"
	    }
    } 
	
	theXML := SubStr(theXML, 2)
	Return theXML
} 
XA_InvalidTag(Tag) {
	Char1      := SubStr(Tag, 1, 1) 
	Chars3     := SubStr(Tag, 1, 3)
	StartChars := "~``!@#$%^&*()_-+={[}]|\:;""'<,>.?/1234567890 	`n`r"
	Chars := """'<>=/ 	`n`r"
	
	Loop, Parse, StartChars
	{
		If (Char1 = A_LoopField)
		  Return 1
	}
	
	Loop, Parse, Chars
	{
		If (InStr(Tag, A_LoopField))
		  Return 1
	}
	
	If (Chars3 = "xml")
	  Return 1
	  
	Return 0
}
XA_XMLEncode(Text) {
StringReplace, Text, Text, &, &, All
StringReplace, Text, Text, <, <, All
StringReplace, Text, Text, >, >, All
StringReplace, Text, Text, ", ", All
StringReplace, Text, Text, ', ', All
Return Text
}

-trueski-

trueski
  • Members
  • 121 posts
  • Last active: Jun 25 2014 09:12 PM
  • Joined: 08 Apr 2008
EDIT: All bugs should be fixed.
-trueski-

GeekDude
  • Spam Officer
  • 391 posts
  • Last active: Oct 05 2015 08:13 PM
  • Joined: 23 Nov 2009
hmm... possible to make an xml to array converter?

trueski
  • Members
  • 121 posts
  • Last active: Jun 25 2014 09:12 PM
  • Joined: 08 Apr 2008

hmm... possible to make an xml to array converter?


That's what I've done. Look at the internal functions. In order to get the integer keys to work, I need to make some modifications so that the node name becomes the name of the object, and an ID or Key attribute is added with the correct key for each item in the array.
-trueski-

GeekDude
  • Spam Officer
  • 391 posts
  • Last active: Oct 05 2015 08:13 PM
  • Joined: 23 Nov 2009
oh, will it work for any kind of xml, supporting attributes and self contained tags? <Tag Attribute1="hello " Attribute2="world!" />

trueski
  • Members
  • 121 posts
  • Last active: Jun 25 2014 09:12 PM
  • Joined: 08 Apr 2008

oh, will it work for any kind of xml, supporting attributes and self contained tags? <Tag Attribute1="hello " Attribute2="world!" />


No. I could make it do that I suppose, but my intended purpose was to have a simple way to load and save any array, and also have that in a format that can be read easily by other programs. XML works great for this.

In your example, what would the array structure look like if it did read that? Something like this?
Tag
    Attribute1 = hello
    Attribute2 = world!

[Object "Tag", containing Attribute1 and Attribute2]
-trueski-

GeekDude
  • Spam Officer
  • 391 posts
  • Last active: Oct 05 2015 08:13 PM
  • Joined: 23 Nov 2009
I was thinking that it would be like this
<Tag A1 = "Hello " A2 = "world!">
    <Tag1 self="contained" />
    <Tag2 mc="donalds">ba dum tss</Tag2>
</Tag>
Would turn into an array like this:
Tag
    Attributes
        A1 = Hello
        A2 = world!
    SubTags
        Tag1
            Attributes
                self = contained
            SubTags
            Text =
        Tag2
            Attributes
                mc = donalds
            SubTags
            Text = ba dum tss
    Text = (Perhaps we can put the xml of the sub tags here)


trueski
  • Members
  • 121 posts
  • Last active: Jun 25 2014 09:12 PM
  • Joined: 08 Apr 2008
Hmm.. I see what you're saying. Is there any application for this other than parsing HTML? I can see that this would conflict with my scheme I've come up with for integer keys in arrays. If the method you showed was only for parsing HTML, I could have the function check if it's HTML, and then treat it differently.
-trueski-

GeekDude
  • Spam Officer
  • 391 posts
  • Last active: Oct 05 2015 08:13 PM
  • Joined: 23 Nov 2009
I was talking (typing) about creating another function completely separate from this one just for converting any old xml format file (html, rss, xml, and more) into an object.

Oh, and one more thing, MSXML fails to load certain documents for me, and to load an RSS file, I had to change the top level node from this: "<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-gb">" into this: "<feed>" for it to work. Any idea why some documents fail to load?

trueski
  • Members
  • 121 posts
  • Last active: Jun 25 2014 09:12 PM
  • Joined: 08 Apr 2008
I think that it would make sense for it to detect if the text is HTML or an RSS feed. I could also have the saving script append some kind of a comment that shows that it is an XML representation of an array in AHK. If I did this, it could use different rules for data that came straight from AHK as opposed to another source. I do feel that the rules for AHK should be standard though, unless there's a specific type of document that commonly is formatted differently (like HTML or RSS). Most XML that simply stores hierarchical data is fairly uniform and organized.

See example:
http://msdn.microsof...1(v=vs.85).aspx

If I were to use the method that you posted for every xml document, it would add unnecessary depth to the array. Additionally, when most people start creating arrays in autohotkey, I doubt they typically make objects in the array called "Attributes" or "Subtags". This would prevent the AHK-originated arrays from matching the structure that they had before they were saved.

I had to change the top level node from this: "<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-gb">" into this: "<feed>" for it to work. Any idea why some documents fail to load?


That would be because I have the script grab everything between the first <> to determine what the root node is. It should look for the first "<", and then get the text until the first space.
-trueski-

GeekDude
  • Spam Officer
  • 391 posts
  • Last active: Oct 05 2015 08:13 PM
  • Joined: 23 Nov 2009
The bit about rss and such is off-topic. I meant using the MSXML COM object, and the LoadXML (perhaps maybe Load too) method, it would not work correctly and o.document.xml would be blank.

Back on topic:
The format I described was not for xml generated by ArrrayToXml(), but for xml from anywhere. I am saying that it would be awesome if you could make a seperate function that will convert xml with attributes and other strange bits that don't fit in with ahk arrays, into a well formatted array/sub-array system. (or maybe I could take a crack at it)

EDIT: It seems that html is not always valid xml, and that there is a seperate com object for it. (It can't use XPath though, and I'm not really sure if it can do what I want it to)

trueski
  • Members
  • 121 posts
  • Last active: Jun 25 2014 09:12 PM
  • Joined: 08 Apr 2008
I updated the code, and I think it's now working with all arrays that originated in AHK. Please try to get the code to break.

If an array element name is an invalid xml element name, the element name becomes "Invalid_Name", and the name of the array element is included in the attribute "id". The attribute "ahk" is also added, to allow "Invalid_Name" as a potential element name.
-trueski-

magusneo
  • Members
  • 51 posts
  • Last active: May 10 2014 03:15 PM
  • Joined: 30 May 2012
Really cool,Thanks.
It seems it doesn't support tag name including "-"?
like this:
<AB-Analysis></AB-Analysis>

roi1
  • Members
  • 2 posts
  • Last active: Sep 28 2013 04:09 PM
  • Joined: 12 Aug 2013

The Load function errors if there is an ampersand (&) in a top item in a two dimensional array. Try XA_Load on this

<?xml version="1.0" encoding="UTF-8"?>
<testarray>
    <Invalid_Name id="test & test" ahk="True">
        <Invalid_Name id="1" ahk="True">1</Invalid_Name>
    </Invalid_Name>
</testarray>