TSV в XML с использованием Coldfusion

У меня есть разделенный файл 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 ответ

Решение

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

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

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

Сначала настройте базовую структуру, которая будет вашим "корневым" 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 (), мы обрезаем эту строку на безопасные значения и преобразуем любые пробелы в подчеркивания, так как пробел недопустим в имени элемента 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>

ПРЕДОСТЕРЕЖЕНИЕ

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

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

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

Другие вопросы по тегам