XPath 関数拡張
XPath2.0 は正規表現が使えるようになっていて反則的に便利なのだけど、標準で使える環境は(たぶん)あまりなく Java でもまだ使えない。
Java から XPath2.0 使いたい
http://twitter.com/hetappi/statuses/500570172
10:47 PM December 14, 2007 from TwitterIrcGateway
Twitter で呟けば何か反応があるかと期待したのだけど、私のコミュニケーション能力が低いせいか全くありませんでした…。
で、調べた。
XPath2.0 実装ライブラリ
このへんのライブラリで使えるようになる。
XPath 関数拡張
このへんのインタフェースを実装すれば XPath 関数を拡張できるので、自分で作っちゃってもよいかも。
- javax.xml.namespace.NamespaceContext
- javax.xml.xpath.XPathFunctionResolver
- javax.xml.xpath.XPathFunction
さっくり matches
7.6.2 fn:matches
http://www.w3.org/TR/xquery-operators/#func-matches
fn:matches($input as xs:string?, $pattern as xs:string) as xs:boolean
fn:matches($input as xs:string?, $pattern as xs:string, $flags as xs:string) as xs:boolean
を実装してみた。
ソース
package tmp; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathFunction; import javax.xml.xpath.XPathFunctionException; import javax.xml.xpath.XPathFunctionResolver; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; class XPathFunctionImplLazyMatches implements XPathFunction { public Object evaluate(List args) throws XPathFunctionException { Pattern pat; if (args.size() == 2) { pat = Pattern.compile((String) args.get(1)); } else if (args.size() == 3) { int flag = 0; String arg2 = (String) args.get(2); for (int i = arg2.length() - 1; i >= 0; --i) { switch (arg2.charAt(i)) { case 's': flag |= Pattern.DOTALL; break; case 'm': flag |= Pattern.MULTILINE; break; case 'i': flag |= Pattern.CASE_INSENSITIVE; break; case 'x': flag |= Pattern.COMMENTS; break; default: return new XPathFunctionException(""); } } pat = Pattern.compile((String) args.get(1), flag); } else { return new XPathFunctionException(""); } Object obj = args.get(0); String value = (obj instanceof NodeList) ? ((NodeList) obj).item(0).getTextContent() : obj.toString(); return pat.matcher(value).matches(); } } class XPathFunctionResolverImpl implements XPathFunctionResolver { private static final QName qname = new QName(NamespaceContextImpl.XPATH_EXT_NS_URI, "matches"); public XPathFunction resolveFunction(QName functionName, int arity) { return qname.equals(functionName) && (arity == 2 || arity == 3) ? new XPathFunctionImplLazyMatches() : null; } } class NamespaceContextImpl implements NamespaceContext { public static final String XPATH_EXT_NS_URI = "http://d.hatena.ne.jp/hetappi/xpath-ext"; public String getNamespaceURI(String prefix) { if (prefix == null) throw new NullPointerException(); return "ext".equals(prefix) ? XPATH_EXT_NS_URI : "xml".equals(prefix) ? XMLConstants.XML_NS_URI : XMLConstants.NULL_NS_URI; } public String getPrefix(String namespaceURI) { throw new UnsupportedOperationException(); } public Iterator getPrefixes(String namespaceURI) { throw new UnsupportedOperationException(); } } public class TestXPathExt { public static void main(String[] args) throws Exception { XPathFactory xpf = XPathFactory.newInstance(); xpf.setXPathFunctionResolver(new XPathFunctionResolverImpl()); XPath xpath = xpf.newXPath(); xpath.setNamespaceContext(new NamespaceContextImpl()); Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse("./in.xml"); NodeList nodes = (NodeList) xpath.evaluate( "//item[ext:matches(.,'\\d{1,3}')]", doc, XPathConstants.NODESET); for (int i = nodes.getLength() - 1; i >= 0; --i) { nodes.item(i).setTextContent("99999"); } Node node = (Node) xpath.evaluate( "//item[ext:matches(.,'abCdEF', 'i')]", doc, XPathConstants.NODE); if (node != null) node.setTextContent("XXXXX"); Transformer trans = TransformerFactory.newInstance().newTransformer(); trans.setOutputProperty(OutputKeys.INDENT, "yes"); trans.transform(new DOMSource(doc), new StreamResult(System.out)); // trans.transform(new DOMSource(doc), new StreamResult("./out.xml")); } }
実行結果
D:\Temp>type in.xml <?xml version="1.0" encoding="UTF-8" standalone="no"?> <items> <item>1</item> <item>123</item> <item>123456</item> <item>aBcdeF</item> </items> D:\Temp>java -classpath bin tmp.TestXPathExt <?xml version="1.0" encoding="UTF-8" standalone="no"?> <items> <item>99999</item> <item>99999</item> <item>123456</item> <item>XXXXX</item> </items> D:\Temp>