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