/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source 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, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.quercus.lib.xml; import com.caucho.quercus.annotation.*; import com.caucho.quercus.env.*; import com.caucho.util.L10N; import com.caucho.vfs.*; import java.io.*; import java.util.*; import java.util.logging.*; /** * XMLWriter */ public class XMLWriter { private static final Logger log = Logger.getLogger(XMLWriter.class.getName()); private static final L10N L = new L10N(XMLWriter.class); private static final XMLWriterStream NULL_STREAM = new XMLWriterStream(); private XMLWriterStream _s = NULL_STREAM; private WriterState _state = WriterState.TOP; private HashMap<StringValue,StringValue> _nsMap = new HashMap<StringValue,StringValue>(); private ArrayList<StringValue> _elementStack = new ArrayList<StringValue>(); private ArrayList<ArrayList<StringValue>> _nsStack = new ArrayList<ArrayList<StringValue>>(); /** * Default constructor */ public XMLWriter() { } /** * Flushes the output and returns the result. */ public Value flush() { return _s.flush(); } /** * Opens the writer for a memory target */ public boolean openMemory(Env env) { StringValue s = env.createUnicodeBuilder(); _s = new MemoryXMLWriterStream(s); _nsMap.clear(); _elementStack.clear(); _nsStack.clear(); _state = WriterState.TOP; return true; } /** * Opens the writer for a uri target */ public boolean openURI(Env env, Path path) { try { WriteStream out = path.openWrite(); _s = new PathXMLWriterStream(out); _nsMap.clear(); _elementStack.clear(); _nsStack.clear(); _state = WriterState.TOP; } catch (IOException e) { log.log(Level.WARNING, e.toString(), e); } return true; } /** * Returns the memory result */ public Value outputMemory() { return flush(); } /** * Ends an attribute */ public boolean endAttribute() { if (_state != WriterState.ATTRIBUTE) return false; _s.append("\""); _state = WriterState.ELEMENT_HEADER; return true; } /** * Starts a CData section */ public boolean endCData() { if (_state != WriterState.CDATA) return false; _state = WriterState.ELEMENT_BODY; _s.append("]]>"); return true; } /** * Starts a comment section */ public boolean endComment() { if (_state != WriterState.COMMENT) return false; _state = WriterState.ELEMENT_BODY; _s.append("-->"); return true; } /** * Ends a pi section */ public boolean endPI() { if (_state != WriterState.PI) return false; _state = WriterState.ELEMENT_BODY; _s.append("?>"); return true; } /** * Ends the document */ public boolean endDocument() { return true; } /** * Ends a DTD attribute list */ public boolean endDTDAttlist() { return true; } /** * Ends a DTD element list */ public boolean endDTDElement() { return true; } /** * Ends a DTD entity */ public boolean endDTDEntity() { return true; } /** * Ends a DTD */ public boolean endDTD() { return true; } /** * Ends an element */ public boolean endElement(Env env) { if (_state == WriterState.ATTRIBUTE) endAttribute(); if (_elementStack.size() == 0) return false; else if (_state == WriterState.ELEMENT_HEADER) { popElement(); _s.append("/>"); return true; } else if (_state == WriterState.ELEMENT_BODY) { StringValue name = popElement(); _s.append("</").append(env, name).append(">"); return true; } else return false; } /** * Ends an element */ public boolean endElementNS(Env env) { return endElement(env); } /** * Ends an element */ public boolean fullEndElement(Env env) { if (_state == WriterState.ATTRIBUTE) endAttribute(); if (_elementStack.size() == 0) return false; if (_state == WriterState.ELEMENT_HEADER) { _s.append(">"); } else if (_state != WriterState.ELEMENT_BODY) return false; StringValue name = popElement(); _s.append("</").append(env, name).append(">"); return true; } /** * enables indentation */ public boolean setIndent(boolean isIndent) { return false; } /** * sets the indentation string */ public boolean setIndentString(StringValue value) { return false; } /** * Starts an attribute */ public boolean startAttribute(Env env, StringValue name) { if (_state != WriterState.ELEMENT_HEADER) return false; _s.append(" ").append(env, name).append("=\""); _state = WriterState.ATTRIBUTE; return true; } /** * Starts an attribute with a namespace */ public boolean startAttributeNS(Env env, StringValue prefix, StringValue name, StringValue uri) { if (_state != WriterState.ELEMENT_HEADER) return false; pushNamespace(env, prefix, uri); _s.append(" ").append(env, prefix).append(":").append(env,name); _s.append("=\""); _state = WriterState.ATTRIBUTE; return true; } /** * Starts a CData section */ public boolean startCData() { startContent(); _state = WriterState.CDATA; _s.append("<![CDATA["); return true; } /** * Starts a comment section */ public boolean startComment() { startContent(); _state = WriterState.COMMENT; _s.append("<!--"); return true; } /** * Starts the document */ public boolean startDocument(Env env, @Optional StringValue version, @Optional StringValue encoding, @Optional StringValue standalone) { _s.append("<?xml"); if (version == null || version.length() == 0) _s.append(" version=\"1.0\""); else _s.append(" version=\"").append(env, version).append("\""); if (encoding != null && encoding.length() != 0) _s.append(" encoding=\"").append(env, encoding).append("\""); if (standalone != null && standalone.length() != 0) _s.append(" standalone=\"").append(env, standalone).append("\""); _s.append("?>\n"); return true; } /** * Starts a DTD attribute list */ public boolean startDTDAttlist(StringValue name) { return true; } /** * Starts a DTD element list */ public boolean startDTDElement(StringValue name) { return true; } /** * Starts a DTD entity */ public boolean startDTDEntity(StringValue name) { return true; } /** * Starts a DTD */ public boolean startDTD(StringValue name, @Optional StringValue publicId, @Optional StringValue systemId) { return true; } /** * Starts an element */ public boolean startElement(Env env, StringValue name) { if (_state == WriterState.ELEMENT_HEADER) { _s.append(">"); } _s.append("<").append(env, name); _elementStack.add(name); _nsStack.add(null); _state = WriterState.ELEMENT_HEADER; return true; } /** * Starts a namespaced element */ public boolean startElementNS(Env env, StringValue prefix, StringValue name, StringValue uri) { startContent(); _s.append("<").append(env, prefix).append(":").append(env, name); StringValue endName = prefix.createStringBuilder(); endName.append(prefix).append(":").append(name); _elementStack.add(endName); _nsStack.add(null); pushNamespace(env, prefix, uri); _state = WriterState.ELEMENT_HEADER; return true; } /** * Starts a processing instruction section */ public boolean startPI(Env env, StringValue target) { startContent(); _state = WriterState.PI; _s.append("<?").append(env, target).append(" "); return true; } /** * Writes text */ public boolean text(Env env, StringValue text) { if (_state == WriterState.ELEMENT_HEADER) startContent(); if (_state == WriterState.ELEMENT_BODY || _state == WriterState.TOP) { int len = text.length(); for (int i = 0; i < len; i++) { char ch = text.charAt(i); switch (ch) { case '<': _s.append("<"); break; case '>': _s.append(">"); break; case '&': _s.append("&"); break; case '"': _s.append("""); break; default: _s.append(ch); break; } } } else _s.append(env, text); return true; } /** * Writes a complete attribute */ public boolean writeAttribute(Env env, StringValue name, StringValue value) { startAttribute(env, name); text(env, value); endAttribute(); return true; } /** * Writes a complete attribute */ public boolean writeAttributeNS(Env env, StringValue prefix, StringValue name, StringValue uri, StringValue value) { startAttributeNS(env, prefix, name, uri); text(env, value); endAttribute(); return true; } /** * Writes a complete cdata */ public boolean writeCData(Env env, StringValue value) { startCData(); text(env, value); endCData(); return true; } /** * Writes a complete comment */ public boolean writeComment(Env env, StringValue value) { startComment(); text(env, value); endComment(); return true; } /** * Writes a DTD attribute list */ public boolean writeDTDAttlist(Env env, StringValue name, StringValue content) { startDTDAttlist(name); text(env, content); endDTDAttlist(); return true; } /** * Writes a DTD element */ public boolean writeDTDElement(Env env, StringValue name, StringValue content) { startDTDElement(name); text(env, content); endDTDElement(); return true; } /** * Writes a DTD entity */ public boolean writeDTDEntity(Env env, StringValue name, StringValue content) { startDTDEntity(name); text(env, content); endDTDEntity(); return true; } /** * Writes a DTD */ public boolean writeDTD(Env env, StringValue name, @Optional StringValue publicId, @Optional StringValue systemId, @Optional StringValue subset) { startDTD(name, publicId, systemId); text(env, subset); endDTDEntity(); return true; } /** * Writes a complete element */ public boolean writeElement(Env env, StringValue name, @Optional StringValue content) { startElement(env, name); if (content != null && content.length() > 0) text(env, content); endElement(env); return true; } /** * Writes a complete element */ public boolean writeElementNS(Env env, StringValue prefix, StringValue name, StringValue uri, @Optional StringValue content) { startElementNS(env, prefix, name, uri); if (content != null && content.length() > 0) text(env, content); endElement(env); return true; } /** * Writes a pi */ public boolean writePI(Env env, StringValue name, StringValue value) { startPI(env, name); text(env, value); endPI(); return true; } /** * Writes raw text */ public boolean writeRaw(Env env, StringValue value) { _s.append(env, value); return true; } private void startContent() { if (_state == WriterState.ATTRIBUTE) { _s.append("\">"); _state = WriterState.ELEMENT_BODY; } else if (_state == WriterState.ELEMENT_HEADER) { _s.append(">"); _state = WriterState.ELEMENT_BODY; } } private void pushNamespace(Env env, StringValue prefix, StringValue uri) { if (! uri.equals(_nsMap.get(prefix))) { _s.append(" "); if (prefix.length() == 0) _s.append("xmlns"); else _s.append("xmlns:").append(env, prefix); _s.append("=\"").append(env, uri).append("\""); ArrayList<StringValue> stack = _nsStack.get(_nsStack.size() - 1); if (stack == null) { stack = new ArrayList<StringValue>(); _nsStack.set(_nsStack.size() - 1, stack); } stack.add(prefix); _nsMap.put(prefix, uri); } } private StringValue popElement() { if (_elementStack.size() > 1) _state = WriterState.ELEMENT_BODY; else _state = WriterState.TOP; if (_elementStack.size() > 0) { StringValue name = _elementStack.remove(_elementStack.size() - 1); ArrayList<StringValue> prefixList = _nsStack.remove(_nsStack.size() - 1); if (prefixList != null) { for (StringValue prefix : prefixList) { _nsMap.remove(prefix); } } return name; } return null; } @Override public String toString() { return getClass().getSimpleName() + "[]"; } static enum WriterState { TOP, ELEMENT_HEADER, ATTRIBUTE, COMMENT, CDATA, ELEMENT_BODY, PI; } static class XMLWriterStream { XMLWriterStream append(char v) { return this; } XMLWriterStream append(Env env, StringValue v) { return this; } XMLWriterStream append(String v) { return this; } Value flush() { return NullValue.NULL; } } static class MemoryXMLWriterStream extends XMLWriterStream { private StringValue _v; MemoryXMLWriterStream(StringValue v) { _v = v; } @Override XMLWriterStream append(char v) { _v.append(v); return this; } @Override XMLWriterStream append(Env env, StringValue v) { _v.append(v); return this; } @Override XMLWriterStream append(String text) { _v.append(text); return this; } @Override Value flush() { return _v; } } static class PathXMLWriterStream extends XMLWriterStream { private WriteStream _out; PathXMLWriterStream(WriteStream out) { _out = out; } @Override XMLWriterStream append(char v) { try { _out.print(v); } catch (IOException e) { log.log(Level.WARNING, e.toString(), e); } return this; } @Override XMLWriterStream append(Env env, StringValue v) { v.print(env, _out); return this; } @Override XMLWriterStream append(String text) { try { _out.print(text); } catch (IOException e) { log.log(Level.WARNING, e.toString(), e); } return this; } @Override Value flush() { try { _out.close(); } catch (IOException e) { log.log(Level.WARNING, e.toString(), e); } return LongValue.create(1); } } }