package org.basex.io.serial; import org.basex.data.Data; import static org.basex.data.DataText.*; import org.basex.data.ExprInfo; import org.basex.data.FTPos; import org.basex.data.FTPosData; import static org.basex.io.serial.SerializerProp.S_METHOD; import static org.basex.query.QueryText.XMLURI; import org.basex.query.item.Item; import org.basex.util.Atts; import static org.basex.util.Token.*; import org.basex.util.list.IntList; import org.basex.util.list.TokenList; import java.io.IOException; import java.io.OutputStream; /** * This is an interface for serializing trees. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public abstract class Serializer { /** Default serialization parameters. */ static final SerializerProp PROPS = new SerializerProp(); /** Stack of opened tags. */ final TokenList tags = new TokenList(); /** Current level. */ int level; /** Current tag. */ byte[] tag; /** Declare namespaces flag. */ boolean undecl; /** Currently available namespaces. */ private final Atts ns = new Atts(XML, XMLURI).add(EMPTY, EMPTY); /** Namespace stack. */ private final IntList nsl = new IntList(); /** Indicates if an element has not been completely opened yet. */ private boolean elem; /** * Returns an XML serializer. * @param os output stream reference * @return serializer * @throws IOException I/O exception */ public static XMLSerializer get(final OutputStream os) throws IOException { return new XMLSerializer(os, PROPS); } /** * Returns a specific serializer. * @param os output stream reference * @param props serialization properties (can be {@code null}) * @return serializer * @throws IOException I/O exception */ public static Serializer get(final OutputStream os, final SerializerProp props) throws IOException { if(props == null) return get(os); final String m = props.check(S_METHOD, M_XML, M_XHTML, M_HTML, M_TEXT, M_JSON, M_JSONML, M_RAW); if(M_XHTML.equals(m)) return new XHTMLSerializer(os, props); if(M_HTML.equals(m)) return new HTMLSerializer(os, props); if(M_TEXT.equals(m)) return new TextSerializer(os, props); if(M_JSON.equals(m)) return new JSONSerializer(os, props); if(M_JSONML.equals(m)) return new JsonMLSerializer(os, props); if(M_RAW.equals(m)) return new RawSerializer(os, props); return new XMLSerializer(os, props); } // FINAL PUBLIC METHODS ===================================================== /** * Opens an element. * @param name tag name * @param atts attributes * @throws IOException I/O exception */ public final void openElement(final byte[] name, final byte[]... atts) throws IOException { finishElement(); nsl.push(ns.size()); elem = true; tag = name; startOpen(name); for(int i = 0; i < atts.length; i += 2) attribute(atts[i], atts[i + 1]); } /** * Closes an element. * @throws IOException I/O exception */ public final void closeElement() throws IOException { ns.size(nsl.pop()); if(elem) { finishEmpty(); elem = false; } else { tag = tags.pop(); level--; finishClose(); } } /** * Opens and closes an empty element. * @param name tag name * @param atts attributes * @throws IOException I/O exception */ public final void emptyElement(final byte[] name, final byte[]... atts) throws IOException { openElement(name, atts); closeElement(); } /** * Serializes a text. * @param value text bytes * @throws IOException I/O exception */ public final void text(final byte[] value) throws IOException { finishElement(); finishText(value); } /** * Serializes a comment. * @param value value * @throws IOException I/O exception */ public final void comment(final byte[] value) throws IOException { finishElement(); finishComment(value); } /** * Serializes a processing instruction. * @param name name * @param value value * @throws IOException I/O exception */ public final void pi(final byte[] name, final byte[] value) throws IOException { finishElement(); finishPi(name, value); } /** * Serializes an item. This method is internally called; * call {@link Item#serialize} instead. * @param item text bytes * @throws IOException I/O exception */ public final void item(final Item item) throws IOException { finishElement(); finishItem(item); } /** * Serializes a node of the specified data reference. * * @param data data reference * @param pre pre value to start from * @throws IOException I/O exception */ public final void node(final Data data, final int pre) throws IOException { node(data, pre, null); } /** * Serializes a node of the specified data reference. * * @param data data reference * @param pre pre value to start from * @param ft full-text data * @throws IOException I/O exception */ public final void node(final Data data, final int pre, final FTPosData ft) throws IOException { boolean doc = false; final TokenList nsp = data.nspaces.size() != 0 ? new TokenList() : null; final IntList pars = new IntList(); int l = 0; int p = pre; // loop through all table entries final int s = pre + data.size(pre, data.kind(p)); while(p < s && !finished()) { final int k = data.kind(p); final int r = data.parent(p, k); // close opened elements... while(l > 0 && pars.get(l - 1) >= r) { closeElement(); --l; } if(k == Data.DOC) { if(doc) closeDoc(); openDoc(data.text(p++, true)); doc = true; } else if(k == Data.TEXT) { final FTPos ftd = ft != null ? ft.get(data, p) : null; if(ftd != null) text(data.text(p++, true), ftd); else text(data.text(p++, true)); } else if(k == Data.COMM) { comment(data.text(p++, true)); } else if(k == Data.ATTR) { attribute(data.name(p, k), data.text(p++, false)); } else if(k == Data.PI) { pi(data.name(p, k), data.atom(p++)); } else { // add element node final byte[] name = data.name(p, k); openElement(name); // add namespace definitions if(nsp != null) { // add namespaces from database nsp.reset(); int pp = p; // check namespace of current element byte[] key = prefix(name); byte[] val = data.nspaces.uri(data.uri(p, k)); if(val == null) val = EMPTY; // add new or updated namespace final byte[] old = ns(key); if(old == null || !eq(old, val)) namespace(key, val); do { final Atts atn = data.ns(pp); for(int n = 0; n < atn.size(); ++n) { key = atn.name(n); val = atn.string(n); if(!nsp.contains(key)) { nsp.add(key); namespace(key, val); } } // check ancestors only on top level if(level != 0 || l != 0) break; pp = data.parent(pp, data.kind(pp)); } while(pp >= 0 && data.kind(pp) == Data.ELEM); } // serialize attributes final int as = p + data.attSize(p, k); while(++p != as) { attribute(data.name(p, Data.ATTR), data.text(p, false)); } pars.set(l++, r); } } // process remaining elements... while(--l >= 0) closeElement(); if(doc) closeDoc(); } /** * Opens an element, adopting the expression name as element name. * @param expr expression info * @param atts attributes * @throws IOException I/O exception */ public final void openElement(final ExprInfo expr, final byte[]... atts) throws IOException { openElement(info(expr), atts); } /** * Opens and closes an empty element, adopting the expression name as * element name. * @param expr expression info * @param atts attributes * @throws IOException I/O exception */ public final void emptyElement(final ExprInfo expr, final byte[]... atts) throws IOException { emptyElement(info(expr), atts); } // OVERWRITABLE PUBLIC METHODS ============================================== /** * Starts a result. * @throws IOException I/O exception */ @SuppressWarnings("unused") public void openResult() throws IOException { } /** * Closes a result. * @throws IOException I/O exception */ @SuppressWarnings("unused") public void closeResult() throws IOException { } /** * Closes the serializer. * @throws IOException I/O exception */ @SuppressWarnings("unused") public void close() throws IOException { } /** * Serializes a namespace if it has not been serialized by an ancestor yet. * @param pref prefix * @param uri URI * @throws IOException I/O exception */ public void namespace(final byte[] pref, final byte[] uri) throws IOException { if(!undecl && pref.length != 0 && uri.length == 0) return; final byte[] u = ns(pref); if(u == null || !eq(u, uri)) { attribute(pref.length == 0 ? XMLNS : concat(XMLNSC, pref), uri); ns.add(pref, uri); } } /** * Tests if the serialization was interrupted. * @return result of check */ public boolean finished() { return false; } /** * Resets the serializer (indentation, etc). */ public void reset() { } /** * Serializes an attribute. * @param name name * @param value value * @throws IOException I/O exception */ public abstract void attribute(final byte[] name, final byte[] value) throws IOException; // PROTECTED METHODS ======================================================== /** * Starts an element. * @param n tag name * @throws IOException I/O exception */ protected abstract void startOpen(final byte[] n) throws IOException; /** * Finishes an opening element node. * @throws IOException I/O exception */ protected abstract void finishOpen() throws IOException; /** * Closes an empty element. * @throws IOException I/O exception */ protected abstract void finishEmpty() throws IOException; /** * Closes an element. * @throws IOException I/O exception */ protected abstract void finishClose() throws IOException; /** * Serializes a text. * @param v value * @throws IOException I/O exception */ protected abstract void finishText(final byte[] v) throws IOException; /** * Serializes a comment. * @param v value * @throws IOException I/O exception */ protected abstract void finishComment(final byte[] v) throws IOException; /** * Serializes a processing instruction. * @param n name * @param v value * @throws IOException I/O exception */ protected abstract void finishPi(final byte[] n, final byte[] v) throws IOException; /** * Serializes an item. * @param i item * @throws IOException I/O exception */ protected abstract void finishItem(final Item i) throws IOException; /** * Serializes a text. * @param v value * @param ftp full-text positions, used for visualization highlighting * @throws IOException I/O exception */ @SuppressWarnings("unused") void finishText(final byte[] v, final FTPos ftp) throws IOException { text(v); } /** * Opens a document. * @param n name * @throws IOException I/O exception */ @SuppressWarnings("unused") void openDoc(final byte[] n) throws IOException { } /** * Closes a document. * @throws IOException I/O exception */ @SuppressWarnings("unused") void closeDoc() throws IOException { } /** * Returns the name of the specified expression. * @param expr expression * @return name */ byte[] info(final ExprInfo expr) { return token(expr.info()); } // PRIVATE METHODS ========================================================== /** * Gets the URI currently bound by the given prefix. * @param pref namespace prefix * @return URI if found, {@code null} otherwise */ private byte[] ns(final byte[] pref) { for(int i = ns.size() - 1; i >= 0; i--) if(eq(ns.name(i), pref)) return ns.string(i); return null; } /** * Serializes a text. * @param v text bytes * @param ftp full-text positions, used for visualization highlighting * @throws IOException I/O exception */ private void text(final byte[] v, final FTPos ftp) throws IOException { finishElement(); finishText(v, ftp); } /** * Finishes an opening element node if necessary. * @throws IOException I/O exception */ private void finishElement() throws IOException { if(!elem) return; elem = false; finishOpen(); tags.push(tag); level++; } }