1
1

More than 5 years have passed since last update.

【Java】JavaでXML扱うためにJavaQuery(作りかけ)

Last updated at Posted at 2016-10-08

JavaでDOMを扱うためのjQueryのようなものver0.1

最近のwebapiはjsonでやり取りするのが主流(多分)。
でもXMLで通信しなきゃいけない場合もある。
さらには両方扱わないといけない場合もある。

今まさにそういうシーンに遭遇しています。
XMLとjson両方を同じように扱うにしたい・・・ということで、一旦DOMに変換して扱うことにしました。
幸いJava8ともなると標準のライブラリでDOM操作ができるようです。(org.w3c.dom以下のやつ)
しかしこれ、検索が使いにくい。
JavaScriptよろしくgetElementsBy...な感じで検索して、NodeListオブジェクトを結果として受け取って、Nodeオブジェクト取り出して、Elementにキャストして。。。
XMLもjsonも解析してくれるから非常に便利なんだけども・・・。

目的

  • JavaでWebAPI作成
  • 通信はXML/JSON両方からくるのでDOMで扱いたい
  • 今のところ検索ができればいい

実装

見たらわかるかも知れないですがJavaQueryクラスは飾りです。
保持してあるjQueryオブジェクトの名前を$に固定したかっただけ。

JavaQuery.java
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * jQuery風にDocumentオブジェクトを操作したかった
 * @author deigo
 *
 */
public class JavaQuery {

    private static DocumentBuilder documentBuilder;
    static{
        try {
            documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        }
    }


// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// オブジェクト生成関連

    public jQuery $;
    public jQuery $() {
        return create();
    }
    public jQuery $(String s) {

        // セレクタなのかXML文字列なのか
        boolean isSelector = s.indexOf("<") >= 0;

        if (false == isSelector) {
            return $.find(s);// セレクタの場合は要素を返却
        } else {
            return create(s);// XML文字列の場合はパースする
        }
    }
    public jQuery $(Element e) {
        return create(e);
    }
    public jQuery $(Document d){
        return create(d);
    }


    /**
     * ドキュメントから$を生成
     * */
    private static jQuery create(Document d){
        return create(new ArrayList<>(Arrays.asList(d.getDocumentElement())));
    }
    private static jQuery create(List<Element> e){
        return new jQuery(e);
    }
    private static jQuery create(Element e){
        return create(new ArrayList<>(Arrays.asList(e)));
    }
    private static jQuery create() {
        return create(documentBuilder.newDocument());
    }
    private static jQuery create(String s){
        jQuery result = null;

        try {
            result = create(documentBuilder.parse(new ByteArrayInputStream(s.getBytes("UTF-8"))));
        } catch (Exception e) {
            //FIXME 適切な処理を
            e.printStackTrace();
        }

        return result;
    }

// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// コンストラクタ関連

    /** コンストラクタ */
    public JavaQuery() {
        this.$ = create();
    }
    /** コンストラクタ */
    public JavaQuery(Document d) {
        this.$ = create(d);
    }
    /** コンストラクタ */
    public JavaQuery(Element e) {
        this.$ = create(e);
    }
    /** コンストラクタ */
    public JavaQuery(String s) {
        this.$ = create(s);
    }


// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
    /**
     * jQuery風オブジェクト
     * @author deigo
     *
     */
    public static class jQuery {

        /** 呼び出し元のjQueryオブジェクト */
        private jQuery prev;
        /** このjQueryオブジェクトが保持している要素リスト */
        private List<Element> elements;


        /** コンストラクタ */
        private jQuery(List<Element> elements, jQuery prev) {
            this.elements = new ArrayList<>();
            this.elements.addAll(elements);
            this.prev = prev;
        }
        /** コンストラクタ */
        private jQuery(List<Element> elements) {
            this.elements = new ArrayList<>();
            this.elements.addAll(elements);
            this.prev = null;
        }

        /* 検索 */

        /**
         * 最初の要素のDOMツリーを検索<br>
         * FIXME 疑似セレクタ対応検討
         * FIXME find および findcoreは最適化されてない とりあえず動くだけ
         * @param selectorString
         * @return 検索結果の$オブジェクト
         */
        public jQuery find(String selectorString){
            jQuery result = null;

            List<String> selectors = new ArrayList<>();
            Arrays.asList(selectorString.split(",")).forEach(selector -> {selectors.add(selector.trim());});

            List<Element> elements = new ArrayList<>();
            for (String selector : selectors) {

                // w3cのオブジェクトにID等から引き当てる機能がないのでタグ名のみ考慮
                elements.addAll(findCore(this.elements.get(0), selector));
            }

            result = new jQuery(elements, this);

            return result;
        }
        /** findのコア処理 */
        private List<Element> findCore(Element element, String selector){
            List<Element> result = new ArrayList<>();

            String subselector = "";
            {
                Pattern p = Pattern.compile("\\s+");
                Matcher m = p.matcher(selector);
                if (m.find()) {
                    subselector = selector.substring(0, m.start());
                    selector = selector.substring(m.end());
                }
            }

            if (null != subselector && false == subselector.isEmpty()) {
                NodeList nl = element.getElementsByTagName(subselector);
                for (int i=0; i<nl.getLength(); i++) {
                    result.addAll( findCore((Element)nl.item(i), selector) );
                }

            } else {
                NodeList nl = element.getElementsByTagName(selector);
                if (nl.getLength()>0) {
                    for (int i=0; i<nl.getLength(); i++) {
                        result.add((Element)nl.item(i));
                    }
                }
            }

            return result;
        }

        /**
         * 子要素を返却
         * @return 子要素の$オブジェクト
         */
        public jQuery children() {
            jQuery result = null;

            NodeList nl = this.elements.get(0).getChildNodes();

            List<Element> elements = new ArrayList<>();
            for (int i=0; i<nl.getLength(); i++) {
                elements.add((Element)nl.item(i));
            }

            result =  new jQuery(elements, this);

            return result;
        }

        /**
         * 親要素を返却
         * @return 親要素の$オブジェクト
         */
        public jQuery parent() {
            jQuery result = null;

            List<Element> elements = new ArrayList<>();
            elements.add((Element)this.elements.get(0).getParentNode());
            result =  new jQuery(elements, this);

            return result;
        }

        /**
         * 呼び出し元の$オブジェクトにポインタを戻す
         * @return 呼び出し元の$オブジェクト
         */
        public jQuery end() {
            return this.prev;
        }

        /* 制御 */

        /**
         * 繰り返し処理<br>
         * 要素の書き換えはできない
         * @param c
         * @return
         */
        public jQuery each(Consumer<Element> c){

            this.elements.forEach(c);
            return this;
        }

        /* 要素返却 */

        /**
         * 要素リストを返却
         * @return
         */
        public List<Element> get() {
            return this.elements;
        }
        /**
         * i番目の要素を返却
         * @param i
         * @return
         */
        public Element get(int i) {
            return this.elements.get(i);
        }

        /**
         * テキストノードを返却
         * @return テキストノード
         */
        public String text() {

            return this.elements.get(0).getTextContent();
        }

        /**
         * テキストノードを更新 *モックです*
         * @param text
         * @return
         */
        public jQuery text(String text) {

            this.elements.get(0).setTextContent(text);
            return this;
        }

    }
}

正直ムリだろうと思っていた.eachメソッドも、lambdaが使えるようになっているため
簡単に実装できました。ランバダすごい。←
$が要素リストを持っていた場合、検索対象は基本的に最初の要素です。
ここら辺はjQueryと同じだと思う。

使用方法

        JavaQuery j = new JavaQuery();

        // 要素を抽出して文字列を取り出す
        String order_no= j.$(document).find("order").find("order_no").text();

        // 複数要素の扱い
        j.$(document).find("items").find("item").each(_this_->{
            System.out.println($(_this_).find("item_code").text());
            System.out.println($(_this_).find("name").text());
        });

課題

  • 要素の編集ができるようにしたい。
  • jQueryと違って検索対象のDOMがころころ変わるので、要検証。
  • find見直し
  • 属性を扱えるようにする。
  • 擬似セレクタも扱えるようにする。
  • エラーハンドリング

修正

2016/10/13 indexOfの判定が誤ってたので修正

boolean isSelector = s.indexOf("<") > 0;//x
boolean isSelector = s.indexOf("<") >= 0;//o
1
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1