I am trying to create an XML file that represents the hierarchical directory structure with an unknown amount of nested directories and files. This question relates to a previous one asked by myself, How to loop through nested directories in Foxpro given the initial start directory, so the code I have so far (see below) is based on the structure from the answer here.
*********************************
**** Variable initialization ****
*********************************
set asserts on
set date ymd
public loXml as MSXML2.DOMDocument60
public loRoot as MSXML2.IXMLDOMElement
public loNode as MSXML2.IXMLDOMElement
public loFile as MSXML2.IXMLDOMElement
public loFolder as MSXML2.IXMLDOMElement
public loSubFolder as MSXML2.IXMLDOMElement
public lcRootDir as String
public lcManifestNS as String
lcRootDir = "C:\LoopTest" && user defined root directory
lcManifestNS = "http://www.randomnamespace.com/ServerManifest/1.1" && namespace which all elements int his document reside
***********************************
**** Creating the XML Document ****
***********************************
loXml = createobject("MSXML2.DOMDocument.6.0")
loRoot = loXml.createNode("Element", "au:Manifest", lcManifestNS) && root element
loRoot.setAttribute("Path", lcRootDir) && path attrubute of root element
GetFilesRecursively(lcRootDir) && builds all folder and file child nodes under the root element
loXml.appendChild(loRoot) && append everything within the root node to the document
*********************
** same xml output **
*********************
loXml.save('C:\results\ex2_manifest_test.xml') && save the xml file to desired location
***************
** Functions **
***************
procedure getfilesrecursively(tcfolder)
assert ( vartype(m.tcfolder) = 'C' )
local lnfile, lafiles[1], lcfileorfolder, datemod && create local variables
for lnfile = 1 to adir(lafiles, addbs(m.tcfolder) + '*', 'd') && loop that will go through all direct items within initial directory
lcfileorfolder = lower(lafiles[m.lnfile, 1]) && this variable is the name of the item inside directory
if empty( strtran(m.lcfileorfolder,'.') ) && some items have names which are just . or .. etc, so ignore these items by restarting the loop
loop
endif
datemod = strtran(dtoc(lafiles[lnfile, 3]), '/', '-') + "T" + lafiles[lnfile, 4] && creating the datetime last modified value
if directory( addbs(m.tcfolder)+m.lcfileorfolder, 1 ) && if there is a subdirectory, re-run the main loop with updated file path
loFolder = loRoot.appendChild(loXml.createNode("Element", "au:Folder", lcManifestNS)) && folder node under root node
loFolder.setAttribute("Name", lcfileorfolder) && attrubutes for folder node
loFolder.setAttribute("Date", datemod)
getfilesrecursively(addbs(m.tcfolder)+m.lcfileorfolder)
else && if no further subdirectory, then insert the item into table
loFile = loFolder.appendChild(loXml.createNode("Element", "au:File", lcManifestNS)) && file node under folder node
loFile.setAttribute("Name", lcfileorfolder) && attributes for file node
loFile.setAttribute("Date", datemod)
endif
endfor
endproc
The main problem I am running into with this code is that all the folder directories are getting put under the root node and are not nesting properly. I understand why, but I do not clearly see how I can change this. Would it be to essentially create a new node every iteration and then append to that? I am just getting used to the syntax around FoxPro XML so some help fixing this issue would be much appreciated. In addition, it seems that any files under the root directory get put inside one of the nested directories in the XML output. You can probably see why as per my code. So ultimately, I am just looking to correct these things to get the XML output nodes to be properly nested. Maybe there is a different approach at solving this, so feel free to answer this question with an entirely new code outline if you wish.
You could do it using ADIR() as follows (please read comments following code):
Local lcOutputFile, lcRoot
lcOutputFile = 'C:\temp\nestedDirs.xml'
lcRoot = Home()
GetTreeXML(m.lcRoot, m.lcOutputFile)
Procedure GetTreeXML(tcRootPath, tcOutput)
Local lcXML
Set Textmerge On To (m.tcOutput) Noshow
\<au:Manifest xmlns:au="http://www.randomnamespace.com/ServerManifest/1.1" Path="<< ADDBS(m.tcRootPath) >>">
GetTree(m.tcRootPath,'myDirectories',0)
*!* Select * From myDirectories &&ORDER BY Filepath
*!* Use In 'myDirectories'
\</au:Manifest>
Set Textmerge Off
Set Textmerge To
Return m.lcXML
Endproc
Procedure GetTree(tcPath, tcCursorName, tnLevel)
Local lcCurDir, ix, lcPath
Local Array laDirs[1]
lcCurDir = Addbs(m.tcPath)
If m.tnLevel = 0
Create Cursor (m.tcCursorName) (FilePath c(250), Level i)
Endif
Insert Into (m.tcCursorName) (FilePath,Level) Values (m.lcCurDir, m.tnLevel)
For ix = 1 To Adir(laDirs,m.lcCurDir+"*.*","DHS")
lcPath = m.lcCurDir+laDirs[m.ix,1]
If laDirs[m.ix,1]#"." And "D"$laDirs[m.ix,5]
GetTree(m.lcPath, m.tcCursorName, m.tnLevel+1)
Else
If laDirs[m.ix,1] != '..'
ProcessPath( m.lcPath, m.tnLevel, laDirs[m.ix,3], laDirs[m.ix, 4] )
Endif
Endif
Endfor
\<< SPACE(2 * m.tnLevel) >></au:Folder>
Endproc
Procedure ProcessPath(tcPath, tnLevel, tdFileDate, tcFileTime)
Local lcName, lcDate
lcDate = Ttoc( Cast(m.tdFileDate As Datetime) + (Ctot(m.tcFileTime)-Ctot('0')), 3)
If Right(m.tcPath,2) == '\.' && Directory
If m.tnLevel > 0 && Skip root
lcName = Justfname(Left(m.tcPath, Len(m.tcPath)-2))
\<< SPACE(2 * m.tnLevel) >><au:Folder Name="<< m.lcName >>" Date="<< m.lcDate >>">
Endif
Else
lcName = Justfname(m.tcPath)
\<< SPACE(2 * (m.tnLevel+1)) >><au:File Name="<< m.lcName >>" Date="<< m.lcDate >>"/>
Endif
Endproc
I'am not sure about the resulting XML. I followed the format of yours. To me it looks a liitle awkward but if that is the format of your manifest then it is fine (I couldn't browse the schema).
Unrelated, in your code there were variables declared as public, please forget that there is a 'public' in VFP. Almost all of us use it only for the specific 'oApp' object created at the start of an application if any. Believe me it is dangerous to use.
Note: VFP ships a DLL called Filer.dll. It contains an activex 'Filer.FileUtil'. It can collect folder and files with some filters applied (has more than simple *.*' file skeleton filter). And it returns the results with original casing, unlike adir() result which uppercases all. Also, you can get file creation, modification and last access times.
Another one is Scripting.FileSystemObject. However, due to security it might not be installed.
If Adir() is fine for you then it is simple and fast.
EDIT: I later noticed above code would add an extra au:Folder at the end plus it wouldn't handle characters that are not allowed in XML. DOM processing would take care of both. So here is the version with DOM:
Local lcOutputFile, lcRoot
lcOutputFile = 'C:\temp\nestedDirs.xml'
lcRoot = Home()
GetTreeXML(m.lcRoot, m.lcOutputFile)
Procedure GetTreeXML(tcRootPath, tcOutput)
Local loXML As "MSXML2.DOMDocument.6.0", loRoot As "MSXML2.IXMLDOMElement"
loXML = Createobject("MSXML2.DOMDocument.6.0")
loRoot = m.loXML.createNode("Element", "au:Manifest", "http://www.randomnamespace.com/ServerManifest/1.1")
loRoot.setAttribute("Path", m.tcRootPath)
GetTree(m.tcRootPath,'myDirectories',0, m.loXML, m.loRoot)
m.loXML.appendChild(m.loRoot)
m.loXML.Save(m.tcOutput)
Endproc
Procedure GetTree(tcPath, tcCursorName, tnLevel, toDOM, toParent)
Local lcCurDir, ix, lcPath, lcName, lcDate, loElement
Local Array laDirs[1]
lcCurDir = Addbs(m.tcPath)
For ix = 1 To Adir(laDirs,m.lcCurDir+"*.*","DHS")
lcName = laDirs[m.ix,1]
lcDate = Ttoc( Cast(laDirs[m.ix,3] As Datetime) + (Ctot(laDirs[m.ix,4])-Ctot('0')), 3)
If laDirs[m.ix,1]#"." And "D"$laDirs[m.ix,5]
lcPath = m.lcCurDir + laDirs[m.ix,1]
loElement = m.toDOM.createElement("au:Folder")
m.loElement.setAttribute("Name", m.lcName)
m.loElement.setAttribute("Date", m.lcDate)
m.toParent.appendChild(m.loElement)
GetTree(m.lcPath, m.tcCursorName, m.tnLevel+1, m.toDOM, m.loElement)
Else
If laDirs[m.ix,1] != '.' And laDirs[m.ix,1] != '..'
loElement = m.toDOM.createElement("au:File")
m.loElement.setAttribute("Name", m.lcName)
m.loElement.setAttribute("Date", m.lcDate)
m.toParent.appendChild(m.loElement)
Endif
Endif
Endfor
Endproc