/**
* This file is part of Archiv-Editor.
*
* The software Archiv-Editor serves as a client user interface for working with
* the Person Data Repository. See: pdr.bbaw.de
*
* The software Archiv-Editor was developed at the Berlin-Brandenburg Academy
* of Sciences and Humanities, Jägerstr. 22/23, D-10117 Berlin.
* www.bbaw.de
*
* Copyright (C) 2010-2013 Berlin-Brandenburg Academy
* of Sciences and Humanities
*
* The software Archiv-Editor was developed by @author: Christoph Plutte.
*
* Archiv-Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Archiv-Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Archiv-Editor.
* If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package org.bbaw.pdr.ae.export.xml.utils;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Vector;
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.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.bbaw.pdr.ae.common.AEConstants;
import org.bbaw.pdr.ae.common.CommonActivator;
import org.bbaw.pdr.ae.control.comparator.ReferenceByAuthorTitleComparator;
import org.bbaw.pdr.ae.control.core.XMLProcessor;
import org.bbaw.pdr.ae.control.core.XMLProcessorInterface;
import org.bbaw.pdr.ae.control.facade.Facade;
import org.bbaw.pdr.ae.export.pluggable.AeExportCoreProvider;
import org.bbaw.pdr.ae.metamodel.PdrId;
import org.bbaw.pdr.ae.model.Aspect;
import org.bbaw.pdr.ae.model.PdrObject;
import org.bbaw.pdr.ae.model.Person;
import org.bbaw.pdr.ae.model.ReferenceMods;
import org.bbaw.pdr.ae.model.view.OrderingHead;
import org.bbaw.pdr.ae.view.control.PDRObjectsProvider;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;
/**
* Holds given XML Data as a DOM tree and provides merging XML and transforming
* XML via XSLT and XSL-FO.
* <p>
* The DOM tree can be created out of an XMl-formatted String.
* </p>
* @author jhoeper
*/
public class XMLContainer
{
private ILog log = AEConstants.ILOGGER;
static String ELEMENT_PDR_OBJECT = "pdrEntity";
static String ELEMENT_PDR_GROUPED = "pdrAspectsGroup";
static String ATTRIBUTE_LABEL = "label";
static String ATTRIBUTE_ID = "id";
static String ATTRIBUTE_GROUP_CONDITION = "classification";
/**
* DOM document containing the XML data.
*/
private Document _document = null;
/**
* Builder used to parse XML Data given as Strings into DOM documents.
*/
private DocumentBuilder _builder = null;
/**
* Sets up Container with an empty DOM document.
*/
public XMLContainer()
{
setupBuilder();
_document = _builder.newDocument();
}
/**
* Creates a new Container containing given DOM Document.
* @param doc DOM
*/
public XMLContainer(final Document doc)
{
setupBuilder();
_document = doc;
}
/**
* Sets up the Container and creates DOM tree out of XML formatted String.
* @param xml XML String
*/
public XMLContainer(final String xml)
{
setupBuilder();
try
{
_document = _builder.parse(
new ByteArrayInputStream(xml.getBytes("UTF-8")));
}
catch (Exception e)
{
log.log(new Status(IStatus.ERROR, CommonActivator.PLUGIN_ID,
"Couldn't build DOM tree for XML Data"));
e.printStackTrace();
}
}
/**
* Creates an instance of XMLContainer containing an XML representation of the
* given PdrObjects.
* <p>The array where the PdrObjects come in may either be of the common type,
* meaning that it holds only objects of the type {@link Person} or it may as
* well be of mixed types, most likely {@link Person}s and {@link Aspect}s
* that don't need to be in any particular order.</p>
* <p>In the first case, the constructor doesn't mind collecting the
* corresponding <code>Aspect</code>s itself, with the support of the
* {@link PDRObjectsProvider} class. In the latter case, the XML being put
* together along with the container creation will actually only contain
* aspects provided by the given array explicitly. By doing so, it is even
* possible to create an XML representation merely containing {@link Person}
* elements.</p>
* <p>What about references??</p>
* @param exportObjects
*/
public XMLContainer(PdrObject[] exportObjects) {
this();
XMLProcessorInterface xmlproc = new XMLProcessor();
/* setRootNode("export", new String[]
{"date"}, new String[]
{new Date().toString()});*/
setRootNode("export", new String[]{"xmlns:xsi"},
new String[]{"http://www.w3.org/2001/XMLSchema-instance"});
appendText("\n\n");
Vector<Person> persons = new Vector<Person>();
Vector<Aspect> aspects = new Vector<Aspect>();
// FIXME: nicht auf Person beschränken, OrderingHeads zulassen?
for (PdrObject obj : exportObjects) {
if (obj instanceof Person) {
Person person = (Person) obj;
// try to export xml data of the Person
try {
importXML(xmlproc.writeToXML(person));
appendText("\n\n");
persons.add(person);
} catch (Exception e) {
appendComment(" ERROR EXPORTING PERSON DATA ");
e.printStackTrace();
}
} else if (obj instanceof Aspect) {
Aspect aspect = (Aspect) obj;
// stack aspect object for later use
aspects.add(aspect);
}
}
log.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
"number of persons to export: "+persons.size()));
log.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
"number of aspects to export: "+aspects.size()));
// set up PDR object provider
// FIXME: PdrObjectsProvider nicht anfassen! Ausgewählte PdrObjects/OrderingHeads(?) kommen
// vom jeweiligen Export-Plugin und werden brav abgearbeitet.
PDRObjectsProvider provider = AeExportCoreProvider.getInstance().getPdrObjectsProvider();
provider.setInput(persons);
// provider.setOrderer(new AspectsBySemanticOrderer());
// prepare processing of aspects
Vector<OrderingHead> aspectGroups;
if (aspects.size() < 1) {
// if no aspects in method argument, get them via provider
aspectGroups = provider.getArrangedAspects();
} else {
// if aspects have been passed within argument, use those
aspectGroups = new Vector<OrderingHead>();
OrderingHead aspectGroup = new OrderingHead();
for (Aspect a : aspects)
aspectGroup.addAspect(a);
aspectGroups.add(aspectGroup);
}
appendText("\n\n");
appendComment(" BEGIN ASPECT LIST ");
// try to export xml data of the person's aspects
for (OrderingHead aspectGroup : aspectGroups)
{
aspects = aspectGroup.getAspects();
for (Aspect a : aspects)
{
try
{
importXML(xmlproc.writeToXML(a));
appendText("\n\n");
}
catch (Exception e)
{
appendComment(" ERROR EXPORTING ASPECTS ");
e.printStackTrace();
}
}
}
// retrieve references
provider.setOrderer(null);
provider.setRefOrderer(null);
// catch nullpointer exception
// at org.bbaw.pdr.ae.view.control.PDRObjectsProvider.
// getArrangedReferences(PDRObjectsProvider.java:421)
try {
provider.setRefComparator(new ReferenceByAuthorTitleComparator(true));
} catch (Exception e) { }
// FIXME nullpointer
// at org.bbaw.pdr.ae.view.control.PDRObjectsProvider$2.run(PDRObjectsProvider.java:450)
// bzw at org.bbaw.pdr.ae.view.control.PDRObjectsProvider.getArrangedReferences(PDRObjectsProvider.java:421)
Vector<OrderingHead> referenceGroups = null;
try {
referenceGroups = provider.getArrangedReferences();
} catch (Exception e) { };
appendText("\n\n");
appendComment(" BEGIN REFERENCE LIST ");
appendText("\n");
// try to export xml data of the references concerning the person
if (referenceGroups != null)
try {
//FIXME nullpointer
// at org.bbaw.pdr.ae.view.control.PDRObjectsProvider$2.run(PDRObjectsProvider.java:450)
for (OrderingHead referenceGroup : referenceGroups) {
Vector<ReferenceMods> references = referenceGroup.getReferences();
for (ReferenceMods r : references) {
importXML(xmlproc.writeToXML(r));
appendText("\n\n");
}
}
}
catch (Exception e)
{
appendComment(" ERROR EXPORTING REFERENCES ");
e.printStackTrace();
}
//TODO: effect?
this._document.normalize();
}
/**
* Instantiates a new {@link XMLContainer} containing the XML representation
* of the provided list of {@link OrderingHead}.
* @param heads
*/
public XMLContainer(Collection<OrderingHead> heads) {
this();
XMLProcessorInterface xmlproc = new XMLProcessor();
setRootNode("export", new String[]{"xmlns:xsi"},
new String[]{"http://www.w3.org/2001/XMLSchema-instance"});
Vector<ReferenceMods> references = new Vector<ReferenceMods>();
// encode root level of passed structure, assuming to find Person objects
for (OrderingHead head : heads) {
PdrId id = new PdrId(head.getValue());
if (id.isValid()) {
Person p = Facade.getInstanz().getPerson(id);
//System.out.println(p.getDisplayNameWithID());
HashMap<String, String> attributes = new HashMap<String, String>();
attributes.put(ATTRIBUTE_ID, head.getValue());
attributes.put(ATTRIBUTE_LABEL, p.getDisplayName());
//
appendComment("PdrObject: "+p.getPdrId().toString());
Element entityNode = appendNode(ELEMENT_PDR_OBJECT, attributes, null);
// person/pdr object xml
try {
importXML(xmlproc.writeToXML(p), entityNode);
} catch (Exception e) {
log.log(new Status(IStatus.ERROR, CommonActivator.PLUGIN_ID,
"Failed to write xml representation for Person "+p.getDisplayName()));
e.printStackTrace();
}
// orderinghead/groups xml
appendComment("Start aspect group list", entityNode);
for (OrderingHead cat : head.getSubCategories()) {
// collect references
for (ReferenceMods ref : cat.getReferences())
if (!references.contains(ref))
references.add(ref);
// extract attributes
String classification = cat.getValue().substring(cat.getValue().indexOf("grouped"));
attributes = new HashMap<String, String>();
attributes.put(ATTRIBUTE_GROUP_CONDITION, classification);
attributes.put(ATTRIBUTE_ID, cat.getValue());
attributes.put(ATTRIBUTE_LABEL, cat.getLabel());
appendComment(cat.getLabel()+"; "+cat.getAspects().size()+" aspects", entityNode);
Element catNode = appendNode(entityNode, ELEMENT_PDR_GROUPED, attributes, null);
// aspects xml
for (Aspect aspect : cat.getAspects())
try {
appendComment(aspect.getDisplayNameWithID(), catNode);
importXML(xmlproc.writeToXML(aspect), catNode);
} catch (Exception e) {
log.log(new Status(IStatus.ERROR, CommonActivator.PLUGIN_ID,
"Failed to write xml representation for Aspect "+aspect.getDisplayName()));
e.printStackTrace();
}
if (cat.getAspects().size()>0)
appendComment("end of aspects list", catNode);
}
entityNode.normalize();
}
}
appendComment("Bibliography - "+references.size()+" sources");
this._document.getDocumentElement().normalize();
for (ReferenceMods ref : references) {
/*appendComment(ref.getNameMods().firstElement().getFullName() +
(ref.getNameMods().size()>1 ? " et al." : ""));
appendComment(ref.getDisplayName());*/
try {
importXML(xmlproc.writeToXML(ref));
} catch (Exception e) {
log.log(new Status(IStatus.ERROR, CommonActivator.PLUGIN_ID,
"Could not import XML for reference "+ref.getDisplayName()));
e.printStackTrace();
}
}
this._document.getDocumentElement().normalize();
this._document.normalize();
this._document.normalizeDocument();
}
/**
* appends comment of given content to the document's root node.
* @param text text
*/
public final void appendComment(final String text) {
appendComment(text, _document.getDocumentElement());
appendText("\n");
}
/**
* Appends a comment to a given node.
* @param text
* @param node
*/
public final void appendComment(final String text, Element node)
{
Comment comment = _document.createComment(text);
try {
node.appendChild(comment);
} catch (DOMException e) {
e.printStackTrace();
log.log(new Status(IStatus.ERROR, CommonActivator.PLUGIN_ID,
"Couldn't append Node. Is node set?"));
}
}
/**
* appends a new node of the name <code>name</code> and which the given
* attributes to the document's root node.<br/>
* appends a text node of the given text
* @param name name
* @param attributes attributes
* @param text text
*/
public final Element appendNode(Element parent, final String name, final HashMap<String, String> attributes, final String text)
{
Element element = _document.createElement(name);
if (attributes != null)
{
for (String k : attributes.keySet())
{
element.setAttribute(k, attributes.get(k));
}
}
if (text != null)
element.appendChild(_document.createTextNode(text));
appendText("\n");
try
{
parent.appendChild(element);
}
catch (DOMException e)
{
e.printStackTrace();
log.log(new Status(IStatus.ERROR, CommonActivator.PLUGIN_ID,
"Couldn't append Node. Does parent element exist?"));
}
appendText("\n");
return element;
}
public Element appendNode(final String name, final HashMap<String, String> attributes, final String text) {
return appendNode(_document.getDocumentElement(), name, attributes, text);
}
/**
* appends a {@link Text} node to this document's root node.
* @param text Text to be written to XML
*/
public final void appendText(final String text) {
appendText(text, _document.getDocumentElement());
}
/**
* Attaches a new {@link Text} node to the given XML {@link Element}.
* @param text
* @param node {@link Element} to which text is to be attached. If null,
* current {@link Document}'s root is used.
*/
public final void appendText(final String text, Element node)
{
Text textNode = _document.createTextNode(text);
if (node == null)
node = _document.getDocumentElement();
try
{
node.appendChild(textNode);
}
catch (DOMException e)
{
e.printStackTrace();
log.log(new Status(IStatus.ERROR, CommonActivator.PLUGIN_ID,
"Couldn't append Text Node. Does parent node exist?"));
}
}
/**
* Imports an XML-formatted String and merges the included XML structure
* with the one already loaded.
* @param xml XML-formatted String
* @throws SAXException exc
* @throws IOException exc
* @return the created {@link Node}
*/
public final Node importXML(final String xml, Element node) throws SAXException,
IOException {
if (xml == null) {
log.log(new Status(IStatus.ERROR, CommonActivator.PLUGIN_ID,
"String is NULL"));
return null;
}
if (_builder == null) {
log.log(new Status(IStatus.ERROR, CommonActivator.PLUGIN_ID,
"No builder set up"));
return null;
}
if (_document == null) {
log.log(new Status(IStatus.ERROR, CommonActivator.PLUGIN_ID,
"No document set up"));
return null;
}
// Create DOM of the XML String to attach
Document doc = _builder.parse(new ByteArrayInputStream(
xml.getBytes("UTF-8")));
//TODO any effect?
doc.normalize();
// Extracts all direct child nodes of the DOM to import
NodeList nodes = doc.getChildNodes();
String nodeName = null;
Node impNode = null;
node.appendChild(_document.createTextNode("\n"));
// append all child nodes of the DOM to import to the root node of the
// currently contained DOM
for (int i = 0; i < nodes.getLength(); i++) {
try {
impNode = _document.importNode(nodes.item(i), true);
String idTag = null;
// Try to get an aspect or reference ID
nodeName = impNode.getNodeName();
/*System.out.println(impNode.getBaseURI());
System.out.println(impNode.getNamespaceURI());
System.out.println();*/
if (impNode.hasAttributes()) {
NamedNodeMap attr = impNode.getAttributes();
idTag = nodeName.equals("aodl:aspect") ? "id" : "ID";
Node id = attr.getNamedItem(idTag);
// insert aspect ID as a comment into XML dump
if (id != null) {
node.appendChild(_document.createComment(
(nodeName.equals("aodl:aspect") ? " ASPECT "
: " REFERENCE ") + id + " "));
//node.appendChild(_document.createTextNode("\n"));
}
for (int a=0; a<attr.getLength(); a++) {
Node att = attr.item(a);
String ns = att.getPrefix();
/* System.out.println(ns);
System.out.println(att.getLocalName());
System.out.println(att.getNamespaceURI());
System.out.println();*/
if (ns != null)
if (ns.matches("xmlns")) {
node.setAttribute(att.getNodeName(),
att.getNodeValue());
/*System.out.println(att.getNodeName()+"="+
att.getNodeValue());*/
attr.removeNamedItem(att.getNodeName());
}
}
}
node.appendChild(impNode);
}
catch (DOMException e)
{
log.log(new Status(IStatus.ERROR, CommonActivator.PLUGIN_ID,
"Error at Node No " + i));
e.printStackTrace();
return null;
}
}
node.normalize();
// System.out.println("imported "+nodes.getLength()+" Nodes");
return impNode;
}
/**
* Inserts elements encoded by the XML structure in a given string as children
* of this document's root element.
* @param xml
* @return
* @throws SAXException
* @throws IOException
*/
public Node importXML(String xml) throws SAXException, IOException {
return importXML(xml, _document.getDocumentElement());
}
/**
* Creates a new root node and appends all contained nodes to it.
* @param tagname tag name of the root node to create
* @return true on success, false otherwise
*/
public final boolean setRootNode(final String tagname)
{
return setRootNode(tagname, null, null);
}
/**
* Encapsules the complete DOM tree under a new root node.
* <p>
* A node name is required and optional attributes can be set.
* </p>
* @param name the name of the node
* @param attributes optional attributes as String[]
* @param values values of the attributes as String[]
* @return true usually
*/
public final boolean setRootNode(final String name, final String[] attributes, final String[] values)
{
if (_builder == null) {
log.log(new Status(IStatus.ERROR, CommonActivator.PLUGIN_ID,
"No builder set up"));
return false;
}
// create empty document
Document doc = _builder.newDocument();
//doc.getDomConfig().setParameter("canonical-form", true);
// get root Nodes of current document
NodeList nodes = _document.getChildNodes();
// create new root element in empty document
Element root = doc.createElement(name);
// apply attributes
if (attributes != null && values != null)
{
for (int i = 0; i < Math.min(attributes.length, values.length); i++)
{
root.setAttribute(attributes[i], values[i]);
}
}
// import nodes from document and append them to new root node
Node impNode = null;
for (int i = 0; i < nodes.getLength(); i++)
{
impNode = doc.importNode(nodes.item(i), true);
root.appendChild(impNode);
}
doc.appendChild(root);
_document = doc;
log.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
"Set up new root node <" + _document.getDocumentElement().getNodeName() + ">"));
return true;
}
/**
* setup builder.
*/
private void setupBuilder()
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
try
{
_builder = factory.newDocumentBuilder();
}
catch (ParserConfigurationException e)
{
e.printStackTrace();
log.log(new Status(IStatus.ERROR, CommonActivator.PLUGIN_ID,
"failed on instantiating Document builder"));
}
}
@Override
public final String toString()
{
StringWriter out = new StringWriter();
Result result = new StreamResult(out);
Source src = new DOMSource(_document);
try
{
TransformerFactory transFactory = TransformerFactory.newInstance();
transFactory.setAttribute("indent-number", 8);
Transformer trans = transFactory.newTransformer();
trans = SAXTransformerFactory.newInstance().newTransformer();
trans.setOutputProperty(OutputKeys.INDENT, "yes");
trans.setOutputProperty(
"{http://xml.apache.org/xslt}indent-amount", "2");
trans.transform(src, result);
}
catch (Exception e)
{
log.log(new Status(IStatus.ERROR, CommonActivator.PLUGIN_ID,
"Could not convert DOM to String"));
e.printStackTrace();
return "";
}
return out.toString();
}
/**
* Returns a {@link Source} object for the XML which this container is
* currently holding.
* @return {@link StreamSource} object ready to be processed by a
* {@link Transformer}, or <code>null</code> if something goes wrong
*/
public Source getStream() {
StreamSource source = null;
try {
source = new StreamSource(
new ByteArrayInputStream(this.toString().getBytes("UTF-8")));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
log.log(new Status(IStatus.ERROR, CommonActivator.PLUGIN_ID,
"Could not retrieve Stream Source for XML."));
}
return source;
}
/**
* Saves XML contents to file.
* @param filename
*/
public void saveToFile(String filename) throws Exception {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(filename), "UTF-8"));
writer.write(this.toString());
writer.close();
}
}