我有一个Tab分隔文件,我必须将其转换为带有相关子节点的xml。该文件看起来像这样 -
Miscellaneous
Ceremonial
Test1
Test2
Test3
Sport
Athletics
Basketball
Biathlon
Boxing
Canoeing
Clay Pigeon Shooting
Climbing
Cricket
Cycling
Diving
Football
Football
Freefall
Gliding
Hill Walking
Hockey
Martial Arts
Karate
Judo
Jujitsu
Modern Pentathlon
Mountaineering
Orienteering
Parachuting
Paragliding
Parascending
Polo
Rugby
Rugby League
Rugby Union
Soccer
我被困在节点的第3级,即武术。
以下是我编写的代码,直到第二级才能正常工作。
有没有人可以告诉我要解决的问题是让它为3级以及更多级别进行操作 -
<cfif structKeyExists(form, "xlsfile") and len(form.xlsfile)>
<!--- Destination outside of web root --->
<cfset dest = getTempDirectory() />
<cffile action="upload" destination="#dest#" filefield="xlsfile" result="upload" nameconflict="makeunique">
<cfset theFileUploaded = upload.serverDirectory & "/" & upload.serverFile />
<cffile action="read" file="#theFileUploaded#" variable="theFile">
<cfset CrLf = chr(10) & chr(13) />
<cfset counter = 0 />
<cfset dataStr = structNew()>
<cfset isRoot = false>
<cfset tabCount = 0>
<cfset counter = 1>
<cfset childCounter = 1>
<cfset previousResult = 1>
<cfloop list="#theFile#" index="run" delimiters="#CrLf#">
<!--- The test value. --->
<cfset strTest = #Rtrim(run)# />
<!--- The instance counter. --->
<cfset intCount = 0 />
<!--- Get the initial position. --->
<cfset intPosition = Find( chr(9), strTest, 0 ) />
<!--- Keep searching till no more instances are found. --->
<cfloop condition="intPosition">
<!--- Increment instance counter. --->
<cfset intCount = (intCount + 1) />
<!--- Get the next position. --->
<cfset intPosition = Find(chr(9), strTest, (intPosition + Len( chr(9) ))) />
</cfloop>
<!--- Output the number of target instances.
<cfoutput>
--- #intCount# --- <br/>
</cfoutput> --->
<cfset childNode = "Child" & counter>
<cfdump var="#intCount-tabCount#">
<!--- Root --->
<cfif intCount eq 0>
<cfset dataStr.root = strTest>
<cfset tabCount = intCount>
<!--- Child at level 1 --->
<cfelseif tabCount eq 0 >
<cfset tabCount = intCount>
<cfset dataStr[childNode] = StructNew()>
<cfset dataStr[childNode].root = strTest>
<!--- Child at sub levels --->
<cfelseif ((intCount-tabCount) eq 0) or ((intCount-tabCount) eq 1)>
<cfif previousResult eq 0 and intCount-tabCount eq 1>
<cfdump var="#strTest#">
</cfif>
<cfset tabCount = intCount>
<cfset tabCount = intCount>
<cfset subChildNode = "Child" & childCounter>
<cfset dataStr[childNode][subChildNode] = strTest>
<cfset childCounter = childCounter+1>
<cfset previousResult = intCount-tabCount>
<cfelseif previousResult eq 0>
<cfset counter = counter+1>
<cfset childNode = "Child" & counter>
<cfset childCounter = 1>
<cfset tabCount = intCount>
<cfset dataStr[childNode] = StructNew()>
<cfset dataStr[childNode].root = strTest>
<cfelse>
<cfset counter = counter+1>
<cfset childNode = "Child" & counter>
<cfset childCounter = 1>
<cfset tabCount = intCount>
<cfset dataStr[childNode] = StructNew()>
<cfset dataStr[childNode].root = strTest>
</cfif>
</cfloop>
<cfdump var="#dataStr#">
答案 0 :(得分:6)
我会假设您正在努力解决递归和层次结构(父子)数据结构这一概念,我会回答这个问题。在你的问题中没有说清楚究竟是什么问题。
你在循环中获得前两个级别的循环很好,但是你可以通过你自己的代码看到它已经变得麻烦和不守规则必须管理...如果你的选项卡式txt文件突然得到第四个或者第五级别的孩子 - 您必须不断更新您的代码。
解决方法是编写递归函数;也就是说,一个自称的函数。
首先,设置一个基础结构,它将是你的&#34; root&#34; xml节点,我们随意调用根文档&#34; Categories&#34;:
<cfset XmlDoc = XmlNew(true) />
<cfset XmlDoc.xmlRoot = XmlElemNew(XmlDoc, "Categories") />
让我们也读一下你的txt文件的内容(你上面提供的那个标签反映了父母对孩子的等级):
<cffile action="read" file="c:\workspace\nodes.txt" variable="nodes">
显然,txt文件可以来自任何地方,所以我会留给你调整,只需注意我们最终得到一个名为&#34; node&#34;的变量。其中包含上面选项卡式txt文件的上下文。
接下来,您将要传递XmlDoc以及当前节点(要开始,将是根节点和已解析的内容,将成为您将要编写的新函数:
<cfset parseNodes( XmlDoc, XmlDoc['Categories'], nodes ) />
现在,您将编写处理您的“节点”的递归函数&#39;变量,将它找到的内容转换为xml元素,并将它们附加到传递给start的根xml元素,即&#39; categories&#39;。在我向你爆炸整个事情之前,让我们详细看看这个函数:
<cffunction name="parseNodes" returntype="string">
<cfargument name="rootXml" type="xml" required="true" />
<cfargument name="parentNode" type="xml" required="true" />
<cfargument name="content" type="string" required="true" />
<cfargument name="level" type="numeric" required="false" default="0" />
参数1是根xml文档,您将继续通过递归调用传递,因为它是生成xml节点所需的(通过XmlElemNew()
)
参数2是您要将子项附加到的父xml节点。
参数3是当前内容(您解析的选项卡式txt文件的剩余内容),您将在我们在处理期间吃掉的那一刻看到它。
参数4是我们用来跟踪什么&#34;层&#34;的标记。我们目前处于父子层级。首先,我们处于最高级别(0),因为我们在上面调用parseNodes()
函数时没有指定参数。
<cfset var thisLine = "" />
<cfset var localContent = arguments.content />
我们会设置一些本地变量,因此我们不会意外地覆盖CF隐含转换为全局的值,因为我们会自行处理。
<cfloop condition="#Len(localContent)#">
我们接下来开始循环一个条件:循环,直到localContent变量没有更多的长度。我们这样做是因为当我们递归地称呼自己时,我们需要继续“吃掉”#34;我们已经处理过的内容,这将阻止我们在输入和退出递归函数调用时反复重新处理它。
<cfset thisLine = ListGetAt(localContent, 1, Chr(13) & Chr(10)) />
我们将使用新行作为分隔符来抓取txt文件中的第一行。
<cfif CountIt(thisLine, chr(9)) eq arguments.level>
在这里,我们将计算在我们正在处理的当前行中发现的标签数量。 CountIt()函数是CFLib.org上可用的另一个外部UDF;我将在下面的最终代码预览中包含它。我们计算选项卡的数量,以确定我们正在工作的当前级别是否与父子格式中的正确位置匹配。因此,例如,如果我们在root(0),并且我们计算1个标签 - 我们立即知道,我们不在正确的级别,因此需要递减。
<cfset arguments.parentNode.XmlChildren[ArrayLen(arguments.parentNode.XmlChildren)+1] = XmlElemNew(arguments.rootXml, '#Replace(Trim(thisLine),' ','_','ALL')#') />
<cfset arguments.parentNode.XmlChildren[ArrayLen(arguments.parentNode.XmlChildren)].XmlText = Trim(thisLine) />
我们已经确定我们处于正确的级别,因此我们向XmlChildren数组添加一个新元素,并将其XmlName设置为我们解析出的值(保存在thisLine
中)。请注意,当调用实际的XmlElemNew()函数时,我们Trim()thisLine是安全的,并将任何空格转换为下划线,因为空格在XML元素的名称中无效(即。<My Xml Node>
会产生错误)。
<cfset localContent = ListDeleteAt(localContent, 1, chr(10) & chr(13)) />
这是我们吃饭的地方&#34;我们已处理的txt文件中的内容行,因此无法再次处理。我们再次将内容视为列表(使用CRLF作为分隔符)并删除第一个(最顶层)项。
现在,如果我们确定我们不在父子层次结构的正确级别上,接下来的两行就是我们所做的:
<cfelseif CountIt(thisLine, chr(9)) gt arguments.level>
<cfset localContent = parseNodes( arguments.rootXml, arguments.parentNode.XmlChildren[ArrayLen(arguments.parentNode.XmlChildren)], localContent, arguments.level + 1 ) />
在这里,我们确定当前行中的标签数量更大比我们正在处理的水平更高,因此必须递减。这发生在下一行,其中我们已经在的parseNodes()函数再次被调用,但稍微更新了参数:
最后,最重要的是,请注意方法的返回更新localContent变量。这个很重要!对该函数的递归调用也将进入&#34;吃掉&#34;解析后的txt文件,因此重要的是要确保每个外部调用也适用于最新的解析(和吃掉)内容。
如果tab的计数小于当前层,则执行最后一个条件,这意味着我们需要退出当前的递归迭代,并返回到父级,确保返回&#34; eaten up&#34;到目前为止我们在此次迭代中处理的内容:
<cfelse>
<cfreturn localContent />
</cfif>
</cfloop>
<cfreturn '' />
</cffunction>
现在你有一个函数可以递归调用自身并处理任意数量的父子关系层。
已完成的代码
<cfset nl = chr(10) & chr(13) />
<cfset tab = chr(9) />
<cfscript>
//@author Peini Wu (pwu@hunter.com)
function CountIt(str, c) {
var pos = findnocase(c, str, 1);
var count = 0;
if(c eq "") return 0;
while(pos neq 0){
count = count + 1;
pos = findnocase(c, str, pos+len(c));
}
return count;
}
</cfscript>
<cffunction name="parseNodes" returntype="string">
<cfargument name="rootXml" type="xml" required="true" />
<cfargument name="parentNode" type="xml" required="true" />
<cfargument name="content" type="string" required="true" />
<cfargument name="level" type="numeric" required="false" default="0" />
<cfset var thisLine = "" />
<cfset var localContent = arguments.content />
<!--- we will loop until the localContent is entirely processed/eaten up, and we'll trim it as we go --->
<cfloop condition="#Len(localContent)#">
<cfset thisLine = ListGetAt(localContent, 1, nl) />
<!--- handle everything at my level (as specified by arguments.level) --->
<cfif CountIt(thisLine, tab) eq arguments.level>
<cfset arguments.parentNode.XmlChildren[ArrayLen(arguments.parentNode.XmlChildren)+1] = XmlElemNew(arguments.rootXml, '#Replace(Trim(thisLine),' ','_','ALL')#') />
<cfset arguments.parentNode.XmlChildren[ArrayLen(arguments.parentNode.XmlChildren)].XmlText = Trim(thisLine) />
<!--- this line has been processed, so strip it away --->
<cfset localContent = ListDeleteAt(localContent, 1, nl) />
<!--- the current line is the next level down, so we must recurse upon ourselves --->
<cfelseif CountIt(thisLine, tab) gt arguments.level>
<cfset localContent = parseNodes( arguments.rootXml, arguments.parentNode.XmlChildren[ArrayLen(arguments.parentNode.XmlChildren)], localContent, arguments.level + 1 ) />
<!--- the current level is completed, and the next line processed is determined as a "parent", so we return what we have processed thus far, allowing the recursed parent function
to continue processing from that point --->
<cfelse>
<cfreturn localContent />
</cfif>
</cfloop>
<!--- at the very end, we've processed the entire text file, so we can simply return an empty string --->
<cfreturn '' />
</cffunction>
<cffile action="read" file="c:\workspace\cf\sandbox\nodes.txt" variable="nodes">
<cfset XmlDoc = XmlNew(true) />
<cfset XmlDoc.xmlRoot = XmlElemNew(XmlDoc, "Categories") />
<cfset parseNodes( XmlDoc, XmlDoc['Categories'], nodes ) />
<cfdump var=#xmlDoc#>
<textarea rows="40" cols="40">
<cfoutput>#xmlDoc#</cfoutput>
</textarea>
CAVEAT
你没有在你的问题中明确你想要的最终XML的格式,所以这个过程创建了一个有点冗余的节点结构,它们的值匹配(这不是很有用) ):
<?xml version="1.0" encoding="UTF-8"?>
<Categories>
<Miscellaneous>Miscellaneous</Miscellaneous>
这可能不是你想要的东西,但除非你进一步说明,否则我必须猜测并提出假设以保持示例的简单。