0

タブ区切りのファイルがあり、関連する子ノードを含む 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 レベル、つまり Martial Arts で立ち往生しています。

ここに私が書いたコードがあり、第 2 レベルまで正常に動作します。

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

質問で正確に何が問題なのかを明確にしていないため、再帰階層型(親子)データ構造の概念に苦労しているという前提でこれに答えます。

最初の 2 つのレベルを取得するためのループ内のループは問題ありませんが、独自のコードから、管理が面倒で手に負えないものになっていることが既にわかります...そして、タブ付きの txt ファイルが突然 4 番目または 5 番目のレベルになった場合コードを継続的に更新する必要があります。

これに対する解決策は、再帰関数を書くことです。つまり、自分自身を呼び出す関数です。

最初に、「ルート」xml ノードとなる基本構造体をセットアップします。ルート ドキュメントを任意に「Categories」と呼びます。

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

txt ファイルの内容も読みましょう (親から子への階層を反映するタブ付きの、上記で提供したファイル)。

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

明らかに、txt ファイルはどこからでも取得できるため、調整はあなたに任せます。ただし、上記のタブ付き txt ファイルのコンテキストを含む "nodes" という名前の変数になることに注意してください。

次に、XmlDoc を現在のノード (最初にルートとなるノード、および解析されたコンテンツ) と共に、次のように記述する新しい関数に渡します。

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

ここで、'nodes' 変数を処理する再帰関数を作成し、見つかったものを xml 要素に変換し、開始に渡されたルート xml 要素、つまり 'categories' にそれらをアタッチします。全体を爆破する前に、この関数を詳しく見てみましょう。

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

XmlElemNew()引数 1 はルート xml ドキュメントであり、xml ノードの生成 ( 経由)に必要なため、再帰呼び出しを介して引き続き渡します。

引数 2 は、子をアタッチする親 xml ノードです。

引数 3 は現在のコンテンツ (解析されたタブ付き txt ファイルの残りの部分) であり、処理中にすぐに削除されます。

引数 4 は、親子階層で現在どの「レイヤー」にいるかを追跡するために使用するマーカーです。parseNodes()上記の関数を呼び出したときに引数を指定しなかったため、最初に最高レベル (0) から始めます。

<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() 関数は、CFLib.orgで利用できる別の外部 UDFです。以下の最終的なコード プレビューに含めます。タブの数を数えて、作業中の現在のレベルが親子階層の正しい場所に一致するかどうかを判断します。たとえば、ルート (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() 関数が呼び出されたときに、安全のために thisLine を Trim() し、空白をアンダースコアに変換することに注意してください。空白は XML 要素の名前では無効です (つまり<My Xml Node>、エラーが発生します)。

<cfset localContent = ListDeleteAt(localContent, 1, chr(10) & chr(13)) />

ここで、処理した txt ファイル内のコンテンツ行を「食い尽くし」、再度処理されないようにします。コンテンツを再びリストとして扱い (CRLF を区切り記号として使用)、最初 (一番上) の項目を削除します。

次の 2 行は、親子階層の正しいレベルにいないと判断した場合の処理​​です。

<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 に答える