Преобразуйте XML-файл в CSV в Java

@Before Там будет, вероятно, некоторыми дублирующимися предложениями вопросов, я не думаю, что это имеет место, возможно, читает это сначала, я попытаюсь быть максимально кратким. Заголовок дает основную идею.

Вот является пример 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>      

Вот является пример XML (случай 2):

          <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>

Я заимствовал этот XML у Google, так или иначе мои объекты являются не всегда тем же, иногда существуют дополнительные элементы как в 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

Эта 1-я строка является заголовком, это должно также быть включено в csv. Я получил некоторые полезные ссылки на stax сегодня, я действительно не делаю не, знают то, что является правильным/оптимальным подходом для этого, я борюсь с этим в течение 3 дней теперь, не действительно готовый сдаться все же.

Скажите мне, что Вы думаете, как был бы Вы решать это

Я забыл упоминать, что это - очень огромный XML-файл до 1 ГБ

ОБНОВЛЕНИЕ ЩЕДРОСТИ:

Я ищу больше Универсального подхода, подразумевая, что это должно работать на любое количество узлов с любой глубиной, и иногда как в примере xml, это может произойти что один item объект имеет большее количество узлов, чем следующий/предыдущий, таким образом, должен быть также случай для того (так все соответствие столбцов и значений в CSV).

Также это может произойти, что узлы имеют тот же name/localName, но различные значения и атрибуты, если это так, затем новый столбец должен появиться в CSV с соответствующим значением. (Я добавил пример этого случая внутри <averages> тег называют category)

11
задан ant 31 July 2010 в 10:28
поделиться

8 ответов

Приведенный код следует рассматривать как набросок, а не окончательную статью. Я не являюсь экспертом по SAX, и реализация может быть улучшена для повышения производительности, упрощения кода и т. Д. При этом SAX должен быть в состоянии справиться с потоковой передачей больших файлов XML.

Я бы подошел к этой проблеме с 2 проходами, используя синтаксический анализатор SAX. (Между прочим, я бы также использовал библиотеку генерации CSV для создания вывода, поскольку это будет иметь дело со всеми неудобными экранированиями символов, которые включает CSV, но я не реализовал это в моем эскизе).

Первый проход: Установите количество столбцов заголовка

Второй проход: Выходной CSV

Я предполагаю, что XML-файл сформирован правильно. Я предполагаю, что у нас нет схемы / DTD с заранее определенным порядком.

На первом проходе я предположил, что столбец CSV будет добавлен для каждого элемента XML, содержащего текстовое содержимое, или для любого атрибута (я предположил, что атрибуты будут что-то содержать!).

На втором проходе, после определения количества целевых столбцов, будет выполнен фактический вывод 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

Обратите внимание, что я использовал коллекции Google 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;
        }
    }
}
12
ответ дан 3 December 2019 в 02:19
поделиться

Лучший способ кодирования, основанный на описанных вами требованиях, - это использовать простую функцию FreeMarker и обработки XML. См. Документы .

В этом случае вам понадобится только шаблон, который будет создавать CSV.

Альтернативой этому является XMLGen , но очень похожий подход. Просто посмотрите на эту диаграмму и примеры, и вместо операторов SQL вы получите CSV.

Эти два похожих подхода не являются «традиционными», но очень быстро выполняют свою работу в вашей ситуации, и вам не нужно изучать XSL (я думаю, что это довольно сложно освоить).

2
ответ дан 3 December 2019 в 02:19
поделиться

Я не уверен, что SAX - лучший подход для вас. Однако здесь есть разные способы использования SAX.

Если порядок элементов не гарантируется в определенных элементах, таких как ListingDetails, вам необходимо проявлять инициативу.

Когда вы запускаете ListingDetails, инициализируйте карту как переменную-член в обработчике. В каждом подэлементе установите соответствующую пару "ключ-значение" на этой карте. Когда вы закончите ListingDetails, изучите карту и явно имитируйте значения, такие как нули, для отсутствующих элементов. Предполагая, что у вас есть один ListingDetails для каждого элемента, сохраните его в переменной-члене в обработчике.

Теперь, когда ваш элемент item закончен, у вас есть функция, которая записывает строку CSV на основе карты в нужном вам порядке.

Риск заключается в том, что вы испортили XML. Я бы настоятельно рекомендовал установить для всех этих переменных значение null при запуске элемента, а затем проверять наличие ошибок и сообщать о них, когда элемент заканчивается.

1
ответ дан 3 December 2019 в 02:19
поделиться

Вы можете использовать XStream ( http://x-stream.github.io/ ) или JOX ( http : //www.wutka.com/jox.html ), чтобы распознать xml и затем преобразовать его в Java Bean. Я думаю, вы можете автоматически преобразовать Beans в CSV, как только получите bean.

0
ответ дан 3 December 2019 в 02:19
поделиться

Вот код, который реализует преобразование XML в CSV с помощью StAX. Хотя XML, который вы привели, является лишь примером, я надеюсь, что это покажет вам, как работать с необязательными элементами.

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();
    }    
}
2
ответ дан 3 December 2019 в 02:19
поделиться

Ну, это примерно шесть или семь страниц в книге, так что это немного сложно для ответа на StackOverflow. :-)

Вот пример проекта с пользовательским View, который также обернут в пользовательский Preference. Это всего ~80 строк кода, хотя большая часть интеллектуальных возможностей пользовательского интерфейса связана с пользовательским классом View.

В двух словах, чтобы создать пользовательский DialogPreference, вам нужно определить:

  • конструкторы
  • onCreateDialogView() и onBindDialogView() для создания содержимого диалога и заполнения его данными предпочтения
  • onDialogClosed() для обработки, когда пользователь изменяет предпочтение
  • onGetDefaultValue() для обработки вашего общего значения по умолчанию, если предпочтение еще не установлено
  • onSetInitialValue() для загрузки значения предпочтения или использования вашего значения по умолчанию
-. 121---3949972-

Это выглядит как хороший случай для использования XSL. Учитывая ваши основные требования, может быть проще получить нужные узлы с помощью XSL по сравнению с пользовательскими парсерами или сериализаторами. Преимуществом будет то, что ваш XSL может нацеливаться на "//Item//AverageTime" или любые другие узлы, которые вам нужны, не заботясь о глубине узла.

UPDATE: Ниже приведен 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>
9
ответ дан 3 December 2019 в 02:19
поделиться

Обратите внимание, что это был бы яркий пример использования XSLT, за исключением того, что большинство процессоров XSLT считывают весь XML-файл в память, что не является вариантом, поскольку оно велико. Однако обратите внимание, что корпоративная версия Saxon может выполнять потоковую обработку XSLT (если сценарий XSLT соответствует ограничениям).

Вы также можете вместо этого использовать внешний XSLT-процессор вне JVM, если это применимо. Это открывает еще несколько вариантов.

Потоковая передача в Saxon-EE: http://www.saxonica.com/documentation/sourcedocs/serial.html

1
ответ дан 3 December 2019 в 02:19
поделиться

Я не уверен, что понимаю, насколько общим должно быть решение. Вы действительно хотите дважды разобрать файл размером 1 ГБ для универсального решения? И если вы хотите что-то общее, почему вы пропустили элемент в вашем примере? Сколько разных форматов вам нужно обработать? Вы действительно не знаете, каким может быть формат (даже если какой-то элемент может быть опущен)? Не могли бы вы пояснить?

По моему опыту, обычно предпочтительнее разбирать конкретные файлы конкретным способом (это не исключает использования общего API). Мой ответ будет идти в этом направлении (и я обновлю его после уточнения).


Если вы не чувствуете себя комфортно с XML, вы можете рассмотреть возможность использования некоторых существующих (коммерческих) библиотек, например, Ricebridge XML Manager и CSV Manager. Полный пример см. в Как преобразовать CSV в XML и XML в CSV с помощью Java. Подход довольно прост: вы определяете поля данных с помощью выражений XPath (что идеально подходит в вашем случае, поскольку вы можете иметь "дополнительные" элементы), разбираете файл, а затем передаете результат List компоненту CSV для создания CSV-файла. API выглядит простым, код протестирован (исходный код их тестовых примеров доступен под лицензией BSD), они заявляют о поддержке файлов гигабайтного размера.

Вы можете получить лицензию Single Developer за $170, что не очень дорого по сравнению с дневными ставками разработчиков.

Они предлагают 30-дневные пробные версии, посмотрите.


Другим вариантом может быть использование Spring Batch. Spring batch предлагает все необходимое для работы с XML файлами в качестве ввода или вывода (используя StAX и XML binding framework по вашему выбору) и плоскими файлами в качестве ввода или вывода. См.:


Вы также можете использовать Smooks для преобразования XML в CSVtransformations. Смотрите также:


Другим вариантом может быть создание собственного решения, используя парсер StAX или, почему бы и нет, используя VTD-XML и XPath. Посмотрите:

6
ответ дан 3 December 2019 в 02:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: