【问题标题】:XML to CSV conversion as fast as possible for medium sized files对于中等大小的文件,尽可能快地将 XML 转换为 CSV
【发布时间】:2019-06-21 16:09:07
【问题描述】:

所以我查找并尝试了几种将 XML 文件转换为 CSV 文件的方法。我尝试过的方法是:

  1. XSLT :为给定的 XML 获取 XSLT,然后形成 CSV。但这太难维护了,因为我们不知道我们会得到什么样的 XML 文件,这使得它不是通用的解决方案。
  2. 使用 Apache Digester
  3. SAXPJAXP 上述两种方法的问题在于,它需要在之前定义您的 java 对象,因此创建如此多的类又是一个瓶颈,因为我们不知道那里会出现哪种 XML 模式。每次都会发生变化。
  4. 一直使用DocumentBuildFactory 并对其进行解析。这适用于通用 XML 文件,但对于 5MB 到 1GB 范围内的文件来说速度很慢。我的 XML 文件肯定不会大于 1GB。

除了我已经尝试过的这些方法之外,还有什么想法可以比上面更快地以编程方式实现它吗?我查看了几个在线工具,它们可以在很短的时间内将任何 XML 文件转换为 CSV 文件,它们似乎适用于任何通用 XML 文件。有什么建议吗?

以下是可能出现的不同示例,它们也可能会发生变化:

    <?xml version="1.0"?>
<Company>
  <Employee id="1">
      <Email>tp@xyz.com</Email>
      <artist>Bob Dylan</artist>
    <country>USA</country>
  </Employee>

</Company>

这是最简单的。预期输出为:

Company/Employee/Email,Company/Employee/artist,Company/Employee/country,Company/Employee/_id
tp@xyz.com,Bob Dylan,USA,1

另一个例子

<?xml version="1.0"?>
<Company>
  <Employee id="1">
      <Email>tp@xyz.com</Email>
      <UserData id="id32" type="AttributesInContext">
<UserValue value="7in" title="Height"></UserValue>
<UserValue value="" title="Weight"></UserValue></UserData>
  </Employee>
  <Employee id="2">
      <Email>tp@xyz.com</Email>
      <UserData id="id33" type="AttributesInContext">
<UserValue value="6in" title="Height"></UserValue>
<UserValue value="" title="Weight"></UserValue></UserData>
  </Employee>
  <Employee id="3">
      <Email>tp@xyz.com</Email>
      <UserData id="id34" type="AttributesInContext">
<UserValue value="4in" title="Height"></UserValue>
<UserValue value="" title="Weight"></UserValue></UserData>
  </Employee>
</Company>

预期输出是

Email,UserData/UserValue/0/_value,UserData/UserValue/0/_title,UserData/UserValue/1/_value,UserData/UserValue/1/_title,UserData/_id,UserData/_type,_id
tp@xyz.com,7in,Height,,Weight,id32,AttributesInContext,1
tp@xyz.com,6in,Height,,Weight,id33,AttributesInContext,2
tp@xyz.com,4in,Height,,Weight,id34,AttributesInContext,3

这有点复杂。这可能会变得更加复杂和嵌套,最大可达 1GB。

【问题讨论】:

  • “我们不知道我们会得到什么样的 XML 文件” - 你想如何针对未知的输入格式编写任何东西?
  • 这就是问题所在。我们可以得到一个简单的 xml,也可以得到一个复杂的嵌套 xml 文件。像属性和属性名称可能会改变。嵌套逻辑可以改变。同样。
  • 请提供至少两个输入示例和您相应的预期输出。
  • 更新了问题。
  • @CodeHunter 如果 xml 结构相同,并且在某些情况下,元素/属性是可选的,那么您没有理由不能编写处理“复杂”情况的 XSLT,并且仍然适用于一个“简单”的案例。

标签: java xml csv saxparser


【解决方案1】:

您可以尝试使用Java StAX API 进行此提议。

例如:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

public class XmlToCSV {

    public static void convert(InputStream xml, OutputStream csv) throws Exception {
        try (StringWriter header = new StringWriter(4096); StringWriter content = new StringWriter(4096)) {
            XMLInputFactory factory = XMLInputFactory.newInstance();
            XMLEventReader xmlEventReader = factory.createXMLEventReader(xml);
            XMLEvent xmlEvent;
            long nestingLevel = -1;
            StringBuilder line = null;
            while (xmlEventReader.hasNext()) {
                xmlEvent = xmlEventReader.nextEvent();
                switch (xmlEvent.getEventType()) {
                case XMLEvent.START_ELEMENT:
                    ++nestingLevel;
                    if (0 == nestingLevel) {
                        break;
                    } else if (1 == nestingLevel) {
                        line = new StringBuilder();
                    }
                    StartElement startElement = xmlEvent.asStartElement();
                    serializeElementHeader(header, line, startElement);
                    break;
                case XMLEvent.CHARACTERS:
                case XMLEvent.CDATA:
                    if (nestingLevel < 1)
                        break;
                    Characters chars = xmlEvent.asCharacters();
                    if (!chars.isWhiteSpace()) {
                        line.append(chars.getData());
                        line.append(',');
                    }
                    break;
                case XMLEvent.END_ELEMENT:
                    if (--nestingLevel == 0) {
                        header.write("0/");
                        for(int i= line.length()-1; ',' == line.charAt(i); --i) {                           
                            line.deleteCharAt(i);
                        }
                        content.write(line.toString());
                        content.write('\n');
                    }
                    break;
                default:
                    break;
                }
            }
            // write csv
            try (Writer cvsWriter = new OutputStreamWriter(csv, StandardCharsets.UTF_8.name())) {
                cvsWriter.write(header.toString());
                cvsWriter.write('\n');
                cvsWriter.write(content.toString());
            }
        }
    }

    private static void serializeElementHeader(StringWriter header, StringBuilder line,
            StartElement startElement) {
        header.write(startElement.getName().getLocalPart());
        header.write('/');
        Iterator<Attribute> it = startElement.getAttributes();
        while(it.hasNext()) {
            Attribute attr = it.next();
            header.write('_');
            header.write(attr.getName().getLocalPart());
            header.write('/');
            line.append(attr.getValue());
            line.append(',');
        }
    }

    private static String TEST_XML = "<?xml version='1.0'?>" 
            + "<Company>" 
            + "  <Employee id='1'>"
            + "      <Email>tp@xyz.com</Email>" 
            + "      <UserData id='id32' type='AttributesInContext'>"
            + "         <UserValue value='7in' title='Heigh'></UserValue>"
            + "         <UserValue value='' title='Weight'></UserValue>" 
            + "      </UserData>" 
            + "  </Employee>"
            + "  <Employee id='2'>"
            + "      <Email>tp@xyz.com</Email>" 
            + "      <UserData id='id33' type='AttributesInContext'>"
            + "         <UserValue value='6in' title='Heigh'></UserValue>"
            + "         <UserValue value='' title='Weight'></UserValue>" 
            + "      </UserData>" 
            + "  </Employee>"
            + "  <Employee id='3'>"
            + "      <Email>tp@xyz.com</Email>" 
            + "      <UserData id='id34' type='AttributesInContext'>"
            + "         <UserValue value='4in' title='Heigh'></UserValue>"
            + "         <UserValue value='' title='Weight'></UserValue>" 
            + "      </UserData>" 
            + "  </Employee>"
            + "</Company>";

    public static void main(String[] args) throws Exception {

        try (InputStream in = new ByteArrayInputStream(TEST_XML.getBytes(Charset.defaultCharset()));
                ByteArrayOutputStream out = new ByteArrayOutputStream(4096)) {
            convert(in, out);
            System.out.print(out.toString());
        }

    }

【讨论】:

  • 这实际上比上述解决方案效果更好。非常感谢朋友!
  • 另外,有什么方法可以让我选择特定的 rowTag,然后仅根据该 rowTag 的条目展平 xml 文件?
  • 跳过 util "MyTag".equals( startElement.getName().getLocalPart() ) 然后解析直到结束。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-04
  • 2011-03-05
  • 2015-10-28
  • 2014-11-22
  • 2017-04-18
相关资源
最近更新 更多