使用XSLT生成具有重复行的Text文件的XML转换

时间:2014-09-15 10:32:35

标签: xml xslt

我是XSLT的新手,我正在研究一个小例子,我希望使用XSLT转换XML输入文件以生成文本文件。

这是我输入的xml文件:

<?xml version="1.0" ?>
<result>
    <users>
        <user>
            <user-name>user 1</user-name>
            <blood-group>A-</blood-group>
            <id>4</id>
            <col1>c1</col1>
            <col2>c2</col2>
            <col4>c4</col4>
        </user>
        <user>
            <user-name>user 2</user-name>
            <blood-group>B+</blood-group>
            <id>3</id>
            <col3>c3</col3>
            <col4>c4</col4>
        </user>
    </users>
</result>

我希望在使用XSLT转换后得到这样的输出:

User Name | Blood Group | Id | col1 | col2 | col3 | col4
user 1    | A-          | 4  | c1   | null | null | null
user 1    | A-          | 4  | null | c2   | null | null 
user 1    | A-          | 4  | null | null | null | c4
user 2    | B+          | 3  | null | null | c3   | null
user 2    | B+          | 3  | null | null | null | c4

这个想法是每个记录将以记录所具有的col元素的数量重复,并且输出文本的每一行将具有特定单个col元素的值以及所有其他元素col的值为null

我已经创建了一个这样的XSL文件:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="str">

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

<xsl:template match="/result">
    <xsl:text>User Name | Blood Group | Id | col1 | col2 | col3 | col4&#10;</xsl:text>
    <xsl:for-each select="users/user">
        <xsl:value-of select="str:align(user-name, '          | ', 'left')" />
        <xsl:value-of select="str:align(blood-group, '            | ', 'left')" />
        <xsl:value-of select="str:align(id, '   | ', 'left')" />
        <xsl:value-of select="str:align(col1, '     | ', 'left')" />
        <xsl:value-of select="str:align(col2, '     | ', 'left')" />
        <xsl:value-of select="str:align(col3, '     | ', 'left')" />
        <xsl:value-of select="col4" />
        <xsl:if test="position()!=last()">
            <xsl:text>&#10;</xsl:text>
        </xsl:if>
    </xsl:for-each>
</xsl:template>
</xsl:stylesheet>

通过这个XSL,我得到了输出:

User Name | Blood Group | Id | col1 | col2 | col3 | col4
user 1    | A-          | 4  | c1   | c2   |      | c4
user 2    | B+          | 3  |      |      | c3   | c4

我不清楚可以使用哪些函数来获得所需的输出。有人可以帮助我吗?

帮助我进行转换的java代码是:

public static void main(String[] args) {
        String path="/";
        String xml = path+"input.xml";
        String xslt = path+"input.xsl";
        String output = path+"output.txt";
        try {
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer tr = tf.newTransformer(new StreamSource(xslt));
            tr.transform(new StreamSource(xml), new StreamResult(
                    new FileOutputStream(output)));

            System.out.println("Output to " + output);
        } catch (Exception e) {
            System.out.println(e);
            e.printStackTrace();
        }
    }

更新

使用迈克尔答案中提供的XSL文件,输出为:

User Name | Blood Group | Id | col1 | col2 | col3 | col4
user 1    | A-          | 4  | c1   |      |      | 
user 2    | B+          | 3  |      |      |      | c4
user 2    | B+          | 3  |      |      | c3   | 
user 2    | B+          | 3  |      |      |      | c4

4 个答案:

答案 0 :(得分:2)

以这种方式试试吗?

XSLT 1.0 (需要支持EXSLT str:align()函数)

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="str">
<xsl:output method="text" encoding="UTF-8"/>

<xsl:template match="/result">
    <xsl:text>User Name | Blood Group | Id | col1 | col2 | col3 | col4&#10;</xsl:text>
    <xsl:for-each select="users/user/*[starts-with(name(), 'col')]">
        <xsl:value-of select="str:align(../user-name, '          | ', 'left')" />
        <xsl:value-of select="str:align(../blood-group, '            | ', 'left')" />
        <xsl:value-of select="str:align(../id, '   | ', 'left')" />
        <xsl:value-of select="str:align(self::col1, '     | ', 'left')" />
        <xsl:value-of select="str:align(self::col2, '     | ', 'left')" />
        <xsl:value-of select="str:align(self::col3, '     | ', 'left')" />
        <xsl:value-of select="self::col4" />
        <xsl:if test="position()!=last()">
            <xsl:text>&#10;</xsl:text>
        </xsl:if>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

答案 1 :(得分:1)

你需要一个嵌套的for-each over col元素:

<xsl:template match="/result">
    <xsl:text>User Name | Blood Group | Id | col1 | col2 | col3 | col4&#10;</xsl:text>
    <xsl:for-each select="users/user">
      <xsl:for-each select="*[starts-with(local-name(), 'col')]">
        <xsl:value-of select="str:align(../user-name, '          | ', 'left')" />
        <xsl:value-of select="str:align(../blood-group, '            | ', 'left')" />
        <xsl:value-of select="str:align(../id, '   | ', 'left')" />
        <xsl:value-of select="str:align(self::col1, '     | ', 'left')" />
        <xsl:value-of select="str:align(self::col2, '     | ', 'left')" />
        <xsl:value-of select="str:align(self::col3, '     | ', 'left')" />
        <xsl:value-of select="self::col4" />
        <xsl:if test="position()!=last()">
            <xsl:text>&#10;</xsl:text>
        </xsl:if>
      </xsl:for-each>
      <xsl:if test="position()!=last()">
          <xsl:text>&#10;</xsl:text>
      </xsl:if>
    </xsl:for-each>
</xsl:template>

只有空结果而不是null。如果您可以使用XSLT 2.0,则可以使用自定义功能。

答案 2 :(得分:1)

这是一个部分答案,显示如何为缺少的列获取null而不是空字符串

现在你有两个单独的答案,展示了如何用你需要的形状建立一个表格,拼图的最后一部分是如何把单词&#34; null&#34;在当前空白列中。为此,你可以使用这样的技巧:而不是简单地使用self::colN中的value-of,使用

substring(concat('null', self::colN), 5*boolean(self::colN))

这是有效的,因为boolean(self::colN)如果当前节点是colN则为真,否则为假,当被视为数字时,true为1且false为0.因此,在真实情况下,这变为

之类的东西
substring('nullc1', 5) => 'c1'

并且在错误的情况下它变成

substring('null', 0) => 'null'

答案 3 :(得分:1)

鉴于这些错误,也许你应该考虑放弃EXSLT功能,转而采用更多的行人&#34;,而是纯粹的XSLT 1.0方法:

<?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:variable name="spaces" select="'                    '" />

<xsl:template match="/result">
    <xsl:text>User Name | Blood Group | Id | col1 | col2 | col3 | col4&#10;</xsl:text>
    <xsl:for-each select="users/user/*[starts-with(name(), 'col')]">
        <xsl:value-of select="concat(substring(concat(../user-name, $spaces), 1, 10), '| ')" />
        <xsl:value-of select="concat(substring(concat(../blood-group, $spaces), 1, 12), '| ')" />
        <xsl:value-of select="concat(substring(concat(../id, $spaces), 1, 3), '| ')" />
        <xsl:value-of select="concat(substring(concat(self::col1, $spaces), 1, 5), '| ')" />
        <xsl:value-of select="concat(substring(concat(self::col2, $spaces), 1, 5), '| ')" />
        <xsl:value-of select="concat(substring(concat(self::col3, $spaces), 1, 5), '| ')" />
        <xsl:value-of select="self::col4" />
        <xsl:if test="position()!=last()">
            <xsl:text>&#10;</xsl:text>
        </xsl:if>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>