Я собираюсь ответить на это при условии, что вы боретесь с концепцией рекурсии и иерархических (родительско-дочерних) структур данных , поскольку вы этого не сделалиВ вашем вопросе ясно, в чем именно заключается проблема.
Ваш цикл внутри цикла для получения первых двух уровней - это хорошо, но вы уже по собственному коду можете видеть, что управлять им становится громоздким и неуправляемым... и если ваш текстовый файл с вкладками внезапно получит четвертый или пятый уровень дочерних элементов - вам придется постоянно обновлять ваш код.
Решение этого вопроса - написать рекурсивный функция;то есть функция, которая вызывает себя.
Сначала настройте базовую структуру, которая будет вашим «корневым» 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 (), в которой мы уже работаем, вызывается снова, но с немного обновленными параметрами:
- Мы по-прежнему передаем корневой XML-документ.
- Теперь мы передаем последний созданный дочерний элемент как новый корень.
- Мы передаем наше текущее текстовое содержимое (помните, что это то, что мы «съедаем», когда идем)
- Мы передаем текущий уровень в иерархию плюс 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>
Это, вероятно, НЕ то, что вы хотите в будущем, но если вы неуточните далее, я должен угадать и придумать предположения, чтобы пример был простым.