2014-12-16 1 views
1

Ниже приводится описание question posted here last year. Все еще будучи новичком, я борюсь (снова ...), чтобы преобразовать - используя XSLT 1.0 - следующий XML, который описывает объекты (обратите внимание на небольшое изменение ввода - «BC *» - на предыдущий вопрос):Последующие действия: создавать (грандиозные) элементы parent-child на основе разделителя в значениях атрибутов

<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'-элемент, который делает не содержать разделитель»» в атрибуте «Имя», преобразуйте атрибут «Name» в дочерний элемент и выберите значение его атрибута «Значение».
  2. Для каждого элемента «Свойства», который делает, содержит разделители (ы) '.' в атрибуте «Имя», создайте:
    • a) Родительский элемент (грандиозный) с использованием «подстроки перед» разделитель в атрибуте «Имя» - «рекурсивно до последнего появления» (не уверен, как описать, увидеть желаемый результат ниже) и
    • b) дочерний элемент с использованием «подстроки-после» последний разделитель в атрибуте «Имя» и выберите значение его атрибута «Значение». Поэтому

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

<?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> 

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

+0

Я не думаю, что ваш вывод следует ваши правила.Например, вы не ** создали родительский элемент B для 'Name =" B.C.Id "'; вы использовали ранее созданный элемент B для 'Name =" B.Id "'. Если вы не добавите еще каких-либо ограничений, эта проблема не определена. –

+0

Это также поможет узнать, какой именно процессор XSLt 1.0 вы используете. Поскольку вход содержит данные с разделителями-точками (вместо правильной разметки XML), вы могли бы сэкономить много работы, используя некоторые функции расширения - если ваш процессор сможет их поддерживать. –

+0

Что касается вашего первого замечания, вы на самом деле правы; простите за это. В принципе: элементы нужно создавать иерархически, используя строки до/после разделителя. Если строка не содержит разделителя, тогда создайте элемент as-is. Если родительский элемент «будет создан» уже существует, не создавайте его, а продолжайте его создание (ren) (правило 2). Надеюсь, это прояснит проблему. – user25160809

ответ

1

Интересная проблема, но у меня есть только время, чтобы решить эту проблему с помощью 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> 
+0

Благодарим вас за решение: оно производит точно нужный результат. К сожалению, хотя я ограничен использованием XSLT 1.0 ... – user25160809

0

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

  • exsl: node- множество()
  • ул: токенизировать()
  • набор: различны()

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

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> 
Смежные вопросы