代码之家  ›  专栏  ›  技术社区  ›  ant

在java中将XML文件转换为CSV

  •  10
  • ant  · 技术社区  · 15 年前

    @在可能会有重复的问题和建议之前,我不认为是这样,也许先读一下这个,我会尽量简短。标题给出了基本概念。

    下面是一个XML示例(案例1):

    <root>
          <Item>
            <ItemID>4504216603</ItemID>
            <ListingDetails>
              <StartTime>10:00:10.000Z</StartTime>
              <EndTime>10:00:30.000Z</EndTime>
              <ViewItemURL>http://url</ViewItemURL>
                ....
               </item>      
    

              <Item>
                <ItemID>4504216604</ItemID>
                <ListingDetails>
                  <StartTime>10:30:10.000Z</StartTime>
                  <!-- Start difference from case 1 -->
                  <averages>
                  <AverageTime>value1</AverageTime>
                  <category type="TX">9823</category>
                  <category type="TY">9112</category>
                  <AveragePrice>value2</AveragePrice>
                  </averages>
                  <!-- End difference from case 1 -->
                  <EndTime>11:00:10.000Z</EndTime>
                  <ViewItemURL>http://url</ViewItemURL>
                    ....
                   </item>
                    </root>
    

    我从google借用了这个XML,不管怎么说,我的对象并不总是一样的,有时会有额外的元素,比如case2。现在我想从这两个案例中生成如下CSV:

    ItemID,StartTime,EndTime,ViewItemURL,AverageTime,AveragePrice
    4504216603,10:00:10.000Z,10:00:30.000Z,http://url
    4504216604,10:30:10.000Z,11:00:10.000Z,http://url,value1,value2
    

    这第一行是标题,它也应该包含在csv中。我今天得到了一些有用的链接到stax,我真的不知道什么是正确的/最佳的方法,我挣扎了3天,现在还不愿意放弃。

    告诉我你怎么想你怎么解决这个问题

    我忘了提到这是一个非常大的xml文件,高达1gb

    我正在寻找更通用的方法,这意味着这应该适用于任何深度的任意数量的节点,有时如示例xml中所示,这种情况可能会发生 item

    此外,节点可能具有相同的名称/localName,但值和属性不同,如果是这种情况,则新列应以适当的值出现在CSV中(我在里面加了这个例子 <averages> 已调用标记 category

    8 回复  |  直到 15 年前
        1
  •  13
  •   Mark McLaren    15 年前

    我将使用SAX解析器通过两次传递来解决这个问题(顺便提一下,我还将使用CSV生成库来创建输出,因为这将处理CSV涉及的所有复杂字符转义(但我在草图中没有实现)。

    第一次通过:

    第二遍:

    我假设XML文件格式良好。我假设我们没有具有预定义顺序的scheme/DTD。

    在第一步中,我假设将为每个包含文本内容的XML元素或任何属性(我假设属性将包含某些内容!)添加一个CSV列。

    第二个过程建立了目标列的数量,将执行实际的CSV输出。

    根据您的示例XML,我的代码草图将生成:

    ItemID,StartTime,EndTime,ViewItemURL,AverageTime,category,category,type,type,AveragePrice
    4504216603,10:00:10.000Z,10:00:30.000Z,http://url,,,,,,
    4504216604,10:30:10.000Z,11:00:10.000Z,http://url,value1,9823,9112,TX,TY,value2
    

    请注意,我使用了googlecollections LinkedHashMultimap,因为这在将多个值与单个键关联时非常有用。我希望你觉得这个有用!

    import com.google.common.collect.LinkedHashMultimap;
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    import java.util.LinkedHashMap;
    import java.util.Map.Entry;
    import org.xml.sax.Attributes;
    import org.xml.sax.InputSource;
    import org.xml.sax.SAXException;
    import org.xml.sax.XMLReader;
    import org.xml.sax.helpers.DefaultHandler;
    import org.xml.sax.helpers.XMLReaderFactory;
    
    public class App {
    
        public static void main(String[] args) throws SAXException, FileNotFoundException, IOException {
            // First pass - to determine headers
            XMLReader xr = XMLReaderFactory.createXMLReader();
            HeaderHandler handler = new HeaderHandler();
            xr.setContentHandler(handler);
            xr.setErrorHandler(handler);
            FileReader r = new FileReader("test1.xml");
            xr.parse(new InputSource(r));
    
            LinkedHashMap<String, Integer> headers = handler.getHeaders();
            int totalnumberofcolumns = 0;
            for (int headercount : headers.values()) {
                totalnumberofcolumns += headercount;
            }
            String[] columnheaders = new String[totalnumberofcolumns];
            int i = 0;
            for (Entry<String, Integer> entry : headers.entrySet()) {
                for (int j = 0; j < entry.getValue(); j++) {
                    columnheaders[i] = entry.getKey();
                    i++;
                }
            }
            StringBuilder sb = new StringBuilder();
            for (String h : columnheaders) {
                sb.append(h);
                sb.append(',');
            }
            System.out.println(sb.substring(0, sb.length() - 1));
    
            // Second pass - collect and output data
    
            xr = XMLReaderFactory.createXMLReader();
    
            DataHandler datahandler = new DataHandler();
            datahandler.setHeaderArray(columnheaders);
    
            xr.setContentHandler(datahandler);
            xr.setErrorHandler(datahandler);
            r = new FileReader("test1.xml");
            xr.parse(new InputSource(r));
        }
    
        public static class HeaderHandler extends DefaultHandler {
    
            private String content;
            private String currentElement;
            private boolean insideElement = false;
            private Attributes attribs;
            private LinkedHashMap<String, Integer> itemHeader;
            private LinkedHashMap<String, Integer> accumulativeHeader = new LinkedHashMap<String, Integer>();
    
            public HeaderHandler() {
                super();
            }
    
            private LinkedHashMap<String, Integer> getHeaders() {
                return accumulativeHeader;
            }
    
            private void addItemHeader(String headerName) {
                if (itemHeader.containsKey(headerName)) {
                    itemHeader.put(headerName, itemHeader.get(headerName) + 1);
                } else {
                    itemHeader.put(headerName, 1);
                }
            }
    
            @Override
            public void startElement(String uri, String name,
                    String qName, Attributes atts) {
                if ("item".equalsIgnoreCase(qName)) {
                    itemHeader = new LinkedHashMap<String, Integer>();
                }
                currentElement = qName;
                content = null;
                insideElement = true;
                attribs = atts;
            }
    
            @Override
            public void endElement(String uri, String name, String qName) {
                if (!"item".equalsIgnoreCase(qName) && !"root".equalsIgnoreCase(qName)) {
                    if (content != null && qName.equals(currentElement) && content.trim().length() > 0) {
                        addItemHeader(qName);
                    }
                    if (attribs != null) {
                        int attsLength = attribs.getLength();
                        if (attsLength > 0) {
                            for (int i = 0; i < attsLength; i++) {
                                String attName = attribs.getLocalName(i);
                                addItemHeader(attName);
                            }
                        }
                    }
                }
                if ("item".equalsIgnoreCase(qName)) {
                    for (Entry<String, Integer> entry : itemHeader.entrySet()) {
                        String headerName = entry.getKey();
                        Integer count = entry.getValue();
                        //System.out.println(entry.getKey() + ":" + entry.getValue());
                        if (accumulativeHeader.containsKey(headerName)) {
                            if (count > accumulativeHeader.get(headerName)) {
                                accumulativeHeader.put(headerName, count);
                            }
                        } else {
                            accumulativeHeader.put(headerName, count);
                        }
                    }
                }
                insideElement = false;
                currentElement = null;
                attribs = null;
            }
    
            @Override
            public void characters(char ch[], int start, int length) {
                if (insideElement) {
                    content = new String(ch, start, length);
                }
            }
        }
    
        public static class DataHandler extends DefaultHandler {
    
            private String content;
            private String currentElement;
            private boolean insideElement = false;
            private Attributes attribs;
            private LinkedHashMultimap dataMap;
            private String[] headerArray;
    
            public DataHandler() {
                super();
            }
    
            @Override
            public void startElement(String uri, String name,
                    String qName, Attributes atts) {
                if ("item".equalsIgnoreCase(qName)) {
                    dataMap = LinkedHashMultimap.create();
                }
                currentElement = qName;
                content = null;
                insideElement = true;
                attribs = atts;
            }
    
            @Override
            public void endElement(String uri, String name, String qName) {
                if (!"item".equalsIgnoreCase(qName) && !"root".equalsIgnoreCase(qName)) {
                    if (content != null && qName.equals(currentElement) && content.trim().length() > 0) {
                        dataMap.put(qName, content);
                    }
                    if (attribs != null) {
                        int attsLength = attribs.getLength();
                        if (attsLength > 0) {
                            for (int i = 0; i < attsLength; i++) {
                                String attName = attribs.getLocalName(i);
                                dataMap.put(attName, attribs.getValue(i));
                            }
                        }
                    }
                }
                if ("item".equalsIgnoreCase(qName)) {
                    String data[] = new String[headerArray.length];
                    int i = 0;
                    for (String h : headerArray) {
                        if (dataMap.containsKey(h)) {
                            Object[] values = dataMap.get(h).toArray();
                            data[i] = (String) values[0];
                            if (values.length > 1) {
                                dataMap.removeAll(h);
                                for (int j = 1; j < values.length; j++) {
                                    dataMap.put(h, values[j]);
                                }
                            } else {
                                dataMap.removeAll(h);
                            }
                        } else {
                            data[i] = "";
                        }
                        i++;
                    }
                    StringBuilder sb = new StringBuilder();
                    for (String d : data) {
                        sb.append(d);
                        sb.append(',');
                    }
                    System.out.println(sb.substring(0, sb.length() - 1));
                }
                insideElement = false;
                currentElement = null;
                attribs = null;
            }
    
            @Override
            public void characters(char ch[], int start, int length) {
                if (insideElement) {
                    content = new String(ch, start, length);
                }
            }
    
            public void setHeaderArray(String[] headerArray) {
                this.headerArray = headerArray;
            }
        }
    }
    
        2
  •  9
  •   Robert Diana    15 年前

    这看起来是一个使用XSL的好例子。根据您的基本需求,与自定义解析器或序列化程序相比,使用XSL可能更容易找到正确的节点。这样做的好处是,XSL可以针对“//Item//AverageTime”或您需要的任何节点,而不必担心节点深度。

    更新:下面是我创建的xslt,以确保它按预期工作。

    <?xml version="1.0"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" />
    <xsl:template match="/">
    ItemID,StartTime,EndTime,ViewItemURL,AverageTime,AveragePrice
    <xsl:for-each select="//Item">
    <xsl:value-of select="ItemID"/><xsl:text>,</xsl:text><xsl:value-of select="//StartTime"/><xsl:text>,</xsl:text><xsl:value-of select="//EndTime"/><xsl:text>,</xsl:text><xsl:value-of select="//ViewItemURL"/><xsl:text>,</xsl:text><xsl:value-of select="//AverageTime"/><xsl:text>,</xsl:text><xsl:value-of select="//AveragePrice"/><xsl:text>
    </xsl:text>
    </xsl:for-each>
    </xsl:template>
    
    </xsl:stylesheet>
    
        3
  •  6
  •   kgui K-K    8 年前

    我不确定我是否理解这个解决方案应该有多通用。是否确实要为通用解决方案分析两次1 GB文件?如果你想要一些普通的东西,你为什么要跳过 <category>

    根据我的经验,通常最好以特定的方式解析特定的文件(但这并不排除使用通用API)。我的答案将朝这个方向发展(澄清之后我会更新它)。


    XML Manager CSV Manager . 看到了吗 How to convert CSV into XML and XML into CSV using Java 举个完整的例子。这种方法非常简单:使用XPath表达式定义数据字段(这在您的情况下是完美的,因为您可以有“额外”元素),解析文件,然后传递结果 List 到CSV组件以生成CSV文件。API看起来很简单,代码经过测试(他们的源代码) test cases 他们声称支持千兆字节大小的文件。

    你可以得到一个单一的开发者许可证为170美元,这是不是很昂贵相比,开发人员的每日费率。


    另一种选择是使用 Spring Batch . springbatch提供了所需的一切 XML files 作为 input 或输出(使用您选择的StAX和XML绑定框架)和 flat files 作为输入或 output


    Smooks 将XML转换为CSV transformations . 另请参见:


    另一种选择是使用StAX解析器或者,为什么不使用 VTD-XML 和XPath。看看:

        4
  •  2
  •   A. Ionescu    15 年前

    根据所描述的需求编写代码的最佳方法是使用FreeMarker和XML处理的简单特性。 See the docs .

    在这种情况下,您只需要生成CSV的模板。

    XMLGen ,但方法非常相似。只要看看这个图表和示例,就可以输出CSV而不是SQL语句。

    这两种类似的方法不是“常规”的,但是根据您的情况可以很快完成工作,而且您不必学习XSL(我认为很难掌握)。

        5
  •  2
  •   mdma    15 年前

    import javax.xml.stream.XMLInputFactory;
    import javax.xml.stream.XMLStreamConstants;
    import javax.xml.stream.XMLStreamException;
    import javax.xml.stream.XMLStreamReader;
    import java.io.*;
    
    public class App 
    {
        public static void main( String[] args ) throws XMLStreamException, FileNotFoundException
        {
            new App().convertXMLToCSV(new BufferedInputStream(new FileInputStream(args[0])), new BufferedOutputStream(new FileOutputStream(args[1])));
        }
    
        static public final String ROOT = "root";
        static public final String ITEM = "Item";
        static public final String ITEM_ID = "ItemID";
        static public final String ITEM_DETAILS = "ListingDetails";
        static public final String START_TIME = "StartTime";
        static public final String END_TIME = "EndTime";
        static public final String ITEM_URL = "ViewItemURL";
        static public final String AVERAGES = "averages";
        static public final String AVERAGE_TIME = "AverageTime";
        static public final String AVERAGE_PRICE = "AveragePrice";
        static public final String SEPARATOR = ",";
    
        public void convertXMLToCSV(InputStream in, OutputStream out) throws XMLStreamException
        {
            PrintWriter writer = new PrintWriter(out);
            XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(in);
            convertXMLToCSV(xmlStreamReader, writer);
        }
    
        public void convertXMLToCSV(XMLStreamReader xmlStreamReader, PrintWriter writer) throws XMLStreamException {
            writer.println("ItemID,StartTime,EndTime,ViewItemURL,AverageTime,AveragePrice");
            xmlStreamReader.nextTag();
            xmlStreamReader.require(XMLStreamConstants.START_ELEMENT, null, ROOT);
    
            while (xmlStreamReader.hasNext()) {
                xmlStreamReader.nextTag();
                if (xmlStreamReader.isEndElement())
                    break;
    
                xmlStreamReader.require(XMLStreamConstants.START_ELEMENT, null, ITEM);
                String itemID = nextValue(xmlStreamReader, ITEM_ID);
                xmlStreamReader.nextTag(); xmlStreamReader.require(XMLStreamConstants.START_ELEMENT, null, ITEM_DETAILS);
                String startTime = nextValue(xmlStreamReader, START_TIME);
                xmlStreamReader.nextTag();
                String averageTime = null;
                String averagePrice = null;
    
                if (xmlStreamReader.getLocalName().equals(AVERAGES))
                {
                    averageTime = nextValue(xmlStreamReader, AVERAGE_TIME);
                    averagePrice = nextValue(xmlStreamReader, AVERAGE_PRICE);
                    xmlStreamReader.nextTag();
                    xmlStreamReader.require(XMLStreamConstants.END_ELEMENT, null, AVERAGES);
                    xmlStreamReader.nextTag();
                }
                String endTime = currentValue(xmlStreamReader, END_TIME);
                String url = nextValue(xmlStreamReader,ITEM_URL);
                xmlStreamReader.nextTag(); xmlStreamReader.require(XMLStreamConstants.END_ELEMENT, null, ITEM_DETAILS);
                xmlStreamReader.nextTag(); xmlStreamReader.require(XMLStreamConstants.END_ELEMENT, null, ITEM);
    
                writer.append(esc(itemID)).append(SEPARATOR)
                        .append(esc(startTime)).append(SEPARATOR)
                        .append(esc(endTime)).append(SEPARATOR)
                        .append(esc(url));
                if (averageTime!=null)
                    writer.append(SEPARATOR).append(esc(averageTime)).append(SEPARATOR)
                            .append(esc(averagePrice));
                writer.println();                        
            }
    
            xmlStreamReader.require(XMLStreamConstants.END_ELEMENT, null, ROOT);
            writer.close();
    
        }
    
        private String esc(String string) {
            if (string.indexOf(',')!=-1)
                string = '"'+string+'"';
            return string;
        }
    
        private String nextValue(XMLStreamReader xmlStreamReader, String name) throws XMLStreamException {
            xmlStreamReader.nextTag();
            return currentValue(xmlStreamReader, name);
        }
    
        private String currentValue(XMLStreamReader xmlStreamReader, String name) throws XMLStreamException {
            xmlStreamReader.require(XMLStreamConstants.START_ELEMENT, null, name);
            String value = "";
            for (;;) {
                int next = xmlStreamReader.next();
                if (next==XMLStreamConstants.CDATA||next==XMLStreamConstants.SPACE||next==XMLStreamConstants.CHARACTERS)
                    value += xmlStreamReader.getText();
                else if (next==XMLStreamConstants.END_ELEMENT)
                    break;
                // ignore comments, PIs, attributes
            }
            xmlStreamReader.require(XMLStreamConstants.END_ELEMENT, null, name);
            return value.trim();
        }    
    }
    
        6
  •  1
  •   Uri    15 年前

    我不相信SAX是适合您的最佳方法。 不过,在这里使用SAX的方法有很多种。

    启动ListingDetails时,将映射初始化为处理程序上的成员变量。在每个子元素中,在该映射中设置适当的键值。完成ListingDetails后,检查映射并显式地模拟值,例如缺少元素的nulls。假设每个项目有一个ListingDetails,则将其保存到处理程序中的成员变量中。

    现在,当item元素结束时,有一个函数可以根据映射按所需顺序写入csv行。

        7
  •  1
  •   Thorbjørn Ravn Andersen    15 年前

    请注意,这将是使用XSLT的一个主要示例,除了大多数XSLT处理器将整个XML文件读入内存之外,内存不是一个选项,因为它很大。但是,请注意,企业版Saxon可以执行流式XSLT处理(如果XSLT脚本遵守这些限制)。

    如果适用,您可能还希望在JVM之外使用外部XSLT处理器。这为更多的选择打开了大门。

    撒克逊EE流媒体: http://www.saxonica.com/documentation/sourcedocs/serial.html

    推荐文章