12

@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 から借用しましたが、私のオブジェクトは常に同じであるとは限りません。ここで、両方のケースから次のような 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 にも含める必要があります。今日、スタックスへの便利なリンクをいくつか手に入れました。これに対する正しい/最適なアプローチが何であるかは本当にわかりません.3日間これに苦労していますが、まだあきらめたくありません.

この問題をどう解決するか教えてください

これは最大1GBの非常に巨大なxmlファイルであることを忘れていました

報奨金の更新:

私はより一般的なアプローチを探しています。つまり、これは任意の深さの任意の数のノードで機能する必要があり、xml の例のように、あるitemオブジェクトのノード数が次/前のオブジェクトよりも多い場合があるため、その場合にも当てはまります(したがって、すべての列と値が CSV で一致します)。

また、ノードの名前/ローカル名が同じで、値と属性が異なる場合もあります。その場合、新しい列が適切な値で CSV に表示されます。<averages>(このケースの例をというタグ内に追加しましたcategory)

4

8 に答える 8

14

提供されるコードは、最終的な記事ではなくスケッチと見なされるべきです。私は SAX の専門家ではありません。パフォーマンスの向上やコードの簡素化などのために実装を改善することができます。SAX は大きな XML ファイルのストリーミングに対応できるはずです。

私は、SAX パーサーを使用して 2 つのパスでこの問題に取り組みます。(ちなみに、CSV 生成ライブラリを使用して出力を作成します。これは、CSV に含まれるすべての厄介な文字エスケープを処理するためですが、スケッチではこれを実装していません)。

最初のパス: ヘッダー列の数を確立する

2 パス目: 出力 CSV

XML ファイルは適切な形式になっていると思います。事前定義された順序を持つスキーム/DTD がないと仮定します。

最初のパスでは、テキスト コンテンツを含むすべての XML 要素または任意の属性に対して CSV 列が追加されると想定しました (属性には何かが含まれると想定しました!)。

ターゲット列の数を確立した 2 番目のパスは、実際の 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;
        }
    }
}
于 2010-07-30T00:13:02.400 に答える
9

これは、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>
于 2010-07-30T00:42:50.223 に答える
6

ソリューションがどれほど一般的であるかを理解しているかどうかはわかりません。一般的なソリューションのために、1 GB のファイルを 2 回解析しますか? <category>また、一般的なものが必要な場合は、例で要素をスキップしたのはなぜですか? どのくらいの異なるフォーマットを処理する必要がありますか? フォーマットが何であるか本当にわかりませんか(一部の要素を省略できる場合でも)?明確にできますか?

私の経験では、特定のファイルを特定の方法で解析することが一般的に望ましいです (ただし、これは汎用 API の使用を排除するものではありません)。私の答えはこの方向に進みます(明確化後に更新します)。


XML に慣れていない場合は、Ricebridge XML ManagerCSV Managerなどの既存の (商用) ライブラリの使用を検討できます。完全な例については、「Java を使用して CSV を XML に変換する方法」および「XML を CSV に変換する方法」を参照してください。アプローチは非常に簡単です。XPath 式を使用してデータ フィールドを定義し (「追加の」要素を使用できるため、このケースでは完璧です)、ファイルを解析し、結果Listを CSV コンポーネントに渡して CSV ファイルを生成します。API はシンプルに見え、コードはテスト済み (テストケースのソースコードはBSD スタイルのライセンスで入手可能)、ギガバイトサイズのファイルをサポートしていると主張しています。

170 ドルで単一開発者ライセンスを取得できます。これは、開発者の 1 日あたりの料金に比べてそれほど高くありません。

彼らは 30 日間の試用版を提供しています。


別のオプションは、Spring Batchを使用することです。Spring バッチは、XML ファイル入力または出力として (StAX および選択した XML バインディング フレームワークを使用して)、入力または出力としてフラット ファイルを操作するために必要なすべてを提供します。見る:


また、 Smooksを使用して XML から CSVへの変換を行うこともできます。以下も参照してください。


もう 1 つのオプションは、StAX パーサーを使用するか、VTD-XMLと XPath を使用して、独自のソリューションを作成することです。見て:

于 2010-07-30T05:37:46.933 に答える
2

ここでは、StAX を使用して XML から CSV への変換を実装するコードをいくつか示します。あなたが示した 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();
    }    
}
于 2010-07-27T18:25:21.590 に答える
2

説明した要件に基づいてコーディングする最良の方法は、FreeMarker と XML 処理の簡単な機能を使用することです。ドキュメントを参照してください

この場合、CSV を生成するテンプレートのみが必要になります。

これに代わるものはXMLGenですが、アプローチは非常に似ています。その図と例を見るだけで、SQL ステートメントの代わりに、CSV を出力します。

これらの 2 つの同様のアプローチは「従来型」ではありませんが、状況に応じて非常に迅速に機能し、XSL を学ぶ必要はありません (マスターするのは非常に難しいと思います)。

于 2010-07-20T19:32:43.130 に答える
1

私は、SAX があなたにとって最良のアプローチであるとは確信していません。ただし、ここで SAX を使用する方法はいくつかあります。

ListingDetails などの特定の要素内で要素の順序が保証されていない場合は、積極的に対応する必要があります。

ListingDetails を開始するときは、マップをハンドラーのメンバー変数として初期化します。各サブ要素で、そのマップに適切なキー値を設定します。ListingDetails を終了したら、マップを調べて、不足している要素の null などの値を明示的にモックします。アイテムごとに 1 つの ListingDetails があると仮定して、それをハンドラーのメンバー変数に保存します。

item 要素が終了したら、マップに基づいて CSV の行を必要な順序で書き込む関数を用意します。

これに伴うリスクは、XML が破損している場合です。アイテムの開始時にこれらすべての変数を null に設定し、エラーをチェックしてアイテムの終了時にそれらをアナウンスすることを強く検討します。

于 2010-07-20T19:06:49.450 に答える
1

これは、ほとんどの XSLT プロセッサが XML ファイル全体をメモリに読み込むことを除いて、XSLT を使用する主要な例であることに注意してください。これは、サイズが大きいためオプションではありません。ただし、Saxon のエンタープライズ バージョンはストリーミング XSLT 処理を実行できることに注意してください (XSLT スクリプトが制限に準拠している場合)。

該当する場合は、代わりに JVM の外部の外部 XSLT プロセッサを使用することもできます。これにより、さらにいくつかのオプションが開かれます。

Saxon-EE でのストリーミング: http://www.saxonica.com/documentation/sourcedocs/serial.html

于 2010-08-01T13:00:44.917 に答える
0

XStream ( http://x-stream.github.io/ ) または JOX ( http://www.wutka.com/jox.html ) を使用して xml を認識し、それを Java Bean に変換できます。Bean を取得したら、Bean を自動的に CSV に変換できると思います。

于 2010-07-20T19:13:19.887 に答える