使用XSLT 2.0有条件地替换XML标记值

时间:2017-11-06 11:28:01

标签: xml csv xslt-2.0

我正在使用XSLT 2.0(Saxon处理器)并尝试将xml转换为另一个。以下是样本输入xml的样子

<Customer>
    <Name>{{customer_name}}</Name>
    <Transaction>{{customer_transaction}}</Transaction>
    <Id>134</Id>
</Customer>

如何检查每个xml标记的值,并验证其是否为字段名称,例如customer_name或customer_transaction?如果是字段名称,那么我需要在外部csv文件中查找其值并替换其值。

这是我到目前为止编写的xsl模板。有人可以协助我完成它。

<xsl:stylesheet
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:fn="fn"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        version="2.0" exclude-result-prefixes="xs fn">

<xsl:output method="xml" omit-xml-declaration="yes"/>

    <xsl:param name="csv-uri"
               select="'file:///E:/data.csv'"/>

    <xsl:variable name="csv" select="unparsed-text($csv-uri)"/>


    <!-- Identity template : copy all text nodes, elements and attributes -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:function name="fn:getTokens" as="xs:string+">
        <xsl:param name="str" as="xs:string"/>
        <xsl:analyze-string select="concat($str, ',')" regex='(("[^"]*")+|[^,]*),'>
            <xsl:matching-substring>
                <xsl:sequence select='replace(regex-group(1), "^""|""$|("")""", "$1")'/>
            </xsl:matching-substring>
        </xsl:analyze-string>
    </xsl:function>

    <xsl:variable name="lines" select="tokenize($csv, '\r\n')" as="xs:string+"/>
    <xsl:variable name="headerColNames" select="fn:getTokens($lines[1])" as="xs:string+"/>

    <xsl:template match="*/text()[starts-with(.,'{{')]">
        <!-- add more code here -->   
    </xsl:template>

</xsl:stylesheet>

如果data.csv具有以下数据

customer_name, customer_transaction
abc, T1
xyz, T2

输出xml应该看起来像

<Customer>
    <Name>abc</Name>
    <Transaction>T1</Transaction>
    <Id>134</Id>
</Customer>

请注意,我们只关注第一条记录。

感谢。

1 个答案:

答案 0 :(得分:0)

我会考虑首先将CSV“转换”为XML,然后匹配并选择,这里是一个XSLT 3解决方案(可以转换为XSLT 2,但需要更多代码行,但Saxon 9.8运行XSLT 3):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    exclude-result-prefixes="xs math"
    expand-text="yes"
    version="3.0">

    <xsl:param name="csv-uri" as="xs:string">test2017110601.txt</xsl:param>

    <xsl:param name="field-pattern" as="xs:string" expand-text="no">\{\{.+\}\}</xsl:param>

    <xsl:param name="repl-pattern" as="xs:string" expand-text="no">^\{\{|\}\}$</xsl:param>

    <xsl:param name="sep-pattern" as="xs:string">,\s*</xsl:param>

    <xsl:param name="csv-lines" as="xs:string*" select="unparsed-text-lines($csv-uri)"/>
    <xsl:variable name="col-names" as="xs:string*" select="tokenize(head($csv-lines), $sep-pattern)"/>

    <xsl:variable name="csv-doc">
        <xsl:apply-templates select="tail($csv-lines)[normalize-space()]" mode="lines"/>
    </xsl:variable>

    <xsl:key name="fields" match="row/*" use="local-name()"/>

    <xsl:mode on-no-match="shallow-copy"/>

    <xsl:template match=".[. instance of xs:string]" mode="lines">
        <row>
            <xsl:variable name="cols" as="xs:string*" select="tokenize(., ',\s*')"/>
            <xsl:for-each select="$col-names">
                <xsl:element name="{.}">{let $p := position() return $cols[$p]}</xsl:element>
            </xsl:for-each>
        </row>
    </xsl:template>

    <xsl:template match="*[not(*) and matches(., $field-pattern)]">
        <xsl:copy>
            <xsl:value-of select="key('fields', replace(., $repl-pattern, ''), $csv-doc)[1]"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

这是一个XSLT 2版本:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="2.0">

    <xsl:param name="csv-uri" as="xs:string">test2017110601.txt</xsl:param>

    <xsl:param name="field-pattern" as="xs:string">\{\{.+\}\}</xsl:param>

    <xsl:param name="repl-pattern" as="xs:string">^\{\{|\}\}$</xsl:param>

    <xsl:param name="sep-pattern" as="xs:string">,\s*</xsl:param>

    <xsl:param name="csv-lines" as="xs:string*" select="tokenize(unparsed-text($csv-uri), '\r?\n')"/>
    <xsl:variable name="col-names" as="xs:string*" select="tokenize($csv-lines[1], $sep-pattern)"/>

    <xsl:variable name="csv-doc">
        <xsl:for-each select="$csv-lines[position() gt 1][normalize-space()]">
            <row>
                <xsl:variable name="cols" as="xs:string*" select="tokenize(., ',\s*')"/>
                <xsl:for-each select="$col-names">
                    <xsl:element name="{.}">
                        <xsl:variable name="p" as="xs:integer" select="position()"/>
                        <xsl:value-of select="$cols[$p]"/>
                    </xsl:element>
                </xsl:for-each>
            </row>
        </xsl:for-each>
    </xsl:variable>

    <xsl:key name="fields" match="row/*" use="local-name()"/>

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

    <xsl:template match="*[not(*) and matches(., $field-pattern)]">
        <xsl:copy>
            <xsl:value-of select="key('fields', replace(., $repl-pattern, ''), $csv-doc)[1]"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>