从一些已定义的 XPATH 生成 XML

Generate a XML from some defined XPATH

我正在尝试根据一些已定义的 XPATH 从另一个 XML 生成一个 XML。

XPATH:

country/name,
country/org_id,
country/lang,
country/currency,
generate_date,
schedule/category/id,
schedule/category/name,
schedule/category/classes/class/id,
schedule/category/classes/class/duration,
schedule/category/classes/class/price,
schedule/category/classes/class/instruction_language

Xpath不包括根节点的名称,它是一个列表。

XML:

<?xml version="1.0" encoding="utf-8" ?>
<ou_schedule>
  <country>
    <name>Country Name</name>
    <org_id>Org ID</org_id>
    <lang>language</lang>
    <currency>Currency</currency>
  </country>
  <generate_date>Date</generate_date>
  <schedule>
    <category>
      <id>cat id</id>
      <name>Cat name</name>
      <classes>
        <class>
          <id>class id</id>
          <duration>class duration</duration>
          <price>price</price>
          <instruction_language>Test Data</instruction_language>
        </class>
        <class>
          <id>class id</id>
          <duration>class duration</duration>
          <price>price</price>
          <instruction_language>Test Data</instruction_language>
        </class>
      </classes>
    </category>
  </schedule>
</ou_schedule>

输出:

<?xml version="1.0" encoding="utf-8"?>
<ou_schedule>
  <country.name>country name</country.name>
  <country.org_id>org id</country.org_id>
  <country.lang>language</country.lang>
  <country.currency>currency</country.currency>
  <generate_date>date</generate_date>
  <schedule.category.name>Cat Name</schedule.category.name>
  <schedule.category.id>Cat ID</schedule.category.id>
  <schedule.category.classes.class.id>class id</schedule.category.classes.class.id>
  <schedule.category.classes.class.duration>class duration</schedule.category.classes.class.duration>
  <schedule.category.classes.class.price>price</schedule.category.classes.class.price>
  <schedule.category.classes.class.instruction_language>Test Data</schedule.category.classes.class.instruction_language>

  <country.name>country name</country.name>
  <country.org_id>org id</country.org_id>
  <country.lang>language</country.lang>
  <country.currency>currency</country.currency>
  <generate_date>date</generate_date>
  <schedule.category.name>Cat Name</schedule.category.name>
  <schedule.category.id>Cat ID</schedule.category.id>
  <schedule.category.classes.class.id>class id</schedule.category.classes.class.id>
  <schedule.category.classes.class.duration>class duration</schedule.category.classes.class.duration>
  <schedule.category.classes.class.price>price</schedule.category.classes.class.price>
  <schedule.category.classes.class.instruction_language>Test Data</schedule.category.classes.class.instruction_language>
</ou_schedule>

在这里,为了消除歧义,我用它们的祖先命名节点名称,但根节点除外,即与 XPATH 相同,但将 / 替换为 .

是否可以使用一些通用的 XSLT 来实现此目的?

我的第一个想法是:有趣的是,在这里我们将获得一个动态构建的 XSL 转换。但正如 dynamic xpath in xslt 所解释的那样,这似乎无法实现。

因此,需要第二个想法:您可以将 XSL 转换视为 XPATH 表达式的列表。从这个意义上说,您只需要一个如下所示的 XSLT 文件

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

    <!-- the following select-attributes are the set of XPATH expressions 
         (relative to /ou_schedule/schedule/category/classes/class) -->
    <xsl:template name="XPathList">
        <category_name>
            <xsl:apply-templates select="ancestor::category/name"/>
        </category_name>

        <category_id>
            <xsl:apply-templates select="ancestor::category/id"/>
        </category_id>

        <id>
            <xsl:apply-templates select="id"/>
        </id>

        <duration>
            <xsl:apply-templates select="duration"/>
        </duration>

        <price>
            <xsl:apply-templates select="price"/>
        </price>

        <instruction_language>
            <xsl:apply-templates select="instruction_language"/>
        </instruction_language>
    </xsl:template>

    <!-- Basis -->
    <xsl:template match="/">
        <ou_schedule>
            <xsl:apply-templates select="//class"/>
        </ou_schedule>
    </xsl:template>

    <xsl:template match="class">
        <xsl:copy>
            <xsl:call-template name="XPathList"/>
        </xsl:copy>    
    </xsl:template>
</xsl:stylesheet>

好吧,可以用更紧凑的方式编写此转换。但目的是将 "having a list of XPATHs to transform an XML" 的想法转化为代码。

Is it possible to achieve this using some generic XSLT?

如果有两种解决方案:一种用于 XSLT 1.0,另一种用于 XSLT 2.0,则可以(相当人为地)将它们合并为一个,使用 XSLT 2.0 条件编译技术,将排除在 "pre-compile time" XSLT 1.0 解决方案的模板和声明。另一方面,XSLT 1.0 解决方案将在向前兼容模式下运行,并且还将为其模板指定更高的优先级(高于 XSLT 2.0 解决方案模板的优先级),因此没有 XSLT 2.0 解决方案的模板是 selected 执行,当转换 运行 使用 XSLT 1.0 处理器时。

可以将此视为一个有趣的练习,并按照 Michael Kay "XSLT 2.0 and XPath 2.0" 书中的示例,第 3 章:"Stylesheet Structure",第 "Writing Portable stylesheets" 节,小节:"Conditional Compilation"。示例(在我的版本中)在第 128 页。


这是一个简短的 XSLT 2.0 解决方案(如果省略参数值,则为 18 行),纯(无扩展函数),不使用显式 XSLT 条件指令或任何 xsl:variable。连 tokenize() 函数都没用到:

<xsl:stylesheet version="2.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>
 <xsl:param name="pPaths" as="xs:string+" select=
  "'country/name',
   'country/org_id',
   'country/lang',
   'country/currency',
   'generate_date',
   'schedule/category/id',
   'schedule/category/name',
   'schedule/category/classes/class/id',
   'schedule/category/classes/class/duration',
   'schedule/category/classes/class/price',
   'schedule/category/classes/class/instruction_language'"/>

  <xsl:template match="/*">
    <xsl:copy><xsl:apply-templates/></xsl:copy>
  </xsl:template>

  <xsl:template match=
   "*/*[string-join((ancestor::*[position() ne last()]| .)/name(), '/') = $pPaths]">
    <xsl:element 
       name="{string-join((ancestor::*[position() ne last()]|.)/name(), '.')}">
      <xsl:value-of select="."/>
    </xsl:element>
  </xsl:template>
  <xsl:template match="text()"/>
</xsl:stylesheet>

解决方案 2:

此处将资源(文件)的URI(文件路径)作为参数传递。此文件包含所有需要的 XPath 表达式 -- 每个单独一行。

<xsl:stylesheet version="2.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>
 <xsl:param name="pFilePath" select="'file:///C:/temp/expressions.txt'"/>

 <xsl:variable name="vExprs" select="tokenize(unparsed-text($pFilePath), '\r?\n')"/>

  <xsl:template match="/*">
    <xsl:copy><xsl:apply-templates/></xsl:copy>
  </xsl:template>

  <xsl:template match=
   "*/*[string-join((ancestor::*[position() ne last()]| .)/name(), '/') = $vExprs]">
    <xsl:element name=
       "{string-join((ancestor::*[position() ne last()]|.)/name(), '.')}">
      <xsl:value-of select="."/>
    </xsl:element>
  </xsl:template>
  <xsl:template match="text()"/>
</xsl:stylesheet>

解决方案 3:

如果对于输入的 XPath 表达式已知它们 select 具有单个文本节点子节点的元素(这是最初的情况 -提供了输入 XPath 表达式并提供了来源 XML 文档):

<xsl:stylesheet version="2.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>
 <xsl:param name="pFilePath" select="'file:///C:/temp/expressions.txt'"/>

 <xsl:variable name="vExprs" select="tokenize(unparsed-text($pFilePath), '\r?\n')"/>

  <xsl:template match="/*">
    <xsl:copy><xsl:apply-templates/></xsl:copy>
  </xsl:template>

  <xsl:template match=
   "text()[string-join(ancestor::*[position() ne last()]/name(), '/') = $vExprs]">
    <xsl:element 
      name="{string-join(ancestor::*[position() ne last()]/name(), '.')}">
      <xsl:value-of select="."/>
    </xsl:element>
  </xsl:template>
  <xsl:template match="text()"/>
</xsl:stylesheet>