0

我有一个 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 级和更多级别上正常工作-

<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#">

4

1 回答 1

6

我将在假设您正在为递归分层(父子)数据结构的概念苦苦挣扎的情况下回答这个问题,因为您没有在问题中明确说明问题到底是什么。

您在循环中获得前两个级别的循环很好,但是您已经可以通过自己的代码看到它已经变得繁琐且难以管理……如果您的选项卡式 txt 文件突然获得第四或第五级孩子——你必须不断更新你的代码。

解决这个问题的方法是写一个递归函数;也就是调用自身的函数。

首先,设置一个基本结构,它将作为您的“根”xml 节点,我们将任意调用根文档“类别”:

<cfset XmlDoc = XmlNew(true) />
<cfset XmlDoc.xmlRoot = XmlElemNew(XmlDoc, "Categories") />

让我们也读入你的 txt 文件的内容(你在上面提供的那个是标签式的,反映了父母对孩子的层次结构):

<cffile action="read" file="c:\workspace\nodes.txt" variable="nodes">

显然,txt 文件可以来自任何地方,所以我将把它留给你来调整,只需注意我们最终会得到一个名为“nodes”的变量,其中包含上面选项卡式 txt 文件的上下文。

接下来,您将把 XmlDoc 和当前节点(首先是根节点和解析的内容)一起传递到您将编写的新函数中:

<cfset parseNodes( XmlDoc, XmlDoc['Categories'], nodes ) />

现在,您将编写处理“nodes”变量的递归函数,将找到的内容转换为 xml 元素,并将它们附加到传递给 start 的根 xml 元素,即“类别”。在我向你猛烈抨击之前,让我们详细看看这个函数:

<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 是我们将用来跟踪我们当前在父子层次结构中的“层”的标记。首先,我们将在最高级别 (0),因为我们在调用parseNodes()上面的函数时没有指定参数。

<cfset var thisLine = "" />
<cfset var localContent = arguments.content />

我们将设置一些本地变量,这样我们就不会在递归自己时意外覆盖 CF 隐式转换为全局的值。

<cfloop condition="#Len(localContent)#">

我们接下来将开始循环一个条件:循环直到 localContent 变量没有更多长度。我们这样做是因为当我们递归调用自己时,我们将需要继续“吃掉”我们已经处理过的内容,这将阻止我们在进入和退出时一遍又一遍地重新处理它递归函数调用。

<cfset thisLine = ListGetAt(localContent, 1, Chr(13) & Chr(10)) />

我们将获取 txt 文件中的第一行,使用新行作为分隔符。

<cfif CountIt(thisLine, chr(9)) eq arguments.level>

在这里,我们将计算在我们正在处理的当前行中发现的选项卡的数量。CountIt() 函数是另一个外部 UDF,可在CFLib.org上找到;我将在下面的最终代码预览中包含它。我们计算选项卡的数量以确定我们正在工作的当前级别是否与父子层次结构中的正确位置匹配。因此,例如,如果我们在根 (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)) />

这是我们“吃掉”我们已处理的 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() 函数再次被调用,但参数略有更新:

  1. 我们仍然传递根 xml 文档。
  2. 我们现在将最近创建的子元素作为新根传递。
  3. 我们传入我们当前的 txt 内容(记住,这是我们正在“吃掉”的内容)
  4. 我们在层次结构中传入当前级别加 1,表示当我们再次到达函数主体时,我们正在处理正确的级别。

最后,也是最重要的一点,注意方法的返回更新了 localContent 变量。这个很重要!对函数的递归调用也会“吃掉”已解析的 txt 文件,因此确保每个外部调用也适用于最新的已解析(并吃掉)内容是很重要的。

如果选项卡的数量小于当前层级,则执行最后一个条件,这意味着我们需要退出当前的递归迭代,并返回父级,确保返回我们迄今为止处理的“吃掉”内容本次迭代:

    <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>

警告

您在问题中没有明确说明您想要哪种格式的最终​​ XML,因此这里的这个过程创建了一个与其值匹配的节点的冗余结构(这不是很有用):

<?xml version="1.0" encoding="UTF-8"?>
<Categories>
  <Miscellaneous>Miscellaneous</Miscellaneous>

这可能不是您以后想要的,但是除非您进一步指定,否则我必须猜测并提出假设以使示例保持简单。

于 2012-01-10T19:58:37.563 回答