【问题标题】:How to get similar properties using XSLT如何使用 XSLT 获得相似的属性
【发布时间】:2014-03-01 19:35:37
【问题描述】:

我有一个类似的 XML 文件

<node>
  <properties>
    <property name="titleENG">English title</property>
    <property name="titleFRE">French title</property>
    <property name="descENG">English description</property>
    <property name="descFRE">French description</property>
    ...
  </properties>
</node>

作为输出,我想要一个类似的 CSV 文件

title, English title, French title
desc, English description, French description
...

假设我知道可用的语言,但我不知道可用的属性(title、desc、...)

我可以用下面的 XSLT 得到英文字符串

<?xml version="1.0" encoding="ISO-8859-1"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >

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

  <xsl:template match="property">
    <xsl:if test="@name[ends-with(.,'ENG')]">
      <xsl:variable name="key" select="substring(@name,1,string-length(@name)-3)" />
      <xsl:value-of select="$key"/>, <xsl:value-of select="./text()"/><xsl:text>&#10;     </xsl:text>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

但是在'if'里面,我还需要name=concat($key,'FRE')的属性值,我该怎么做呢? 任何帮助表示赞赏。

编辑 我想我可以将我的问题改写为(因为我可以轻松地将我当前的集合转换为这个):假设我有一个类似的 XML

<node>
 <properties>
  <property name="dummy"></property>
  <property name="title" lang="ENG">English title</property>
  <property name="title" lang="FRE">French title</property>
  <property name="desc" lang="ENG">English description</property>
  <property name="desc" lang="FRE">French description</property>
  ...
 </properties>
</node>

在这种情况下,如何创建包含两行的 (CSV) 输出,第一列中的名称和下一列中的英语、法语翻译?

【问题讨论】:

  • 1. "假设我知道可用的语言" 这些知识将存在于哪里? 2. 您的样式表显示的是 1.0 版,但您使用的是 2.0 版函数 ends-with()。 3. 如果你的输出是 CSV,你为什么要复制节点?
  • 1. 我认为这是一个单独的问题;最坏的情况是我对它们进行硬编码,最好的情况是我尝试使用某种数组变量,但我还没有看过它。 2. 我的在线 xslt 测试工具没有报错;感谢您指出了这一点。 3. 我猜你的意思是我可以省略第一个模板?在某一时刻,我似乎需要它通过 xml 进行递归,但显然这确实不再需要了。感谢您指出这一点。
  • 1. 这不是一个单独的问题,因为我们需要知道从哪里获取它们以便想出一个可行的解决方案;如果您说它们可以硬编码到样式表中,那么我们就知道了。我们仍然不知道2.您使用的是 XSLT 1.0 还是 2.0。我的意思是在生产中,而不是用于在线测试。
  • XSLT 2.0 没问题(抱歉没有更明确)
  • 如果您可以更改源 XML,您还可以考虑使用标准语言代码 (ISO 639-1) 和 xml:lang 属性来指定您的语言。这样,您还可以使用lang() 函数来选择节点。并且您可以导入语言代码表(我相信在 IANA.org 中您可能会在 XML、CSV 等中找到它们)

标签: xml xslt


【解决方案1】:

XSLT 2.0 还可以

实际上,我没有时间切换到 XSLT 2.0 模式,这两种模式都适用:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="http://www.example.com/my">

<xsl:output method="text" encoding="UTF-8"/>

<xsl:variable name="root" select="/" />

<xsl:key name="prop-by-prefix" match="property" use="substring(@name, 1, string-length(@name)-3)" />
<xsl:key name="prop-by-name" match="property" use="@name" />

<my:languages>
    <lang>ENG</lang>
    <lang>FRE</lang>
</my:languages>

<xsl:template match="/">
    <xsl:for-each select="node/properties/property[count(. | key('prop-by-prefix', substring(@name, 1, string-length(@name)-3))[1]) = 1]">
        <xsl:variable name="prefix" select="substring(@name, 1, string-length(@name)-3)" />
        <xsl:value-of select="$prefix"/>
        <xsl:text>,</xsl:text>
        <xsl:for-each select="document('')/xsl:stylesheet/my:languages/lang">
            <xsl:variable name="name" select="concat($prefix, .)"/>
            <!-- switch context back to source document in order to use key -->
            <xsl:for-each select="$root">
                <xsl:value-of select="key('prop-by-name', $name)"/>
            </xsl:for-each>
            <xsl:if test="position()!=last()">
                <xsl:text>,</xsl:text>
            </xsl:if>
        </xsl:for-each>
        <xsl:if test="position()!=last()">
            <xsl:text>&#10;</xsl:text>
        </xsl:if>
    </xsl:for-each>
</xsl:template>
</xsl:stylesheet>

应用于您的输入:

<node>
  <properties>
    <property name="titleENG">English title</property>
    <property name="titleFRE">French title</property>
    <property name="descENG">English description</property>
    <property name="descFRE">French description</property>
  </properties>
</node>

得到如下结果:

title,English title,French title
desc,English description,French description

补充:

我刚刚注意到您更改了输入。这使它变得更加简单:

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

<xsl:key name="prop-by-name" match="property" use="@name" />

<xsl:template match="/">
    <xsl:for-each select="node/properties/property[count(. | key('prop-by-name', @name)[1]) = 1][@lang]">
        <xsl:value-of select="@name"/>
        <xsl:text>,</xsl:text>
        <xsl:for-each select="key('prop-by-name', @name)">
            <xsl:value-of select="."/>
            <xsl:if test="position()!=last()">
                <xsl:text>,</xsl:text>
            </xsl:if>
        </xsl:for-each>
        <xsl:if test="position()!=last()">
            <xsl:text>&#10;</xsl:text>
        </xsl:if>
    </xsl:for-each>
</xsl:template>
</xsl:stylesheet>

如果您使用的是 XSLT 2.0(或支持 EXSLT set:distinct) 函数的 XSLT 1.0 处理器),则可以使用它来代替 Muenchian 分组。

【讨论】:

  • 这确实有效,谢谢!你能再解释一件事吗:为什么我们需要&lt;xsl:for-each select="$root"&gt;?为什么我们不能写&lt;xsl:value-of select="//property[@name=$name]"/&gt;
  • @MarcVanDaele 我更喜欢使用密钥来检索“相关”数据。但是,您不能跨文档使用密钥,因此我使用&lt;xsl:for-each select="$root"&gt; 将上下文临时切换回源 XML 文档以便使用密钥。
【解决方案2】:

您可以在 if 之外声明您的 $key 变量:

<xsl:variable name="key" select="substring(@name,1,string-length(@name)-3)" />

并在@name 属性中使用substring-after$key 获取后缀:

<xsl:variable name="suffix" select="substring-after(@name, $key)"/>

然后您可以在不知道后缀的情况下独立进行测试:

<xsl:if test="@name[ends-with(.,$suffix)]">

【讨论】:

  • 我猜在这种情况下 if-test 变成了无操作,因为它总是正确的。请注意,还有与语言无关的属性 dummy (缺少重要信息,对此感到抱歉)。我仍在努力获得包含密钥的单行及其所有翻译(例如“标题,英文标题,法文标题”)
  • 是的。你是对的。有必要提取后缀并将其与可用语言进行比较。 dummy 属性也可以被过滤掉。当我回到我的电脑时,我会编辑答案。但我认为迈克尔在那里的回答解决了你的问题。
猜你喜欢
  • 1970-01-01
  • 2016-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多