后续:根据属性值中的分隔符创建(宏)父子元素

时间:2014-12-16 09:59:21

标签: xslt xslt-1.0 xslt-grouping

此后是对question posted here last year的跟进。 仍然是一个新手,我正在努力(再次......)转换 - 使用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'元素。在'Name'属性中,将'Name'属性转换为子元素,并选择其'Value'属性的值。
  2. 对于所做的每个'Property'元素包含separator(s)''。在'Name'属性中,创建:
    • a)一个(祖父)父元素,使用'name'属性中的'substring-before'分隔符 - '递归直到最后一次出现'(不确定如何描述;参见下面的所需输出),和
    • b)在'Name'属性中使用'substring-after' last 分隔符的子元素,并选择其'Value'属性的值。
  3. 因此,所需的输出应如下所示:

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

    不是我正在寻找的......任何帮助都会再次受到赞赏!

2 个答案:

答案 0 :(得分: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>

答案 1 :(得分:0)

如果您很幸运,并且您的处理器支持以下EXSLT功能:

  • exsl:node-set()
  • str:tokenize()
  • set:distinct()

然后这可能适合你:

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>