/* * Copyright 2008-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.nominanuda.xml; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.Reader; import java.io.StringReader; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import java.util.Arrays; import java.util.Iterator; import java.util.Properties; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Templates; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.w3c.dom.Document; import org.w3c.dom.DocumentType; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLFilterImpl; import com.nominanuda.code.ThreadSafe; import com.nominanuda.lang.Check; import com.nominanuda.lang.Strings; @ThreadSafe public class XmlHelper { public static final XmlHelper XML = new XmlHelper(); private final ThreadLocal<DocumentBuilder> documentBuilder = new ThreadLocal<DocumentBuilder>() { @Override protected DocumentBuilder initialValue() { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); try { return dbf.newDocumentBuilder(); } catch (ParserConfigurationException e) { throw new IllegalStateException(e); } } }; private final SAXTransformerFactory txFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance(); { try { identity = txFactory.newTemplates(new StreamSource( new StringReader(identityXslt))); } catch (TransformerConfigurationException e) { throw new IllegalStateException(e); } } private final Templates identity; public Document newDocument() { return documentBuilder.get().newDocument(); } public String xmlEscape(String s) { final StringBuilder result = new StringBuilder(); final StringCharacterIterator iterator = new StringCharacterIterator(s); char character = iterator.current(); while (character != CharacterIterator.DONE) { switch (character) { case '<': result.append("<"); break; case '>': result.append(">"); break; case '"': result.append("""); break; case '\'': result.append("'"); break; case '&': result.append("&"); break; default: result.append(character); break; } character = iterator.next(); } return result.toString(); } public String xmlEscapeNoAposAndQuote(String s) { final StringBuilder result = new StringBuilder(); final StringCharacterIterator iterator = new StringCharacterIterator(s); char character = iterator.current(); while (character != CharacterIterator.DONE) { switch (character) { case '<': result.append("<"); break; case '>': result.append(">"); break; case '&': result.append("&"); break; default: result.append(character); break; } character = iterator.next(); } return result.toString(); } public Document parseAsDocument(InputSource is) throws SAXException, IOException { Document doc; SAXParser sp = newParser(); doc = newDocument(); DOMBuilder domBuilder = new DOMBuilder(doc); XMLReader xr = sp.getXMLReader(); XMLFilterImpl xfi = new WhiteSpaceIgnoringFilter(xr); xfi.setContentHandler(domBuilder); xfi.parse(is); return doc; } private class WhiteSpaceIgnoringFilter extends XMLFilterImpl { public WhiteSpaceIgnoringFilter(XMLReader xr) { super(xr); } @Override public void characters(char[] ch, int start, int length) throws SAXException { if (isWhiteSpace(ch, start, length)) { // super.ignorableWhitespace(ch, start, length); } else { super.characters(ch, start, length); } } } public boolean isWhiteSpace(char ch[], int start, int length) { int last = start + length; for (int i = start; i < last; i++) { if (!Character.isWhitespace(ch[i])) { return false; } } return true; } public boolean isWhiteSpace(String str) { return isWhiteSpace(str.toCharArray(), 0, str.length()); } public TransformerHandler newIdentityTransformerHandler() { try { return txFactory.newTransformerHandler(identity); // return identity.newTransformer(); } catch (TransformerConfigurationException e) { throw new IllegalStateException(e); } } public TransformerHandler xslTransformerHandler(Reader xslt) { try { return txFactory.newTransformerHandler(xslTemplates(xslt)); } catch (TransformerConfigurationException e) { throw new IllegalStateException(e); } } public TransformerHandler xslTransformerHandler(Templates xslt) { try { return txFactory.newTransformerHandler(xslt); } catch (TransformerConfigurationException e) { throw new IllegalStateException(e); } } public Templates xslTemplates(Reader xslt) { try { return txFactory.newTemplates(new StreamSource(xslt)); } catch (TransformerConfigurationException e) { throw new IllegalStateException(e); } } public static final String identityXslt = "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">" + "<xsl:template match=\"/|@*|node()\">" + "<xsl:copy>" + "<xsl:apply-templates select=\"@* | node()\"/>" + "</xsl:copy>" + "</xsl:template>" + "</xsl:stylesheet>"; public SAXParser newParser() throws SAXException { SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setNamespaceAware(true); SAXParser sp; try { sp = spf.newSAXParser(); } catch (ParserConfigurationException e) { throw new SAXException(e); } return sp; } public String serializeUtf8(Node node) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); serialize(node, baos); return new String(baos.toByteArray(), Strings.UTF8); } public void serialize(Node node, OutputStream out) { Properties outputProperties = getDefaultSerilizationProperties(); DocumentType docType = null; if (node instanceof Document) { docType = ((Document) node).getDoctype(); } else { docType = node.getOwnerDocument().getDoctype(); } if (docType != null) { // TODO: set method. Actually it break the format end remove xml // declaration // outputProperties.put(OutputKeys.METHOD, docType.getName()); outputProperties.put(OutputKeys.DOCTYPE_PUBLIC, docType.getPublicId()); outputProperties.put(OutputKeys.DOCTYPE_SYSTEM, docType.getSystemId()); } serialize(node, out, outputProperties); } public void serialize(Node node, OutputStream out, Properties outputProperties) { try { Source xmlSource = new DOMSource(node); Result output = new StreamResult(out); Transformer transformer = TransformerFactory.newInstance() .newTransformer(); transformer.setOutputProperties(outputProperties); transformer.transform(xmlSource, output); } catch (Exception e) { throw new RuntimeException(e); } } private Properties getDefaultSerilizationProperties() { Properties outputProperties = new Properties(); outputProperties.put(OutputKeys.INDENT, "yes"); outputProperties.put(OutputKeys.OMIT_XML_DECLARATION, "yes"); return outputProperties; } public String xPathForString(Object nodeOrNodeList, String xpathExpr, String... nsBindings) throws IllegalArgumentException { return (String) xPath(nodeOrNodeList, xpathExpr, XPathConstants.STRING, nsBindings); } public Number xPathForNumber(Object nodeOrNodeList, String xpathExpr, String... nsBindings) throws IllegalArgumentException { return (Number) xPath(nodeOrNodeList, xpathExpr, XPathConstants.NUMBER, nsBindings); } public Boolean xPathForBoolean(Object nodeOrNodeList, String xpathExpr, String... nsBindings) throws IllegalArgumentException { return (Boolean) xPath(nodeOrNodeList, xpathExpr, XPathConstants.BOOLEAN, nsBindings); } public NodeList xPathForNodeList(Object nodeOrNodeList, String xpathExpr, String... nsBindings) throws IllegalArgumentException { return (NodeList) xPath(nodeOrNodeList, xpathExpr, XPathConstants.NODESET, nsBindings); } public Node xPathForNode(Object nodeOrNodeList, String xpathExpr, String... nsBindings) throws IllegalArgumentException { return (Node) xPath(nodeOrNodeList, xpathExpr, XPathConstants.NODE, nsBindings); } /** * * @param nodeOrNodeList * @param xpathExpr * @param nsBindings in the form "n1","http://a.b.c/n1", "prefix2", "http://a.b.c/whatever" * @return * @throws XPathExpressionException */ private Object xPath(Object nodeOrNodeList, String xpathExpr, QName resultType, String... nsBindings) throws IllegalArgumentException { try { XPath xpath = XPathFactory.newInstance().newXPath(); xpath.setNamespaceContext(new MyNamespaceContext(nsBindings)); XPathExpression expr = xpath.compile(xpathExpr); Object result = expr.evaluate(nodeOrNodeList, resultType); return result; } catch (XPathExpressionException e) { //TODO return null; //throw new IllegalArgumentException(e); } } private static class MyNamespaceContext implements NamespaceContext { private String[] nsBindings = new String[0]; public MyNamespaceContext(String[] nsBindings) { if(nsBindings != null) { this.nsBindings= nsBindings; } Check.illegalargument.assertTrue(this.nsBindings.length % 2 == 0); } public String getNamespaceURI(String prefix) { for(int i = 0; i < nsBindings.length; i += 2) { if(nsBindings[i].equals(prefix)) { return nsBindings[i+1]; } } return XMLConstants.NULL_NS_URI; } public String getPrefix(String namespace) { if(nsBindings.length == 0) { return null; } else { for(int i = 1; i < nsBindings.length; i += 2) { if(nsBindings[i].equals(namespace)) { return nsBindings[i-1]; } } return null; } } public Iterator<?> getPrefixes(String namespace) { return Arrays.asList(getPrefix(namespace)).iterator(); } } }