package org.openprovenance.prov.model; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.util.LinkedList; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** Utility class for processing DOM {@link Element}, in particular conversion from a PROV attribute to an {@link Element} and vice-versa. * * @author lavm * */ final public class DOMProcessing { private static final String XML_LITERAL = "XMLLiteral"; private static final String RDF_PREFIX = NamespacePrefixMapper.RDF_PREFIX; private static final String RDF_NAMESPACE = NamespacePrefixMapper.RDF_NS; private static final String RDF_LITERAL = RDF_PREFIX + ":" + XML_LITERAL; public static final String XSD_NS_FOR_XML = "http://www.w3.org/2001/XMLSchema"; //Note, this is the xml schema namespace URI without # private final static String FOR_XML_XSD_QNAME=NamespacePrefixMapper.XSD_NS + "QName"; private final QualifiedNameUtils qnU=new QualifiedNameUtils(); private final ProvFactory pFactory; public DOMProcessing(ProvFactory pFactory) { this.pFactory=pFactory; } static public DocumentBuilder builder; static void initBuilder() { try { DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); docBuilderFactory.setNamespaceAware(true); builder = docBuilderFactory.newDocumentBuilder(); } catch (ParserConfigurationException ex) { throw new RuntimeException(ex); } } static { initBuilder(); } static public String qualifiedNameToString(QualifiedName name) { Namespace ns=Namespace.getThreadNamespace(); return ns.qualifiedNameToString(name); } static public String qualifiedNameToString(javax.xml.namespace.QName name) { Namespace ns=Namespace.getThreadNamespace(); return ns.qualifiedNameToString(name); } /** * Converts a string to a QualifiedName, extracting namespace from the DOM. Ensures that the generated qualified name is * properly escaped, according to PROV-N syntax. * * @param str * string to convert to QualifiedName * @param el * current Element in which this string was found (as attribute * or attribute value) * @return a qualified name {@link QualifiedName} */ final public QualifiedName stringToQualifiedName(String str, org.w3c.dom.Element el) { if (str == null) return null; int index = str.indexOf(':'); if (index == -1) { QualifiedName qn = pFactory.newQualifiedName(el.lookupNamespaceURI(null), // find default namespace // namespace str, null); return qn; } String prefix = str.substring(0, index); String local = str.substring(index + 1, str.length()); String escapedLocal=qnU.escapeProvLocalName(qnU.unescapeFromXsdLocalName(local)); QualifiedName qn = pFactory.newQualifiedName(convertNsFromXml(el.lookupNamespaceURI(prefix)), escapedLocal, prefix); return qn; } public static String convertNsToXml(String uri) { if (NamespacePrefixMapper.XSD_NS.equals(uri)) { return XSD_NS_FOR_XML; } return uri; } final public String convertNsFromXml(String uri) { if (XSD_NS_FOR_XML.equals(uri)) { return NamespacePrefixMapper.XSD_NS; } return uri; } /** Creates a DOM {@link Element} for a {@link QualifiedName} and content given by value * * @param elementName a {@link QualifiedName} to denote the element name * @param value for the created {@link Element} * @return a new {@link Element} */ final static public Element newElement(QualifiedName elementName, QualifiedName value) { org.w3c.dom.Document doc = builder.newDocument(); Element el = doc.createElementNS(elementName.getNamespaceURI(), qualifiedNameToString(elementName.toQName())); // 1. we add an xsi:type="xsd:QName" attribute // making sure xsi and xsd prefixes are appropriately declared. el.setAttributeNS(NamespacePrefixMapper.XSI_NS, "xsi:type", "xsd:QName"); el.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xsd", XSD_NS_FOR_XML); // 2. We add the QualifiedName's string representation as child of the element // This representation depends on the extant prefix-namespace mapping String valueAsString=qualifiedNameToString(value.toQName()); el.appendChild(doc.createTextNode(valueAsString)); // 3. We make sure that the QualifiedName's prefix is given the right namespace, or the default namespace is declared if there is no prefix int index=valueAsString.indexOf(":"); if (index!=-1) { String prefix = valueAsString.substring(0, index); el.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:"+prefix, convertNsToXml(value.getNamespaceURI())); } else { el.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", convertNsToXml(value.getNamespaceURI())); } doc.appendChild(el); return el; } /** * Creates a DOM {@link Element} for a {@link QualifiedName} and content given by value and type * @param elementName a {@link QualifiedName} to denote the element name * @param value for the created {@link Element} * @param type of the value * @return a new {@link Element} */ final static public Element newElement(QualifiedName elementName, String value, QualifiedName type) { org.w3c.dom.Document doc = builder.newDocument(); String qualifiedNameString; qualifiedNameString= qualifiedNameToString(elementName.toQName()); Element el = doc.createElementNS(elementName.getNamespaceURI(), qualifiedNameString); el.setAttributeNS(NamespacePrefixMapper.XSI_NS, "xsi:type", qualifiedNameToString(type)); //if (withNullLocal || withDigit) { // el.setAttributeNS(NamespacePrefixMapper.PROV_NS, "prov:local", localPart); //} el.appendChild(doc.createTextNode(value)); doc.appendChild(el); return el; } final public static Element newElement(QualifiedName qualifiedName, String value, QualifiedName type, String lang) { org.w3c.dom.Document doc = builder.newDocument(); Element el = doc.createElementNS(qualifiedName.getNamespaceURI(), qualifiedNameToString(qualifiedName.toQName())); el.setAttributeNS(NamespacePrefixMapper.XSI_NS, "xsi:type", qualifiedNameToString(type)); el.setAttributeNS(NamespacePrefixMapper.XML_NS, "xml:lang", lang); el.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xml", NamespacePrefixMapper.XML_NS); el.appendChild(doc.createTextNode(value)); doc.appendChild(el); return el; } final static public Element newElement(QualifiedName name, Element value) { org.w3c.dom.Document doc = builder.newDocument(); Element el = doc.createElementNS(name.getNamespaceURI(), qualifiedNameToString(name)); el.setAttributeNS(NamespacePrefixMapper.XSI_NS, "xsi:type", RDF_LITERAL); el.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:"+RDF_PREFIX, RDF_NAMESPACE); el.appendChild(doc.importNode(value, true)); return el; } static public void writeDOMToPrinter(Node document, StreamResult result, boolean formatted) throws TransformerConfigurationException, TransformerException { // Use a Transformer for output TransformerFactory tFactory = TransformerFactory.newInstance(); Transformer transformer = tFactory.newTransformer(); DOMSource source = new DOMSource(document); // transformer.setOutputProperty(OutputKeys.ENCODING,"ISO-8859-1"); // transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,"users.dtd"); transformer.setOutputProperty(OutputKeys.METHOD,"xml"); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); if (formatted) { transformer.setOutputProperty(OutputKeys.INDENT, "yes"); } else { transformer.setOutputProperty(OutputKeys.INDENT, "no"); } transformer.transform(source, result); } static public void writeDOMToPrinter(Node document, Writer out, boolean formatted) throws TransformerConfigurationException, TransformerException { StreamResult result = new StreamResult(out); writeDOMToPrinter(document,result,formatted); } static public String writeToString (Node toWrite) throws TransformerConfigurationException, TransformerException { StringWriter sw=new StringWriter(); writeDOMToPrinter(toWrite,new PrintWriter(sw),false); return sw.toString(); } public static void trimNode(Node node) { List<org.w3c.dom.Text> nodes=new LinkedList<org.w3c.dom.Text>(); trimNode(node,nodes); for (org.w3c.dom.Text n: nodes){ if (n.getTextContent().equals("")) n.getParentNode().removeChild(n); } } static void trimNode(Node node, List<org.w3c.dom.Text> nodes) { if (node.getNodeType() == Node.TEXT_NODE) { node.normalize(); org.w3c.dom.Text txt = (org.w3c.dom.Text) node; txt.setTextContent(txt.getTextContent().trim()); nodes.add(txt); } else { NodeList nl = node.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { trimNode(nl.item(i), nodes); } } } final static public org.w3c.dom.Element marshalAttribute(org.openprovenance.prov.model.Attribute attribute) { return marshalTypedValue(attribute, attribute.getElementName()); } final static public org.w3c.dom.Element marshalTypedValue(org.openprovenance.prov.model.TypedValue attribute, QualifiedName elementName) { Object value = attribute.getValue(); QualifiedName type = attribute.getType(); if (value instanceof LangString) { LangString istring = ((LangString) value); return newElement(elementName, istring.getValue(), attribute.getType(), istring.getLang()); } else if (value instanceof QualifiedName) { return newElement(elementName, (QualifiedName) value); } else if (type.getNamespaceURI().equals(RDF_NAMESPACE) && type.getLocalPart().equals(XML_LITERAL)) { return newElement(elementName, (org.w3c.dom.Element) attribute.getConvertedValue()); } else { return newElement(elementName, value.toString(), attribute.getType()); } } final public org.openprovenance.prov.model.Attribute unmarshallAttribute(org.w3c.dom.Element el, ProvFactory pFactory, ValueConverter vconv) { String prefix = el.getPrefix(); String namespace = el.getNamespaceURI(); String local = el.getLocalName(); String child = el.getTextContent(); String typeAsString = el.getAttributeNS(NamespacePrefixMapper.XSI_NS, "type"); String lang = el.getAttributeNS(NamespacePrefixMapper.XML_NS, "lang"); QualifiedName type = ((typeAsString == null) || (typeAsString.equals(""))) ? null : stringToQualifiedName(typeAsString, el); Name name=pFactory.getName(); if (type == null) type = name.XSD_STRING; if (FOR_XML_XSD_QNAME.equals(type.getUri())) { QualifiedName qn = stringToQualifiedName(child, el); Attribute x= pFactory.newAttribute(namespace, local, prefix, qn, name.PROV_QUALIFIED_NAME); return x; } else if (type.equals(name.RDF_LITERAL)) { NodeList nodes=el.getChildNodes(); org.w3c.dom.Element content=null; for (int i=0; i<nodes.getLength(); i++) { Node node=nodes.item(i); if (node instanceof org.w3c.dom.Element) { content=(org.w3c.dom.Element)node; break; } } return pFactory .newAttribute(namespace, local, prefix, content, type); } else if ((lang == null) || (lang.equals(""))) { return pFactory .newAttribute(namespace, local, prefix, vconv.convertToJava(type, child), type); } else { return pFactory.newAttribute(namespace, local, prefix, pFactory .newInternationalizedString(child, lang), type); } } }