【问题标题】:XSL to iterate flat XML structureXSL 迭代平面 XML 结构
【发布时间】:2012-01-23 09:12:16
【问题描述】:

我有以下 XML:

<p:persons>
<p:surname>Surname1</p:surname>
<p:forename>Forename1</p:forname>
<p:surname>Surname2</p:surname>
</p:persons>

姓和名节点都是可选的。我想将此 XML 转换为 HTML 或纯文本并显示如下内容:

-- Person1 --
surname: Surname1
forename: Forename1

-- Person2 --
surname: Surname2
forename: UNKNOWN

请注意,XML 也可能看起来像,因为名字和姓氏都是可选的:

<p:persons>
<p:forename>Forename1</p:surname>
<p:surname>Surname1</p:surname>
<p:forename>Forename2</p:forname>
</p:persons>

谢谢!

【问题讨论】:

  • 在您的示例中,为什么名字“Forename1”映射到人员 1?因为它是第一个p:forename 节点?
  • 您输入的 XML 的结构没有意义!你怎么知道哪个姓氏与哪个姓氏有关?我希望它们被分组在 p:person 元素中。
  • 如果&lt;p:forename&gt;不属于前面的&lt;p:surname&gt;,应该根据什么规则确定哪些节点属于一起?你试过什么?
  • 看起来唯一有效的答案是:“不会发生”。无论是谁给你这个规范,都必须重新考虑和重构。
  • @danleadgy 编写源 XML 的人都需要被一条湿漉漉的鱼拍打。这根本没有意义! XML 是一种富有表现力和结构化的“语言”。将其用作命名值的有序列表非常可笑!

标签: xml xslt xpath


【解决方案1】:

虽然我认为这个规范严重错误,但这里有一个 sn-p 可以满足您的要求:

<xsl:template name="writePersons">
  <xsl:param name="list"/>
  <xsl:param name="index" select="1"/>

  <xsl:if test="count($list/p:forename[$index]|$list/p:surname[$index]) &gt; 0">
    <xsl:text>-- Person</xsl:text>
    <xsl:value-of select="$index"/>
    <xsl:text> --&#x0A;surname: </xsl:text>
    <xsl:choose>
      <xsl:when test="$list/p:surname[$index]">
        <xsl:value-of select="$list/p:surname[$index]"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:text>UNKNOWN</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:text>&#x0A;forename: </xsl:text>
    <xsl:choose>
      <xsl:when test="$list/p:forename[$index]">
        <xsl:value-of select="$list/p:forename[$index]"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:text>UNKNOWN</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:text>&#x0A;&#x0A;</xsl:text>
    <xsl:call-template name="writePersons">
      <xsl:with-param name="index" select="$index + 1"/>
      <xsl:with-param name="list" select="$list"/>
    </xsl:call-template>
  </xsl:if>
</xsl:template>

【讨论】:

  • 非常感谢!我喜欢你如何使用索引来获取每个人的字段。一个非常优雅的解决方案!
  • @danleadgy:您可能对更简单、更短的解决方案感兴趣 :)
  • 这个解决方案正确吗?如果数据按顺序包含姓氏、姓氏、姓氏、名字,则此模板将假定名字与第一个姓氏一起出现。我想规范中的规则并不清楚,但我想它应该与最后一个姓氏一起使用。但我同意规范很糟糕。
  • LarsH:我推断这是问题的第一个修订版中的正确行为,其中输入看起来略有不同。根据当前的版本,Dimitre 的解决方案很可能是正确的。
【解决方案2】:

即使您已经接受了一个答案,我也会毫不犹豫地接受...因为到目前为止,我认为其他任何一种解决方案都不正确(尽管一个与给定的示例输出匹配)。

这是我的实现:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   version="2.0" xmlns:p="p">
   <xsl:output method="text" />

   <xsl:template match="/p:persons">
      <xsl:apply-templates select="*[1]">
         <xsl:with-param name="i" select="1" />
      </xsl:apply-templates>
   </xsl:template>

   <!-- This template is applied to an element whenever it is
      for a new person, i.e. when there is not a preceding
      sibling element belonging to the same person. -->
   <xsl:template match="p:forename | p:surname">
      <xsl:param name="i"/>
      <xsl:variable name="next" select="following-sibling::*[1]"/>
      <xsl:variable name="surname" select="if (self::p:surname) then . else
            if ($next[self::p:surname]) then $next else 'UNKNOWN'"/>
      <xsl:variable name="forename" select="if (self::p:forename) then . else
         if ($next[self::p:forename]) then $next else 'UNKNOWN'"/>
      <xsl:value-of select="concat(if ($i > 1) then '&#10;' else '',
         '--- Person', $i, '---&#10;surname: ',
         $surname, '&#10;forename: ', $forename, '&#10;')"/>

      <xsl:choose>
         <xsl:when test="local-name($next) = local-name(.)">
            <!-- Only one name is supplied. Start processing
            the next sibling as a new person. -->
            <xsl:apply-templates select="following-sibling::*[1]">
               <xsl:with-param name="i" select="$i + 1"/>
            </xsl:apply-templates>
         </xsl:when>
         <xsl:otherwise>
            <!-- Both names are supplied. Skip the second name
            and start processing the sibling after it.-->
            <xsl:apply-templates select="following-sibling::*[2]">
               <xsl:with-param name="i" select="$i + 1"/>
            </xsl:apply-templates>
         </xsl:otherwise>
      </xsl:choose>
   </xsl:template>
</xsl:stylesheet>

我对不完整规范的解释是:从第一个名字/姓氏元素开始。如果前两个元素是名字和姓氏(以任意顺序),则将两者都用于第一人称,然后将第三个元素作为新人开始处理。

否则,使用第一个元素并为未提供的名称提供“未知”。然后作为新人开始处理下一个元素。

有了这个规范解释,我很确定如果没有某种递归就不可能正确实现,因为每个元素与前面或后面的关联可能取决于前面的关联方式(例如,名字在前?),这可能取决于之前的人,等等。

上面的第一个示例输入给出了所需的输出。 第二个(标签更正)给出:

--- Person1---
surname: Surname1
forename: Forename1

--- Person2---
surname: UNKNOWN
forename: Forename2

一个更完整的例子:

<p:persons xmlns:p="p">
   <p:surname>Surname1</p:surname>
   <p:forename>Forename1</p:forename>
   <p:surname>Surname2</p:surname>
   <p:surname>Surname3</p:surname>
   <p:surname>Surname4</p:surname>
   <p:forename>Forename4</p:forename>
   <p:forename>Forename5</p:forename>
   <p:surname>Surname5</p:surname>
</p:persons>

给予

--- Person1---
surname: Surname1
forename: Forename1

--- Person2---
surname: Surname2
forename: UNKNOWN

--- Person3---
surname: Surname3
forename: UNKNOWN

--- Person4---
surname: Surname4
forename: Forename4

--- Person5---
surname: Surname5
forename: Forename5

【讨论】:

    【解决方案3】:

    这是一个更简单的解决方案(没有显式递归,没有xsl:choosexsl:otherwisexsl:call-templatexsl:paramxsl:with-param):

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:p="p">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>
    
     <xsl:param name="pSurnameUnknown">surname: UNKNOWN</xsl:param>
    
     <xsl:template match=
     "p:surname
        [not(preceding-sibling::*[1][self::p:forename])]">
    
         --- Person<xsl:text/>
         <xsl:number level="single" count=
         "p:forename|p:surname[not(preceding-sibling::*[1]
                                         [self::p:forename])]"/>
         <xsl:text>---</xsl:text>
         forename: UNKNOWN
         surname: <xsl:value-of select="."/>
     </xsl:template>
    
     <xsl:template match="p:forename">
    
         --- Person<xsl:text/>
         <xsl:number level="single" count=
         "p:forename|p:surname[not(preceding-sibling::*[1]
                                        [self::p:forename])]"/>
         <xsl:text>---</xsl:text>
         forename: <xsl:value-of select="."/>
       <xsl:if test="not(following-sibling::*[1][self::p:surname])">
         <xsl:copy-of select="$pSurnameUnknown"/>
       </xsl:if>
       <xsl:apply-templates mode="second"
            select="following-sibling::*[1][self::p:surname]"/>
     </xsl:template>
    
     <xsl:template match="p:surname" mode="second">
         surname: <xsl:value-of select="."/>
     </xsl:template>
     <xsl:template match="p:surname"/>
    </xsl:stylesheet>
    

    应用于此 XML 文档时(提供的,格式正确):

    <p:persons xmlns:p="p">
        <p:surname>Surname1</p:surname>
        <p:forename>Forename2</p:forename>
        <p:surname>Surname2</p:surname>
    </p:persons>
    

    产生想要的正确结果

     --- Person1---
     forename: UNKNOWN
     surname: Surname1
    
     --- Person2---
     forename: Forename2
     surname: Surname2
    

    解释

    1. 正确使用模板和模式匹配。

    2. 正确使用&lt;xsl:number&gt;

    【讨论】:

    • 这个解决方案在功能上与我的不同。它希望 forenamesurename 成对出现(其中任何一个都可能丢失),而我的则匹配第 n 个姓氏和第 n 个姓氏,无论名字和姓氏在列表中如何组合。
    • @flyx:如果是这样,您的解决方案不正确-请注意问题。
    • dan Leadgy 的评论可以用两种方式解释。您的输出与问题中指定的不同,我的没有。我不是说你错了,我只是指出我们的答案之间的区别。
    • @Dimitre,我也不确定您的输出是否正确。 OP说在输入surname, forename, surname的情况下,名字应该用第一人输出。他还说,当相关名称缺失时,他想要“forename: UNKNOWN”或“surname: UNKNOWN”。
    • @LarsH:谢谢,我也注意到了这一点——我会将“未知”部分添加到其他正确的解决方案中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-06
    • 2019-02-17
    • 1970-01-01
    • 1970-01-01
    • 2012-12-13
    相关资源
    最近更新 更多