XSLT для добавления тега иерархии элементов - PullRequest
0 голосов
/ 28 апреля 2019

Мне нужно преобразовать XML в другой XML, заменив элемент xml начальным тегом элемента в зависимости от условия.У входа может быть несколько элементов уровня, а у элемента уровня может быть другой уровень как дочерний или родной.

Ниже приведен мой вход

<?xml version="1.0" encoding="UTF-8"?>
<Data>
  <Collection>
    <Primary>
      <PrimaryName>1238</PrimaryName>
      <Content>1</Content>
      <Modifier>81</Modifier>
    </Primary>
  </Collection>
  <CModifier>55</CModifier>
  <LEVEL>BEGIN</LEVEL>
  <Collection>
    <Primary>
      <PrimaryName>1023</PrimaryName>
      <Content>1</Content>
      <Modifier>81</Modifier>
    </Primary>
  </Collection>
  <CModifier>99</CModifier>
  <LEVEL>BEGIN</LEVEL>
  <LEVEL>BEGIN</LEVEL>
  <Collection>
    <Primary>
      <PrimaryName>5754</PrimaryName>
      <Content>Testing%</Content>
      <Modifier>11</Modifier>
    </Primary>
  </Collection>
  <LEVEL>END</LEVEL>
  <LEVEL>END</LEVEL>
  <LEVEL>END</LEVEL>
</Data>

И я пытаюсь преобразовать это в следующий XML

<?xml version="1.0" encoding="UTF-8"?>
<Data>
  <Collection>
    <Primary>
      <PrimaryName>1238</PrimaryName>
      <Content>1</Content>
      <Modifier>81</Modifier>
    </Primary>
  </Collection>
  <CModifier>55</CModifier>
  <LEVEL>
  <Collection>
    <Primary>
      <PrimaryName>1023</PrimaryName>
      <Content>1</Content>
      <Modifier>81</Modifier>
    </Primary>
  </Collection>
  <CModifier>99</CModifier>
  <LEVEL>
  <LEVEL>
  <Collection>
    <Primary>
      <PrimaryName>5754</PrimaryName>
      <Content>Testing%</Content>
      <Modifier>11</Modifier>
    </Primary>
  </Collection>
  </LEVEL>
  </LEVEL>
  </LEVEL>
</Data>

Я пытался использовать XSLT, но, похоже, я не могу просто добавить начальный или конечный тег в зависимости от условия.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">


    <xsl:output method="xml" encoding="utf-8" indent="yes"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>

    <xsl:template match="LEVEL">
        <xsl:if test="LEVEL='BEGIN'">
            <level>
        </xsl:if>
        <xsl:if test="LEVEL='END'">
            </level>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

Ответы [ 4 ]

2 голосов
/ 28 апреля 2019

Это сложная проблема. Ваша попытка не удалась, потому что таблица стилей XSLT также должна быть правильно сформированным документом XML

Следующая таблица стилей будет работать на данном примере. Надеемся, что все ваши входные документы будут соответствовать сделанным здесь предположениям.

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:key name="node-by-level" match="node()" use="generate-id(preceding-sibling::LEVEL[.='BEGIN'][1])" />

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="/Data">
    <xsl:copy>
        <xsl:apply-templates select="LEVEL[.='BEGIN'][1]"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="LEVEL[.='BEGIN']">
    <LEVEL>
        <xsl:apply-templates select="key('node-by-level', generate-id())"/>
    </LEVEL>
</xsl:template>

<xsl:template match="LEVEL[.='END']"/>

</xsl:stylesheet>

Добавлено:

Чтобы справиться с дополнительной сложностью в вашем отредактированном вопросе, я бы сделал преобразование в два этапа:

XSLT 1.0 (+ функция набора узлов EXSLT)

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:key name="elem-by-level" match="*" use="generate-id(preceding-sibling::BEGIN[@level=current()/@level - 1][1])" />

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="/Data">
    <!-- first pass, using sibling recursion -->
    <xsl:variable name="first-pass">
        <xsl:apply-templates select="*[1]" mode="first-pass"/>
    </xsl:variable>
    <!-- output -->
    <xsl:copy>
        <xsl:apply-templates select="exsl:node-set($first-pass)/*[@level=0]" />
    </xsl:copy>
</xsl:template>

<!-- first pass templates -->

<xsl:template match="*" mode="first-pass">
    <xsl:param name="level" select="0"/>
    <xsl:copy>
        <xsl:attribute name="level">
            <xsl:value-of select="$level"/>
        </xsl:attribute>
        <xsl:copy-of select="@*|node()"/>
    </xsl:copy>
    <xsl:apply-templates select="following-sibling::*[1]" mode="first-pass">
        <xsl:with-param name="level" select="$level"/>
    </xsl:apply-templates>
</xsl:template>

<xsl:template match="LEVEL[.='BEGIN']" mode="first-pass">
    <xsl:param name="level" select="0"/>
    <BEGIN level="{$level}"/>
    <xsl:apply-templates select="following-sibling::*[1]" mode="first-pass">
        <xsl:with-param name="level" select="$level + 1"/>
    </xsl:apply-templates>  
</xsl:template>

<xsl:template match="LEVEL[.='END']" mode="first-pass">
    <xsl:param name="level" select="0"/>
    <xsl:apply-templates select="following-sibling::*[1]" mode="first-pass">
        <xsl:with-param name="level" select="$level - 1"/>
    </xsl:apply-templates>  
</xsl:template>

<!-- output templates -->

<xsl:template match="BEGIN">
    <LEVEL>
        <xsl:apply-templates select="key('elem-by-level', generate-id())"/>
    </LEVEL>
</xsl:template>

<xsl:template match="@level"/>

</xsl:stylesheet>

Тестирование на следующем примере ввода:

XML

<Data>
    <Item name="0A"/>
    <Item name="0B"/>
    <LEVEL>BEGIN</LEVEL>
    <Item name="1A"/>
    <LEVEL>BEGIN</LEVEL>
    <LEVEL>BEGIN</LEVEL>
    <Item name="3A"/>
    <LEVEL>END</LEVEL>
    <Item name="2A"/>
    <LEVEL>END</LEVEL>
    <Item name="1B"/>
    <LEVEL>END</LEVEL>
    <Item name="0C"/>
    <LEVEL>BEGIN</LEVEL>
    <Item name="1C"/>
    <LEVEL>END</LEVEL>
    <Item name="0D"/>
</Data>

производит:

Результат

<?xml version="1.0" encoding="utf-16"?>
<Data>
  <Item name="0A" />
  <Item name="0B" />
  <LEVEL>
    <Item name="1A" />
    <LEVEL>
      <LEVEL>
        <Item name="3A" />
      </LEVEL>
      <Item name="2A" />
    </LEVEL>
    <Item name="1B" />
  </LEVEL>
  <Item name="0C" />
  <LEVEL>
    <Item name="1C" />
  </LEVEL>
  <Item name="0D" />
</Data>

Демоверсия : https://xsltfiddle.liberty -development.net / 3NJ38Zr

1 голос
/ 28 апреля 2019

Если вы не ограничены XSLT 1, то я бы предложил двухэтапное преобразование, в XSLT 3 вы могли бы использовать аккумулятор, чтобы убедиться, что ваши элементы LEVEL украшены значением уровня вложенности на первом этапе преобразования, а затем на второмшаг это становится прямой рекурсивной xsl:for-each-group group-starting-with/group-ending-with проблемой группировки:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:output indent="yes"/>

  <xsl:mode on-no-match="shallow-copy" use-accumulators="level"/>

  <xsl:mode name="add-levels" on-no-match="shallow-copy" use-accumulators="level"/>

  <xsl:accumulator name="level" as="xs:integer" initial-value="0">
      <xsl:accumulator-rule match="LEVEL[. = 'BEGIN']" phase="start" select="$value + 1"/>
      <xsl:accumulator-rule match="LEVEL[. = 'END']" phase="end" select="$value - 1"/>
  </xsl:accumulator>

  <xsl:template match="LEVEL" mode="add-levels">
      <LEVEL level="{accumulator-before('level')}">
          <xsl:apply-templates select="@* , node()" mode="#current"/>
      </LEVEL>
  </xsl:template>

  <xsl:variable name="indexed-levels">
      <xsl:apply-templates select="/" mode="add-levels"/>
  </xsl:variable>

    <xsl:function name="mf:nest" as="node()*">
        <xsl:param name="nodes" as="node()*"/>
        <xsl:param name="level" as="xs:integer"/>
        <xsl:for-each-group select="$nodes" group-starting-with="LEVEL[. = 'BEGIN' and accumulator-before('level') = $level]">
            <xsl:choose>
                <xsl:when test="self::LEVEL[. = 'BEGIN' and accumulator-before('level') = $level]">
                    <xsl:for-each-group select="current-group() except ." group-ending-with="LEVEL[. = 'END' and accumulator-before('level') = $level]">
                        <xsl:choose>
                            <xsl:when test="current-group()[last()][self::LEVEL[. = 'END' and accumulator-before('level') = $level]]">
                                <LEVEL>
                                    <xsl:apply-templates select="mf:nest(current-group()[position() lt last()], $level + 1)"/>
                                </LEVEL>
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:apply-templates select="current-group()"/>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:for-each-group>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:apply-templates select="current-group()"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each-group>
    </xsl:function>

  <xsl:template match="/">
      <xsl:apply-templates select="$indexed-levels/node()"/>
  </xsl:template>

  <xsl:template match="/*">
      <xsl:copy>
          <xsl:apply-templates select="mf:nest(*, 1)"/>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty -development.net / bnnZWp / 8

Учитывая характераккумуляторы, было бы даже достаточно использовать только один шаг группировки, используя непосредственно значение аккумулятора вместо первой вставки его во временное дерево:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="#all"
    version="3.0">

    <xsl:output indent="yes"/>

    <xsl:mode on-no-match="shallow-copy" use-accumulators="level"/>

    <xsl:accumulator name="level" as="xs:integer" initial-value="0">
        <xsl:accumulator-rule match="LEVEL[. = 'BEGIN']" phase="start" select="$value + 1"/>
        <xsl:accumulator-rule match="LEVEL[. = 'END']" phase="end" select="$value - 1"/>
    </xsl:accumulator>

    <xsl:template match="LEVEL" mode="add-levels">
        <LEVEL level="{accumulator-before('level')}">
            <xsl:apply-templates select="@* , node()" mode="#current"/>
        </LEVEL>
    </xsl:template>

    <xsl:function name="mf:nest" as="node()*">
        <xsl:param name="nodes" as="node()*"/>
        <xsl:param name="level" as="xs:integer"/>
        <xsl:for-each-group select="$nodes" group-starting-with="LEVEL[. = 'BEGIN' and accumulator-before('level') = $level]">
            <xsl:choose>
                <xsl:when test="self::LEVEL[. = 'BEGIN' and accumulator-before('level') = $level]">
                    <xsl:for-each-group select="current-group() except ." group-ending-with="LEVEL[. = 'END' and accumulator-before('level') = $level]">
                        <xsl:choose>
                            <xsl:when test="current-group()[last()][self::LEVEL[. = 'END' and accumulator-before('level') = $level]]">
                                <LEVEL>
                                    <xsl:apply-templates select="mf:nest(current-group()[position() lt last()], $level + 1)"/>
                                </LEVEL>
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:apply-templates select="current-group()"/>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:for-each-group>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:apply-templates select="current-group()"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each-group>
    </xsl:function>

    <xsl:template match="/*">
        <xsl:copy>
            <xsl:apply-templates select="mf:nest(*, 1)"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty -development.net / bnnZWp / 7

1 голос
/ 28 апреля 2019

Вот решение XSLT 2.0, использующее технику «рекурсии братьев и сестер»:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet exclude-result-prefixes="#all" version="3.0" xmlns:f="http://local/"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    expand-text="yes">

    <xsl:strip-space elements="*"/>
    <xsl:output method="xml" indent="yes"/>

    <xsl:function name="f:depth" as="xs:integer">
        <xsl:param name="n" as="element()"/>
        <xsl:sequence select="count($n/preceding-sibling::LEVEL[.='BEGIN']) - count($n/preceding-sibling::LEVEL[.='END'])"/>
    </xsl:function>

    <xsl:template match="Data">
        <Data>
            <xsl:apply-templates select="*[1]"/>
        </Data>
    </xsl:template>

    <xsl:template match="*">
        <xsl:copy-of select="."/>
        <xsl:apply-templates select="following-sibling::*[1]"/>
    </xsl:template>

    <xsl:template match="LEVEL[.='BEGIN']">
        <LEVEL>
        <xsl:apply-templates select="following-sibling::*[1]"/>
        </LEVEL>
        <xsl:apply-templates select="following-sibling::*[f:depth(.) = f:depth(current())][1]"/>
    </xsl:template>

    <xsl:template match="LEVEL[.='END']"/>

</xsl:stylesheet>

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

В XSLT 1.0 я думаю, что вы могли бы сделать то же самое, просто расширив эту f:depth встроенную функцию.

Вероятно, было бы более эффективно вычислять глубину всех элементов за один проход и прикреплять значение в качестве атрибута к каждому элементу. Или в XSLT 3.0 глубина может быть вычислена с использованием функции памятки, или аккумулятора, или xsl:iterate. Возможно, это также можно сделать с помощью xsl: number.

0 голосов
/ 28 апреля 2019

Для запроса

Я не могу просто добавить начальный или конечный тег в зависимости от условия.

Один из способов сделать это, как показано ниже:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output method="xml" encoding="utf-8" indent="yes" />

<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
</xsl:template>

<xsl:template match="LEVEL">
    <xsl:if test=".='BEGIN'">
        <xsl:variable name="startTag">
            <xsl:text>&lt;</xsl:text>
            <xsl:value-of select="'level'" />
            <xsl:text>&gt;</xsl:text>
        </xsl:variable>
        <xsl:value-of select="$startTag" disable-output-escaping="yes" />
    </xsl:if>
    <xsl:if test=".='END'">
        <xsl:variable name="endTag">
            <xsl:text>&lt;/</xsl:text>
            <xsl:value-of select="'level'" />
            <xsl:text>&gt;</xsl:text>
        </xsl:variable>
        <xsl:value-of select="$endTag" disable-output-escaping="yes" />
    </xsl:if>
</xsl:template>
</xsl:stylesheet>

https://xsltfiddle.liberty -development.net / gWvjQfA

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...