Последующие действия: создание (грандиозных) родительско-дочерних элементов на основе разделителя в значениях атрибутов

Настоящим продолжением вопроса , размещенного здесь в прошлом году. Все еще будучи новичком, я изо всех сил пытаюсь (снова...) преобразовать - используя XSLT 1.0 - следующий XML, который описывает объекты (обратите внимание на небольшое изменение ввода - "BC *" - на предыдущий вопрос):

<Data>
    <Object>
        <Property Name="Id" Value="001"/>
        <Property Name="P.Id" Value="Id P"/>
        <Property Name="P.Description" Value="Descr P"/>
        <Property Name="A.Id" Value="Id A" />
        <Property Name="A.Description" Value="Descr A"/>
        <Property Name="B.Id" Value="Id B"/>
        <Property Name="B.Description" Value="Descr B"/>
        <Property Name="B.C.Id" Value="B.C.Id"/>
        <Property Name="B.C.Description" Value="B.C.Description"/>
    </Object>
</Data>

Для получения желаемого результата должны применяться следующие правила:

  1. Для каждого элемента 'Property', который не содержит разделитель '.' в атрибуте «Имя» преобразовать атрибут «Имя» в дочерний элемент и выбрать значение его атрибута «Значение».
  2. For each 'Property'-element that does contain separator(s) '.' in the 'Name'-attribute, create:
    • a) a (grand)parent element using 'substring-before' the separator in the 'Name'-attribute - 'recursively until the last occurence' (not sure how to describe; see desired output below), and
    • б) дочерний элемент, использующий «подстроку-после» последнего разделителя в атрибуте «Имя» и выбирает значение его атрибута «Значение».

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

<?xml version="1.0" encoding="UTF-8"?>
<Root>
    <ObjectData>
        <Id>001</Id>
        <P>
            <Id>Id P</Id>
            <Description>Descr P</Description>
        </P>
        <A>
            <Id>Id A</Id>
            <Description>Descr A</Description>
        </A>
        <B>
            <Id>Id B</Id>
            <Description>Descr B</Description>
            <C>
                <Id>B.C.Id</Id>
                <Description>B.C.Description</C.Description>
            </C>
        </B>
    </ObjectData>
</Root>

В настоящее время у меня есть следующий код:

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

    <xsl:key name="kPropertyByName" match="Property[contains(@Name, '.')]" use="concat(generate-id(..), '|', substring-before(@Name,'.'))"/>

    <xsl:template match="Data">
        <Root>
            <xsl:apply-templates/>
        </Root>
    </xsl:template>

    <xsl:template match="Object">
        <ObjectData>
            <xsl:apply-templates select="Property[not(contains(@Name, '.'))]"/>
                <xsl:for-each select="Property[generate-id(.) = 
                    generate-id(key('kPropertyByName',
                    concat(generate-id(..), '|', substring-before(@Name,'.')))[1])
                    ] ">
                <xsl:apply-templates select="." mode="parent"/>
                </xsl:for-each>
        </ObjectData>
    </xsl:template>

    <xsl:template match="Property[not(contains(@Name, '.'))]">
        <xsl:element name="{@Name}">
            <xsl:value-of select="@Value"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="Property[(contains(@Name, '.'))]" mode="parent">
        <xsl:element name="{substring-before(@Name,'.')}">
            <xsl:apply-templates mode="child" select="../Property[
                substring-before(current()/@Name,'.') = 
                substring-before(./@Name,'.')]"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="Property[(contains(@Name, '.'))]" mode="child">
        <xsl:element name="{substring-after(@Name,'.')}">
            <xsl:value-of select="@Value"/>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

Что дает мне следующий вывод - наличие (нежелательных) "неразделенных" C.*-элементов:

<?xml version="1.0" encoding="UTF-8"?>
<Root>
    <ObjectData>
        <Id>001</Id>
        <P>
            <Id>Id P</Id>
            <Description>Descr P</Description>
        </P>
        <A>
            <Id>Id A</Id>
            <Description>Descr A</Description>
        </A>
        <B>
            <Id>Id B</Id>
            <Description>Descr B</Description>
            <C.Id>B.C.Id</C.Id>
            <C.Description>B.C.Description</C.Description>
        </B>
    </ObjectData>
</Root>

Не то, что я ищу ... Любая помощь будет очень признательна снова!


person user25160809    schedule 16.12.2014    source источник
comment
Я не думаю, что ваш вывод соответствует вашим правилам. Например, вы не создали родительский элемент B для Name="B.C.Id"; вы использовали ранее созданный элемент B для Name="B.Id". Если вы не добавите еще несколько ограничений, эта проблема не будет четко определена.   -  person michael.hor257k    schedule 16.12.2014
comment
Также было бы полезно узнать, какой конкретный процессор XSLt 1.0 вы используете. Поскольку входные данные содержат данные, разделенные точками (вместо правильной XML-разметки), вы можете сэкономить много работы, используя некоторые функции расширения, если ваш процессор может их поддерживать.   -  person michael.hor257k    schedule 16.12.2014
comment
Что касается вашего первого замечания, вы действительно правы; простите за это. В основном: элементы должны создаваться иерархически, используя строки до/после разделителя. Если строка не содержит разделителя, создайте элемент как есть. Если родительский элемент, который нужно создать, уже существует, то не создавайте его, а продолжайте создавать его дочерние элементы (правило 2). Я надеюсь, что это прояснит проблему.   -  person user25160809    schedule 16.12.2014
comment
Используемый процессор XSLT 1.0 является встроенным процессором .NET (XslCompiledTransform).   -  person user25160809    schedule 16.12.2014
comment
Боюсь, я не знаком с процессорами MS; вам придется искать документацию. FWIW, я опубликовал решение, основанное на некоторых функциях расширения EXSLT. Это уже довольно сложно; без этих функций вы проделаете очень много работы. За это можно поблагодарить авторов исходного документа, которые, видимо, не имеют ни малейшего представления о том, как использовать XML.   -  person michael.hor257k    schedule 16.12.2014


Ответы (2)


Интересная проблема, но у меня есть только время, чтобы решить ее с помощью XSLT 2.0:

<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="xs mf"
    version="2.0">

<xsl:output indent="yes"/>

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

<xsl:function name="mf:group" as="element()*">
  <xsl:param name="elements" as="element()*"/>
  <xsl:param name="index" as="xs:integer"/>
  <xsl:for-each-group select="$elements" group-adjacent="tokenize(@Name, '\.')[$index]">
    <xsl:choose>
      <xsl:when test="not(current-group()[2])">
        <xsl:apply-templates select="."/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:element name="{current-grouping-key()}">
          <xsl:apply-templates select="current-group()[$index = count(tokenize(@Name, '\.'))]"/>
          <xsl:sequence select="mf:group(current-group()[not($index = count(tokenize(@Name, '\.')))], $index + 1)"/>
        </xsl:element>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:for-each-group>
</xsl:function>

<xsl:template match="Object">
  <xsl:copy>
    <xsl:sequence select="mf:group(*, 1)"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="Property[@Name]">
  <xsl:element name="{tokenize(@Name, '\.')[last()]}">
    <xsl:value-of select="@Value"/>
  </xsl:element>
</xsl:template>

</xsl:stylesheet>
person Martin Honnen    schedule 16.12.2014
comment
Спасибо за ваше решение: оно дает именно тот результат, который вам нужен. К сожалению, я ограничен использованием XSLT 1.0... - person user25160809; 16.12.2014

Если вам повезло и ваш процессор поддерживает следующие функции EXSLT:

  • exsl: набор узлов ()
  • ул: токенизировать ()
  • набор: отдельный ()

тогда это может сработать для вас:

XSLT 1.0 + EXSLT

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:set="http://exslt.org/sets"
xmlns:str="http://exslt.org/strings" 
extension-element-prefixes="exsl set str">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>

<xsl:template match="/Data">
    <Root>
        <xsl:apply-templates select="Object"/>
    </Root>
</xsl:template>

<xsl:template match="Object">
    <ObjectData>

        <xsl:variable name="nodes">
            <xsl:for-each select="Property">
                <xsl:variable name="value" select="@Value" />
                <xsl:for-each select="str:tokenize(@Name, '.')">
                    <node parent="{preceding-sibling::*[1]}">
                        <xsl:if test="position()=last()">
                            <xsl:attribute name="value">
                                <xsl:value-of select="$value"/>
                            </xsl:attribute>
                        </xsl:if>
                        <xsl:value-of select="." />
                    </node>
                </xsl:for-each>
            </xsl:for-each>
        </xsl:variable>
        <xsl:variable name="nodes-set" select="exsl:node-set($nodes)/node" />

        <xsl:variable name="ancestors" select="set:distinct($nodes-set[not(string(@parent))])" />

        <xsl:apply-templates select="$ancestors">
            <xsl:with-param name="nodes-set" select="$nodes-set"/>
        </xsl:apply-templates>

    </ObjectData>
</xsl:template>

<xsl:template match="node"> 
    <xsl:param name="nodes-set"/>
    <xsl:element name="{.}">
        <xsl:variable name="children" select="set:distinct($nodes-set[@parent=current()])" />
        <xsl:apply-templates select="@value"/>
        <xsl:apply-templates select="$children">
            <xsl:with-param name="nodes-set" select="$nodes-set"/>
        </xsl:apply-templates>
    </xsl:element>
</xsl:template>

<xsl:template match="@value">   
    <xsl:apply-templates/>
</xsl:template>

</xsl:stylesheet>

Результат (при тестировании с libxslt):

<?xml version="1.0" encoding="utf-8"?>
<Root>
  <ObjectData>
    <Id>001</Id>
    <P>
      <Id>Id P</Id>
      <Description>Descr P</Description>
    </P>
    <A>
      <Id>Id A</Id>
      <Description>Descr A</Description>
    </A>
    <B>
      <Id>Id B</Id>
      <Description>Descr B</Description>
      <C>
        <Id>B.C.Id</Id>
        <Description>B.C.Description</Description>
      </C>
    </B>
  </ObjectData>
</Root>
person michael.hor257k    schedule 16.12.2014