【问题标题】:XSL for converting XML to CSV : Adding Quotes to the end based on data field用于将 XML 转换为 CSV 的 XSL:根据数据字段在末尾添加引号
【发布时间】:2014-09-19 15:25:46
【问题描述】:

我正在尝试使用 Java 代码将 XML 文件动态转换为 CSV 文件。我能够获得转换为 CSV 的数据,但问题是我的数据有“”和“,”。

这是我的示例 XML:

<record>
<column name="ID">537316</column>
<column name="TYPE">MANUAL</column>
<column name="SECONDID">546</column>
<column name="INFO">"THIS","IS",FOR,"TEST"</column>
<column name="KEY">345</column>
</record>

这是Java代码:

import java.io.File;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.w3c.dom.Document;

class xmltocsv {

public static void main(String args[]) throws Exception {
    File stylesheet = new File("C:/testxsl.xsl");
    File xmlSource = new File("C:/test.xml");

    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder builder = factory.newDocumentBuilder();
    Document document = builder.parse(xmlSource);

    StreamSource stylesource = new StreamSource(stylesheet);
    Transformer transformer = TransformerFactory.newInstance()
        .newTransformer(stylesource);
    Source source = new DOMSource(document);
    Result outputTarget = new StreamResult(new File("c:/output.csv"));
    transformer.transform(source, outputTarget);
}
}

这是我的 XSL 文件:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/*">
<xsl:for-each select="*[1]/*">
<xsl:text>"</xsl:text>
<xsl:value-of select="@name"/>
<xsl:text>"</xsl:text>
<xsl:if test="position() != last()">,</xsl:if>
<xsl:if test="position() = last()">
<xsl:text>&#xD;</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:apply-templates/>
</xsl:template>
<xsl:output method="text" encoding="iso-8859-1"/>
<xsl:param name="fieldNames" select="'yes'" />
<xsl:strip-space elements="*" />
<xsl:template match="/*/child::*">
<xsl:for-each select="child::*">
<xsl:if test="position() != last()"><xsl:text>"</xsl:text><xsl:value-of  Select="normalize-space(.)"/><xsl:text>"</xsl:text>,</xsl:if>
<xsl:if test="position() = last()"><xsl:text>"</xsl:text><xsl:value-of select="normalize-space(.)"/><xsl:text>"</xsl:text><xsl:text>&#xD;</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

样本输出应该是:

ID,TYPE,SECONDID,INFO,KEY
"537316","MANUAL","546","THIS"",""IS"",FOR,""TEST""","345"

但我得到的输出是:

ID,TYPE,SECONDID,INFO,KEY\n
"537316","MANUAL","546",""THIS","IS",FOR,"TEST"","345"

我使用的 XML 来自数据库,并且在我的输出 CSV 中包含导致意外结果(当我使用 MS Excel 打开输出 CSV 时)的特殊字符 (")。 我需要验证引号的数据,如果有引号,我必须添加额外的引号以获得所需的输出。 有人可以帮我解决我可以在 XSL 中使用的 if 条件来验证字符串并在数据中搜索 ("")。

【问题讨论】:

  • 但是第二行应该是 537316,MANUAL,546,"""THIS"",""IS"",FOR,""TEST""",345\n。 - 你这样做的方式相当间接。
  • 谢谢卢恩。我的问题是我用 MS Excel 打开它,当它到达嵌入数据中的逗号时会留下空白列。
  • 是的,你把第二行写对了。我需要使用我的 XSL 代码来获取,你能帮我吗?
  • 我无法重现您报告的结果:将 XML 和 XSLT 放入 xslttest.appspot.com(Java 应用程序也是如此)仅在 (1) 更改“ 添加到第二个 xsl:template。即使这样,结果还是:537316MANUAL546"THIS","IS",FOR,"TEST"345
  • 我认为您的预期输出实际上应该是537316,MANUAL,546,"""THIS"",""IS"",FOR,""TEST""",345。如果字段中有逗号,则需要将整个字段括在引号中才能将其视为一个字段。如果该字段中已经有引号,那么这些引号需要变成双引号!

标签: java xml xslt csv


【解决方案1】:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/*">
<xsl:for-each select="*[1]/*">
<xsl:text>"</xsl:text>
<xsl:value-of select="@name"/>
<xsl:text>"</xsl:text>
<xsl:if test="position() != last()">,</xsl:if>
<xsl:if test="position() = last()">
<xsl:text>&#xD;</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:apply-templates/>
</xsl:template>

<xsl:output method="text" encoding="iso-8859-1"/>
<xsl:param name="fieldNames" select="'yes'" />
<xsl:strip-space elements="*" />
<xsl:template match="/*/child::*">
<xsl:for-each select="child::*">
<xsl:if test="position() != last()">
<xsl:text>"</xsl:text>

    <xsl:call-template name="substitute">
        <xsl:with-param name="text" select="."/>
    </xsl:call-template>
    <xsl:text>"</xsl:text>
    <xsl:text>,</xsl:text>
</xsl:if>
<xsl:if test="position() = last()"><xsl:text>"</xsl:text><xsl:value-of select="normalize-space(.)"/><xsl:text>"</xsl:text><xsl:text>&#xD;</xsl:text></xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="substitute">
<xsl:param name="text"/>
<xsl:param name="searchString">"</xsl:param>
<xsl:param name="replaceString">""</xsl:param>
<xsl:choose>
    <xsl:when test="contains($text,$searchString)">
        <xsl:value-of select="substring-before($text,$searchString)"/>
        <xsl:value-of select="$replaceString"/>
        <xsl:call-template name="substitute">
            <xsl:with-param name="text" select="substring-after($text,$searchString)"/>
            <xsl:with-param name="searchString" select="$searchString"/>
            <xsl:with-param name="replaceString" select="$replaceString"/>
        </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
        <xsl:value-of select="$text"/>
    </xsl:otherwise>
</xsl:choose>
</xsl:template>

</xsl:stylesheet>

【讨论】:

    【解决方案2】:

    以下样式表:

    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:template match="/">
        <xsl:text>ID,TYPE,SECONDID,INFO,KEY&#10;</xsl:text>
        <xsl:for-each select="record/column">
            <xsl:text>"</xsl:text>
            <xsl:call-template name="substitute">
                <xsl:with-param name="text" select="."/>
            </xsl:call-template>
            <xsl:text>"</xsl:text>
            <xsl:if test="position()!=last()">
                <xsl:text>,</xsl:text>
            </xsl:if>
        </xsl:for-each>
    </xsl:template>
    
    <xsl:template name="substitute">
        <xsl:param name="text"/>
        <xsl:param name="searchString">"</xsl:param>
        <xsl:param name="replaceString">""</xsl:param>
        <xsl:choose>
            <xsl:when test="contains($text,$searchString)">
                <xsl:value-of select="substring-before($text,$searchString)"/>
                <xsl:value-of select="$replaceString"/>
                <xsl:call-template name="substitute">
                    <xsl:with-param name="text" select="substring-after($text,$searchString)"/>
                    <xsl:with-param name="searchString" select="$searchString"/>
                    <xsl:with-param name="replaceString" select="$replaceString"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$text"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    
    </xsl:stylesheet>
    

    当应用于您的示例输入时,将产生以下输出:

    ID,TYPE,SECONDID,INFO,KEY
    "537316","MANUAL","546","""THIS"",""IS"",FOR,""TEST""","345"
    

    我认为这是 CSV 格式的输入数据的正确表示。

    【讨论】:

    • 谢谢迈克尔,但我没有得到想要的输出。我的问题是我的 XSL 应该是完全动态的,因为它应该采用不同类型的 XML 数据(我的意思是不同的字段)。在我的 XSL 中,哪个是动态的是否可以检查数据是否存在“如果存在将其转换为”我认为这将是解决此问题的最简单方法
    • 它不能是“完全动态的”,但您当然可以对其进行调整,以便标题中的列名取自实际数据。但是,这与您关于引用单个字段的问题无关。
    • 谢谢迈克尔,我做了一些修改,能够产生所需的输出。谢谢:)
    • 我正在更新下面的代码,它可能会帮助以后阅读此线程的人。
    【解决方案3】:
    class XmlToCsv {
    private static void emitHeaders( Node record ){
        NodeList fields = record.getChildNodes();
        String del = "";
        for( int iField = 0; iField < fields.getLength(); iField++ ){
            Node node = fields.item( iField );
            if( ! ( node instanceof Element ) ) continue;
            System.out.print( del );
            System.out.print( ((Element)node).getAttribute("name") );
            del = ",";
        }
        System.out.println();
    }
    private static void emitData( Node record ){
        NodeList fields = record.getChildNodes();
        String del = "";
        for( int iField = 0; iField < fields.getLength(); iField++ ){
            Node node = fields.item( iField );
            if( ! ( node instanceof Element ) ) continue;
            System.out.print( del );
            String cont = node.getTextContent();
            cont = cont.replaceAll( "\"", "\"\"" );
            System.out.print( '"' + cont + '"' );
            del = ",";
        }
        System.out.println();
    }
    
    public static void main(String args[]) throws Exception {
        File xmlSource = new File("test.xml");
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document document = builder.parse(xmlSource);
        Source source = new DOMSource(document);
        Element table = document.getDocumentElement();
        NodeList records = table.getElementsByTagName("record");
        emitHeaders( records.item( 0 ) );
        for( int iRec = 0; iRec < records.getLength(); iRec++ ){
            emitData( records.item( iRec ) );
        }
    }
    }
    

    使用 JAXB 会更简单。

    【讨论】:

    • 感谢 Lune,但我必须将其与 IBM websphere Lombardi 7.2 版工作流工具集成。我只能使用 XSL,因此无法使用此代码。
    猜你喜欢
    • 1970-01-01
    • 2015-09-23
    • 2016-02-13
    • 2021-12-19
    • 1970-01-01
    • 1970-01-01
    • 2011-04-28
    • 2013-06-23
    • 2022-01-09
    相关资源
    最近更新 更多