【Java】JavaでXML扱うためにJavaQuery（作りかけ）

#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オブジェクトの名前を$に固定したかっただけ。

```java: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
```