关于xslt:从某些已定义的XPATH生成XML

Generate a XML from some defined XPATH

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

XPATH:

1
2
3
4
5
6
7
8
9
10
11
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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>

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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来实现?


Is it possible to achieve this using some generic XSLT?

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

您可以将其视为一项有趣的练习,并按照Michael Kay的书中的示例" XSLT 2.0和XPath 2.0",第3章:"样式表结构","编写可移植的样式表"部分,小节:"有条件的编译""。示例(在我所拥有的版本中)在第128页。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<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表达式-每个表达式都位于单独的一行中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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), '\
?\
')"
/>

  <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表达式选择的元素具有单个text-node子元素(并且最初提供的输入XPath表达式就是这种情况),则可以进一步优化和简化这两个先前的解决方案。 XML文档源):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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), '\
?\
')"
/>

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


我的第一个想法是:有趣的是,在这里,我们将获得一个动态构建的XSL转换。但这似乎无法实现,因为xslt中的动态xpath解释了。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?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>

好吧,本来可以以更紧凑的方式编写此转换的。但是,目的是将"具有XPATH列表以转换XML"的思想转换为代码。