Вот общее решение, которое корректно обрабатывает любой набор линий, имеющих заданный формат - даже если есть разное количество подчеркиваний в каждой строке и первое «имя» не то же самое на все линии:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my" exclude-result-prefixes="my xs">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vLines" select="tokenize(/*, '\r?\n')[.]"/>
<xsl:variable name="vPass1">
<t>
<xsl:apply-templates mode="pass1"/>
</t>
</xsl:variable>
<xsl:template match="/*" mode="pass1">
<xsl:for-each select="$vLines">
<xsl:sequence select="my:makeTree(normalize-space(.))"/>
</xsl:for-each>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="$vPass1" mode="pass2"/>
</xsl:template>
<xsl:function name="my:makeTree">
<xsl:param name="pLine"/>
<xsl:variable name="vName" select="substring-before($pLine, '_')"/>
<xsl:choose>
<xsl:when test="$vName">
<xsl:element name="{$vName}">
<xsl:sequence select="my:makeTree(substring-after($pLine, '_'))"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name=
"{normalize-space(substring-before($pLine, '='))}">
<xsl:sequence select="substring-after($pLine, '=')"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:function name="my:group">
<xsl:param name="pNodes" as="node()*"/>
<xsl:for-each-group select="$pNodes[self::*]" group-by="name()">
<xsl:element name="{name()}">
<xsl:for-each select="current-group()">
<xsl:sequence select="my:group(node())"/>
</xsl:for-each>
</xsl:element>
</xsl:for-each-group>
<xsl:copy-of select="$pNodes[not(self::*)]"/>
</xsl:function>
<xsl:template match="*[not(my:path(.) = preceding::*/my:path(.))]" mode="pass2">
<xsl:copy>
<xsl:apply-templates select="//*[my:path(.) = my:path((current()))]/node()"
mode="pass2"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*" mode="pass2"/>
<xsl:template match="/*" mode="pass2" priority="3">
<xsl:apply-templates mode="pass2"/>
</xsl:template>
<xsl:function name="my:path" as="xs:string">
<xsl:param name="pElement" as="element()"/>
<xsl:sequence select=
"string-join($pElement/ancestor-or-self::*/name(.), '/')"/>
</xsl:function>
</xsl:stylesheet>
когда это преобразование применяется на следующий документ XML ( данных строк, завернутые в верхний элемент, чтобы сделать это хорошо сформированный XML-документ):
<t>
heading1_sub1_element1 = data1
heading1_sub1_element2 = data2
heading1_sub1_element3 = data3
heading1_sub2_element1 = data4
heading1_sub2_element2 = data5
heading1_sub2_element3 = data6
</t>
разыскиваемых, правильный результат получается:
<heading1>
<sub1>
<element1> data1</element1>
<element2> data2</element2>
<element3> data3</element3>
</sub1>
<sub2>
<element1> data4</element1>
<element2> data5</element2>
<element3> data6</element3>
</sub2>
</heading1>
При применении же преобразование к этому, гораздо более сложный XML-документ:
<t>
heading1_sub1_element1 = data1
heading1_sub1_element2 = data2
heading1_sub1_element3 = data3
heading1_sub2_element1 = data4
heading1_sub2_element2 = data5
heading1_sub2_element3 = data6
heading2_sub1_sub2_sub3 = data7
heading2_sub1_sub2_sub3_sub4 = data8
heading2_sub1_sub2 = data9
heading2_sub1 = data10
heading2_sub1_sub2_sub3 = data11
</t>
мы снова получить правильный, требуемый результат:
<heading1>
<sub1>
<element1> data1</element1>
<element2> data2</element2>
<element3> data3</element3>
</sub1>
<sub2>
<element1> data4</element1>
<element2> data5</element2>
<element3> data6</element3>
</sub2>
</heading1>
<heading2>
<sub1>
<sub2>
<sub3>
data7
<sub4> data8</sub4>
data11
</sub3>
data9
</sub2>
data10
</sub1>
</heading2>
Объяснение:
Это обработка два прохода:
- В pass1 мы преобразуем вход на временное дерево, что (в случае первого документа XML выше) выглядит так:
.....
<t>
<heading1>
<sub1>
<element1> data1</element1>
</sub1>
</heading1>
<heading1>
<sub1>
<element2> data2</element2>
</sub1>
</heading1>
<heading1>
<sub1>
<element3> data3</element3>
</sub1>
</heading1>
<heading1>
<sub2>
<element1> data4</element1>
</sub2>
</heading1>
<heading1>
<sub2>
<element2> data5</element2>
</sub2>
</heading1>
<heading1>
<sub2>
<element3> data6</element3>
</sub2>
</heading1>
</t>
.2. Во втором проходе мы выполняем определенный тип группировки, чтобы мы получили желаемый результат.
Примечание: В этом решении мы получаем входные строки как единственный дочерний элемент текстового узла единственного элемента в документе XML. Эта информация не нужна, и я сделал это только для удобства. Мы можем читать строки из внешнего текстового файла, используя стандартную функцию XSLT 2.0 unparsed-text()
.
Является ли заголовок всегда '', или есть ли количество заголовков, которые должны быть заключены в корневой узел? –
Borodin