/*
* Reference ETL Parser for Java
* Copyright (c) 2000-2009 Constantine A Plotnikov
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.sf.etl.parsers.xml;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import net.sf.etl.parsers.TermParser;
/**
* base class for different kinds of outputs supported by application
*
* @author const
*/
public abstract class XMLOutput {
/**
* <p>
* Tested StAX parsers are hopelessly broken in situation when root element
* does not declare all prefixes for child elements. The namespace is
* generated for the first element, but is ignored for the second element.
* </p>
*
* <p>
* So workaround is needed to manually maintain declarations. This class
* provides such workaround.
* </p>
*
* <p>
* Stack of namespace contexts. The stack might contain one of the
* following:
* </p>
* <ul>
* <li>null - this context did not defined any prefix</li>
* <li>a string - a single prefix has been defined in the context</li>
* <li>a list of strings - multiple prefixes has been defined in this
* context</li>
* </ul>
*/
private final ArrayList<ArrayList<String>> contextStack = new ArrayList<ArrayList<String>>();
/**
* A current mapping from prefix to namespace
*/
private final HashMap<String, String> prefixToNamespaceMap = new HashMap<String, String>();
/**
* A current mapping namespace to prefix
*/
private final HashMap<String, String> namespaceToPrefixMap = new HashMap<String, String>();
/**
* a parser
*/
TermParser parser;
/**
* a output writer
*/
XMLStreamWriter out;
/**
* Map from namespace to prefix name
*/
HashMap<String, String> prefixes = new HashMap<String, String>();
/**
* a constructor
*/
public XMLOutput() {
super();
}
/**
* start processing input and generating output
*
* @param parser
* parser to use
* @param output
* stream for output file
* @param encoding
* encoding to use
* @throws IOException
* if there is an IO problem
*/
public void process(TermParser parser, OutputStream output, String encoding)
throws IOException {
try {
this.parser = parser;
final XMLOutputFactory factory = XMLOutputFactory.newInstance();
Writer w = new OutputStreamWriter(output, "UTF-8");
this.out = factory.createXMLStreamWriter(w);
process();
} catch (final Exception ex) {
ex.printStackTrace();
}
}
/**
* start processing input and generating output
*
* @param parser
* parser to use
* @param writer
* writer for output file
* @throws IOException
* if there is an IO problem
*/
public void process(TermParser parser, Writer writer) throws IOException {
try {
this.parser = parser;
final XMLOutputFactory factory = XMLOutputFactory.newInstance();
this.out = factory.createXMLStreamWriter(writer);
process();
} catch (final Exception ex) {
ex.printStackTrace();
}
}
/**
* Generate prefix by namespace
*
* @param ns
* a namespace to use
* @return a prefix for that namespace
*/
protected String generatePrefix(String ns) {
String rc = prefixes.get(ns);
if (rc == null) {
rc = "n" + prefixes.size();
prefixes.put(ns, rc);
}
return rc;
}
/**
* process file
*
* @throws IOException
* if there is an IO problem
* @throws Exception
*/
protected abstract void process() throws Exception;
/**
* Start element in default namespace that might contain an auxiliary
* namespace declaration
*
* @param element
* an element to start
* @throws XMLStreamException
* in case of problem with xml output
*/
protected void startElement(String element) throws XMLStreamException {
contextStack.add(null);
out.writeStartElement(element);
}
/**
* This method suggests some prefix to outputter. Suggestions starting with
* letter "n" are ignored.
*
* @param prefix
* a prefix to suggest
* @param namespace
* a namespace for that prefix
*/
protected void suggestPrefix(String prefix, String namespace) {
if (prefix.startsWith("n")) {
// ignore suggestion
} else {
prefixes.put(namespace, prefix);
}
}
/**
* Start element with specified namespace and name
*
* @param namespace
* a namespace
* @param element
* a local name of element
* @throws XMLStreamException
* in case of problem with writer
*/
protected void startElement(String namespace, String element)
throws XMLStreamException {
String prefix = namespaceToPrefixMap.get(namespace);
boolean needNsDeclaration;
contextStack.add(null);
if (prefix == null) {
prefix = registerNewPrefix(namespace);
needNsDeclaration = true;
} else {
needNsDeclaration = false;
}
out.writeStartElement(prefix, element, namespace);
if (needNsDeclaration) {
out.writeNamespace(prefix, namespace);
}
}
/**
* Write attribute
*
* @param name
* a name of attribute
* @param value
* a value
* @throws XMLStreamException
*/
protected void attribute(String name, String value)
throws XMLStreamException {
out.writeAttribute(name, value);
}
/**
* Write attribute for namespace and value
*
* @param namespace
* a namespace of attribute
* @param name
* a name of attribute
* @param value
* a value
* @throws XMLStreamException
*/
protected void attribute(String namespace, String name, String value)
throws XMLStreamException {
final String prefix = getPrefixForAuxiliaryNamespace(namespace);
out.writeAttribute(prefix, namespace, name, value);
}
/**
* Get prefix for auxiliary namespace, one that is used in attribute value.
*
* @param namespace
* a namespace for which prefix is needed
* @return a prefix for auxiliary namespace
* @throws XMLStreamException
* in case of writer problem
*/
protected String getPrefixForAuxiliaryNamespace(String namespace)
throws XMLStreamException {
String prefix = namespaceToPrefixMap.get(namespace);
if (prefix == null) {
prefix = registerNewPrefix(namespace);
out.writeNamespace(prefix, namespace);
}
return prefix;
}
/**
* Registers new prefix for namespace
*
* @param namespace
* a namespace
* @return a new prefix for namespace
*/
private String registerNewPrefix(String namespace) {
final String prefix = generatePrefix(namespace);
final int stackTop = contextStack.size() - 1;
final ArrayList<String> previousTop = contextStack.get(stackTop);
if (previousTop == null) {
final ArrayList<String> l = new ArrayList<String>(1);
l.add(prefix);
contextStack.set(stackTop, l);
} else {
previousTop.add(prefix);
}
namespaceToPrefixMap.put(namespace, prefix);
prefixToNamespaceMap.put(prefix, namespace);
return prefix;
}
/**
* Write end element and clean a context stack
*
* @throws XMLStreamException
* in case of writer problem
*/
protected void endElement() throws XMLStreamException {
final int stackTop = contextStack.size() - 1;
final ArrayList<String> top = contextStack.get(stackTop);
if (top == null) {
// do nothing
} else {
for (final String p : top) {
final Object ns = prefixToNamespaceMap.remove(p);
namespaceToPrefixMap.remove(ns);
}
}
contextStack.remove(stackTop);
out.writeEndElement();
}
}