TSV в XML с использованием Coldfusion - PullRequest
0 голосов
/ 10 января 2012

У меня есть разделенный файл 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-м уровне узла, то есть боевых искусств.

Вот код, который я написал и прекрасно работает до 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#">

1 Ответ

6 голосов
/ 10 января 2012

Я собираюсь ответить на это при условии, что вы боретесь с концепцией рекурсии и иерархических (родительско-дочерних) структур данных , поскольку вы этого не сделалиВ вашем вопросе ясно, в чем именно заключается проблема.

Ваш цикл внутри цикла для получения первых двух уровней - это хорошо, но вы уже по собственному коду можете видеть, что управлять им становится громоздким и неуправляемым... и если ваш текстовый файл с вкладками внезапно получит четвертый или пятый уровень дочерних элементов - вам придется постоянно обновлять ваш код.

Решение этого вопроса - написать рекурсивный функция;то есть функция, которая вызывает себя.

Сначала настройте базовую структуру, которая будет вашим «корневым» xml-узлом, и мы будем произвольно называть корневой документ «Категории»:

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

Давайте также прочитаем содержимое вашего txt-файла (тот, который вы предоставили выше, с вкладками, отражающими иерархию родителей к детям):

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

Очевидно, txt-файлможет прийти откуда угодно, так что я оставлю это вам для настройки, просто обратите внимание, что в итоге мы получим переменную с именем «node», которая содержит контекст вашего txt-файла с вкладками выше.

Далее выВы передадите XmlDoc вместе с текущим узлом (который для начала будет корневым, и проанализированный контент) в новую функцию, которую вы напишите:

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

Теперь выМы собираемся написать рекурсивную функцию, которая обрабатывает переменную ваших узлов, преобразуя то, что она находит, в элементы xml и присоединяя их к корневому элементу 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)) />

Мы возьмем первую строку в текстовом файле, используя новую строку в качестве разделителя.

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

Здесь мы будем считатьколичество вкладок, обнаруженных в текущей строке, которую мы обрабатываем.Функция CountIt () - это еще один внешний UDF, который доступен на CFLib.org ;Я включу его ниже в окончательный предварительный просмотр кода.Мы подсчитываем количество вкладок, чтобы определить, соответствует ли текущий уровень, с которым мы работаем, правильному месту в иерархии родитель-потомок.Так, например, если мы находимся в 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 (), мы обрежем thisLine для обеспечения безопасности и преобразуем все пробелы в подчеркивания, поскольку пробел в имени элемента XML недопустим (т. Е. <My Xml Node> приведет кошибка).

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

Здесь мы «съедаем» строку содержимого в вашем текстовом файле, которую мы обработали, чтобы он не обрабатывался снова.Мы снова воспринимаем содержимое как список (используя 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. Мы передаем наше текущее текстовое содержимое (помните, что это то, что мы «съедаем», когда идем)
  4. Мы передаем текущий уровень в иерархию плюс 1 , указывая, что когда мы снова попадаем в тело функции, мы работаем на правильном уровне.

Наконец, и самое главное, обратите внимание, что при возврате метода обновляется переменная localContent .Это важно!Рекурсивные вызовы функции также «съедают» проанализированный текстовый файл, поэтому важно убедиться, что каждый внешний вызов также работает с самым современным проанализированным (и съеденным) контентом.

Последнее условие выполняется, если количество вкладок меньше текущего уровня, что означает, что нам нужно выйти из текущей рекурсивной итерации и вернуться к родителю, обязательно вернув "съеденный" контент, который мы обработалидо сих пор в этой итерации:

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

Это, вероятно, НЕ то, что вы хотите в будущем, но если вы неуточните далее, я должен угадать и придумать предположения, чтобы пример был простым.

...