/*****************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.xmpbox.xml;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.XmpConstants;
import org.apache.xmpbox.schema.XMPSchema;
import org.apache.xmpbox.type.AbstractComplexProperty;
import org.apache.xmpbox.type.AbstractField;
import org.apache.xmpbox.type.AbstractSimpleProperty;
import org.apache.xmpbox.type.AbstractStructuredType;
import org.apache.xmpbox.type.ArrayProperty;
import org.apache.xmpbox.type.Attribute;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.ProcessingInstruction;
public class XmpSerializer
{
private DocumentBuilder documentBuilder = null;
private boolean parseTypeResourceForLi = true;
public XmpSerializer()
{
// xml init
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
try
{
documentBuilder = builderFactory.newDocumentBuilder();
}
catch (ParserConfigurationException e)
{
// never happens, because we don't call builderFactory#setAttribute
throw new RuntimeException(e);
}
}
public void serialize(XMPMetadata metadata, OutputStream os, boolean withXpacket) throws TransformerException
{
Document doc = documentBuilder.newDocument();
// fill document
Element rdf = createRdfElement(doc, metadata, withXpacket);
for (XMPSchema schema : metadata.getAllSchemas())
{
rdf.appendChild(serializeSchema(doc, schema));
}
// save
save(doc, os, "UTF-8");
}
protected Element serializeSchema(Document doc, XMPSchema schema)
{
// prepare schema
Element selem = doc.createElementNS(XmpConstants.RDF_NAMESPACE, "rdf:Description");
selem.setAttributeNS(XmpConstants.RDF_NAMESPACE, "rdf:about", schema.getAboutValue());
selem.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:" + schema.getPrefix(), schema.getNamespace());
// the other attributes
fillElementWithAttributes(selem, schema);
// the content
List<AbstractField> fields = schema.getAllProperties();
serializeFields(doc, selem, fields,schema.getPrefix(), null, true);
// return created schema
return selem;
}
public void serializeFields(Document doc, Element parent, List<AbstractField> fields, String resourceNS, String prefix, boolean wrapWithProperty)
{
for (AbstractField field : fields)
{
if (field instanceof AbstractSimpleProperty)
{
AbstractSimpleProperty simple = (AbstractSimpleProperty) field;
String localPrefix;
if (prefix != null && !prefix.isEmpty())
{
localPrefix = prefix;
}
else
{
localPrefix = simple.getPrefix();
}
Element esimple = doc.createElement(localPrefix + ":" + simple.getPropertyName());
esimple.setTextContent(simple.getStringValue());
List<Attribute> attributes = simple.getAllAttributes();
for (Attribute attribute : attributes)
{
esimple.setAttributeNS(attribute.getNamespace(), attribute.getName(), attribute.getValue());
}
parent.appendChild(esimple);
}
else if (field instanceof ArrayProperty)
{
ArrayProperty array = (ArrayProperty) field;
// property
Element asimple = doc.createElement(array.getPrefix() + ":" + array.getPropertyName());
parent.appendChild(asimple);
// attributes
fillElementWithAttributes(asimple, array);
// the array definition
Element econtainer = doc.createElement(XmpConstants.DEFAULT_RDF_PREFIX + ":" + array.getArrayType());
asimple.appendChild(econtainer);
// for each element of the array
List<AbstractField> innerFields = array.getAllProperties();
serializeFields(doc, econtainer, innerFields,resourceNS, XmpConstants.DEFAULT_RDF_PREFIX, false);
}
else if (field instanceof AbstractStructuredType)
{
AbstractStructuredType structured = (AbstractStructuredType) field;
List<AbstractField> innerFields = structured.getAllProperties();
// property name attribute
Element listParent = parent;
if (wrapWithProperty)
{
Element nstructured = doc
.createElement(resourceNS + ":" + structured.getPropertyName());
parent.appendChild(nstructured);
listParent = nstructured;
}
// element li
Element estructured = doc.createElement(XmpConstants.DEFAULT_RDF_PREFIX + ":" + XmpConstants.LIST_NAME);
listParent.appendChild(estructured);
if (parseTypeResourceForLi)
{
estructured.setAttribute("rdf:parseType", "Resource");
// all properties
serializeFields(doc, estructured, innerFields,resourceNS, null, true);
}
else
{
// element description
Element econtainer = doc.createElement(XmpConstants.DEFAULT_RDF_PREFIX + ":" + "Description");
estructured.appendChild(econtainer);
// all properties
serializeFields(doc, econtainer, innerFields,resourceNS, null, true);
}
}
else
{
// XXX finish serialization classes
System.err.println(">> TODO >> " + field.getClass());
}
}
}
private void fillElementWithAttributes(Element target, AbstractComplexProperty property)
{
// normalize the attributes list
List<Attribute> toSerialize = normalizeAttributes(property);
for (Attribute attribute : toSerialize)
{
if (XmpConstants.RDF_NAMESPACE.equals(attribute.getNamespace()))
{
target.setAttribute(XmpConstants.DEFAULT_RDF_PREFIX + ":" + attribute.getName(), attribute.getValue());
}
else
{
target.setAttribute(attribute.getName(), attribute.getValue());
}
}
for (Map.Entry<String, String> ns : property.getAllNamespacesWithPrefix().entrySet())
{
target.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":" + ns.getValue(), ns.getKey());
}
}
/** Normalize the list of attributes.
*
* Attributes which match a schema property are serialized as child elements
* so only return the ones which do not match a schema property
*
* @param property the property that needs to be inspected
* @return the list of attributed for serializing
*/
private List<Attribute> normalizeAttributes(AbstractComplexProperty property)
{
List<Attribute> attributes = property.getAllAttributes();
List<Attribute> toSerialize = new ArrayList<>();
List<AbstractField> fields = property.getAllProperties();
for (Attribute attribute : attributes)
{
boolean matchesField = false;
for (AbstractField field : fields)
{
if (attribute.getName().compareTo(field.getPropertyName()) == 0)
{
matchesField = true;
break;
}
}
if (!matchesField)
{
toSerialize.add(attribute);
}
}
return toSerialize;
}
protected Element createRdfElement(Document doc, XMPMetadata metadata, boolean withXpacket)
{
// starting xpacket
if (withXpacket)
{
ProcessingInstruction beginXPacket = doc.createProcessingInstruction("xpacket",
"begin=\"" + metadata.getXpacketBegin() + "\" id=\"" + metadata.getXpacketId() + "\"");
doc.appendChild(beginXPacket);
}
// meta element
Element xmpmeta = doc.createElementNS("adobe:ns:meta/", "x:xmpmeta");
xmpmeta.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:x", "adobe:ns:meta/");
doc.appendChild(xmpmeta);
// ending xpacket
if (withXpacket)
{
ProcessingInstruction endXPacket = doc.createProcessingInstruction("xpacket",
"end=\"" + metadata.getEndXPacket() + "\"");
doc.appendChild(endXPacket);
}
// rdf element
Element rdf = doc.createElementNS(XmpConstants.RDF_NAMESPACE, "rdf:RDF");
// rdf.setAttributeNS(XMPSchema.NS_NAMESPACE, qualifiedName, value)
xmpmeta.appendChild(rdf);
// return the rdf element where all will be put
return rdf;
}
/**
* Save the XML document to an output stream.
*
* @param doc
* The XML document to save.
* @param outStream
* The stream to save the document to.
* @param encoding
* The encoding to save the file as.
*
* @throws TransformerException
* If there is an error while saving the XML.
*/
private void save(Node doc, OutputStream outStream, String encoding) throws TransformerException
{
Transformer transformer = TransformerFactory.newInstance().newTransformer();
// human readable
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
// indent elements
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
// encoding
transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
// initialize StreamResult with File object to save to file
Result result = new StreamResult(outStream);
DOMSource source = new DOMSource(doc);
// save
transformer.transform(source, result);
}
}