| /* Copyright 2004 The Apache Software Foundation |
| * |
| * Licensed 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.xmlbeans.impl.store; |
| |
| import javax.xml.namespace.QName; |
| |
| import org.apache.xmlbeans.SystemProperties; |
| import org.apache.xmlbeans.XmlDocumentProperties; |
| import org.apache.xmlbeans.XmlOptions; |
| import org.apache.xmlbeans.XmlOptionCharEscapeMap; |
| import org.apache.xmlbeans.xml.stream.*; |
| |
| import org.apache.xmlbeans.impl.common.*; |
| |
| import java.io.Writer; |
| import java.io.Reader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.UnsupportedEncodingException; |
| |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.ext.LexicalHandler; |
| import org.xml.sax.SAXException; |
| |
| import org.xml.sax.helpers.AttributesImpl; |
| |
| import java.util.Iterator; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.LinkedHashMap; |
| import java.util.ConcurrentModificationException; |
| |
| abstract class Saver { |
| static final int ROOT = Cur.ROOT; |
| static final int ELEM = Cur.ELEM; |
| static final int ATTR = Cur.ATTR; |
| static final int COMMENT = Cur.COMMENT; |
| static final int PROCINST = Cur.PROCINST; |
| static final int TEXT = Cur.TEXT; |
| |
| protected abstract boolean emitElement(SaveCur c, ArrayList attrNames, ArrayList attrValues); |
| |
| protected abstract void emitFinish(SaveCur c); |
| |
| protected abstract void emitText(SaveCur c); |
| |
| protected abstract void emitComment(SaveCur c); |
| |
| protected abstract void emitProcinst(SaveCur c); |
| |
| protected abstract void emitDocType(String docTypeName, String publicId, String systemId); |
| |
| protected abstract void emitStartDoc(SaveCur c); |
| |
| protected abstract void emitEndDoc(SaveCur c); |
| |
| protected void syntheticNamespace(String prefix, String uri, boolean considerDefault) { |
| } |
| |
| Saver(Cur c, XmlOptions options) { |
| assert c._locale.entered(); |
| |
| options = XmlOptions.maskNull(options); |
| |
| _cur = createSaveCur(c, options); |
| |
| _locale = c._locale; |
| _version = _locale.version(); |
| |
| _namespaceStack = new ArrayList(); |
| _uriMap = new HashMap(); |
| _prefixMap = new HashMap(); |
| |
| _attrNames = new ArrayList(); |
| _attrValues = new ArrayList(); |
| |
| // Define implicit xml prefixed namespace |
| |
| addMapping("xml", Locale._xml1998Uri); |
| |
| if (options.hasOption(XmlOptions.SAVE_IMPLICIT_NAMESPACES)) { |
| Map m = (Map) options.get(XmlOptions.SAVE_IMPLICIT_NAMESPACES); |
| |
| for (Iterator i = m.keySet().iterator(); i.hasNext(); ) { |
| String prefix = (String) i.next(); |
| addMapping(prefix, (String) m.get(prefix)); |
| } |
| } |
| |
| // define character map for escaped replacements |
| if (options.hasOption(XmlOptions.SAVE_SUBSTITUTE_CHARACTERS)) { |
| _replaceChar = (XmlOptionCharEscapeMap) |
| options.get(XmlOptions.SAVE_SUBSTITUTE_CHARACTERS); |
| } |
| |
| // If the default prefix has not been mapped, do so now |
| |
| if (getNamespaceForPrefix("") == null) { |
| _initialDefaultUri = new String(""); |
| addMapping("", _initialDefaultUri); |
| } |
| |
| if (options.hasOption(XmlOptions.SAVE_AGGRESSIVE_NAMESPACES) && |
| !(this instanceof SynthNamespaceSaver)) { |
| SynthNamespaceSaver saver = new SynthNamespaceSaver(c, options); |
| |
| while (saver.process()) |
| ; |
| |
| if (!saver._synthNamespaces.isEmpty()) |
| _preComputedNamespaces = saver._synthNamespaces; |
| } |
| |
| _useDefaultNamespace = |
| options.hasOption(XmlOptions.SAVE_USE_DEFAULT_NAMESPACE); |
| |
| _saveNamespacesFirst = options.hasOption(XmlOptions.SAVE_NAMESPACES_FIRST); |
| |
| if (options.hasOption(XmlOptions.SAVE_SUGGESTED_PREFIXES)) |
| _suggestedPrefixes = (Map) options.get(XmlOptions.SAVE_SUGGESTED_PREFIXES); |
| |
| _ancestorNamespaces = _cur.getAncestorNamespaces(); |
| } |
| |
| private static SaveCur createSaveCur(Cur c, XmlOptions options) { |
| QName synthName = (QName) options.get(XmlOptions.SAVE_SYNTHETIC_DOCUMENT_ELEMENT); |
| |
| QName fragName = synthName; |
| |
| if (fragName == null) { |
| fragName = |
| options.hasOption(XmlOptions.SAVE_USE_OPEN_FRAGMENT) |
| ? Locale._openuriFragment |
| : Locale._xmlFragment; |
| } |
| |
| boolean saveInner = |
| options.hasOption(XmlOptions.SAVE_INNER) && |
| !options.hasOption(XmlOptions.SAVE_OUTER); |
| |
| Cur start = c.tempCur(); |
| Cur end = c.tempCur(); |
| |
| SaveCur cur = null; |
| |
| int k = c.kind(); |
| |
| switch (k) { |
| case ROOT: { |
| positionToInner(c, start, end); |
| |
| if (Locale.isFragment(start, end)) |
| cur = new FragSaveCur(start, end, fragName); |
| else if (synthName != null) |
| cur = new FragSaveCur(start, end, synthName); |
| else |
| cur = new DocSaveCur(c); |
| |
| break; |
| } |
| |
| case ELEM: { |
| if (saveInner) { |
| positionToInner(c, start, end); |
| |
| cur = |
| new FragSaveCur( |
| start, end, Locale.isFragment(start, end) ? fragName : synthName); |
| } else if (synthName != null) { |
| positionToInner(c, start, end); |
| |
| cur = new FragSaveCur(start, end, synthName); |
| } else { |
| start.moveToCur(c); |
| end.moveToCur(c); |
| end.skip(); |
| |
| cur = new FragSaveCur(start, end, null); |
| } |
| |
| break; |
| } |
| } |
| |
| if (cur == null) { |
| assert k < 0 || k == ATTR || k == COMMENT || k == PROCINST || k == TEXT; |
| |
| if (k < 0) { |
| // Save out "" |
| start.moveToCur(c); |
| end.moveToCur(c); |
| } else if (k == TEXT) { |
| start.moveToCur(c); |
| end.moveToCur(c); |
| end.next(); |
| } else if (saveInner) { |
| start.moveToCur(c); |
| start.next(); |
| |
| end.moveToCur(c); |
| end.toEnd(); |
| } else if (k == ATTR) { |
| start.moveToCur(c); |
| end.moveToCur(c); |
| } else { |
| assert k == COMMENT || k == PROCINST; |
| |
| start.moveToCur(c); |
| end.moveToCur(c); |
| end.skip(); |
| } |
| |
| cur = new FragSaveCur(start, end, fragName); |
| } |
| |
| String filterPI = (String) options.get(XmlOptions.SAVE_FILTER_PROCINST); |
| |
| if (filterPI != null) |
| cur = new FilterPiSaveCur(cur, filterPI); |
| |
| if (options.hasOption(XmlOptions.SAVE_PRETTY_PRINT)) |
| cur = new PrettySaveCur(cur, options); |
| |
| start.release(); |
| end.release(); |
| |
| return cur; |
| } |
| |
| private static void positionToInner(Cur c, Cur start, Cur end) { |
| assert c.isContainer(); |
| |
| start.moveToCur(c); |
| |
| if (!start.toFirstAttr()) |
| start.next(); |
| |
| end.moveToCur(c); |
| end.toEnd(); |
| } |
| |
| /** |
| * Test if a character is valid in xml character content. See |
| * http://www.w3.org/TR/REC-xml#NT-Char |
| */ |
| static boolean isBadChar(char ch) { |
| return !( |
| Character.isHighSurrogate(ch) || |
| Character.isLowSurrogate(ch) || |
| (ch >= 0x20 && ch <= 0xD7FF) || |
| (ch >= 0xE000 && ch <= 0xFFFD) || |
| (ch >= 0x10000 && ch <= 0x10FFFF) || |
| (ch == 0x9) || (ch == 0xA) || (ch == 0xD) |
| ); |
| } |
| |
| protected boolean saveNamespacesFirst() { |
| return _saveNamespacesFirst; |
| } |
| |
| protected void enterLocale() { |
| _locale.enter(); |
| } |
| |
| protected void exitLocale() { |
| _locale.exit(); |
| } |
| |
| protected final boolean process() { |
| assert _locale.entered(); |
| |
| if (_cur == null) |
| return false; |
| |
| if (_version != _locale.version()) |
| throw new ConcurrentModificationException("Document changed during save"); |
| |
| switch (_cur.kind()) { |
| case ROOT: { |
| processRoot(); |
| break; |
| } |
| case ELEM: { |
| processElement(); |
| break; |
| } |
| case -ELEM: { |
| processFinish(); |
| break; |
| } |
| case TEXT: { |
| emitText(_cur); |
| break; |
| } |
| |
| case COMMENT: { |
| emitComment(_cur); |
| _cur.toEnd(); |
| break; |
| } |
| case PROCINST: { |
| emitProcinst(_cur); |
| _cur.toEnd(); |
| break; |
| } |
| |
| case -ROOT: { |
| emitEndDoc(_cur); |
| _cur.release(); |
| _cur = null; |
| |
| return true; |
| } |
| |
| default: |
| throw new RuntimeException("Unexpected kind"); |
| } |
| |
| _cur.next(); |
| |
| return true; |
| } |
| |
| private final void processFinish() { |
| emitFinish(_cur); |
| popMappings(); |
| } |
| |
| private final void processRoot() { |
| assert _cur.isRoot(); |
| |
| XmlDocumentProperties props = _cur.getDocProps(); |
| String systemId = null; |
| String docTypeName = null; |
| if (props != null) { |
| systemId = props.getDoctypeSystemId(); |
| docTypeName = props.getDoctypeName(); |
| } |
| |
| if (systemId != null || docTypeName != null) { |
| if (docTypeName == null) { |
| _cur.push(); |
| while (!_cur.isElem() && _cur.next()) |
| ; |
| if (_cur.isElem()) |
| docTypeName = _cur.getName().getLocalPart(); |
| _cur.pop(); |
| } |
| |
| String publicId = props.getDoctypePublicId(); |
| |
| if (docTypeName != null) { |
| QName rootElemName = _cur.getName(); |
| |
| if (rootElemName == null) { |
| _cur.push(); |
| while (!_cur.isFinish()) { |
| if (_cur.isElem()) { |
| rootElemName = _cur.getName(); |
| break; |
| } |
| _cur.next(); |
| } |
| _cur.pop(); |
| } |
| |
| if (rootElemName != null && docTypeName.equals(rootElemName.getLocalPart())) { |
| emitDocType(docTypeName, publicId, systemId); |
| return; |
| } |
| } |
| } |
| |
| emitStartDoc(_cur); |
| } |
| |
| private final void processElement() { |
| assert _cur.isElem() && _cur.getName() != null; |
| |
| QName name = _cur.getName(); |
| |
| // Add a new entry to the frontier. If this element has a name |
| // which has no namespace, then we must make sure that pushing |
| // the mappings causes the default namespace to be empty |
| |
| boolean ensureDefaultEmpty = name.getNamespaceURI().length() == 0; |
| |
| pushMappings(_cur, ensureDefaultEmpty); |
| |
| // |
| // There are four things which use mappings: |
| // |
| // 1) The element name |
| // 2) The element value (qname based) |
| // 3) Attribute names |
| // 4) The attribute values (qname based) |
| // |
| |
| // 1) The element name (not for starts) |
| |
| ensureMapping(name.getNamespaceURI(), name.getPrefix(), !ensureDefaultEmpty, false); |
| |
| // |
| // |
| // |
| |
| _attrNames.clear(); |
| _attrValues.clear(); |
| |
| _cur.push(); |
| |
| attrs: |
| for (boolean A = _cur.toFirstAttr(); A; A = _cur.toNextAttr()) { |
| if (_cur.isNormalAttr()) { |
| QName attrName = _cur.getName(); |
| |
| _attrNames.add(attrName); |
| |
| for (int i = _attrNames.size() - 2; i >= 0; i--) { |
| if (_attrNames.get(i).equals(attrName)) { |
| _attrNames.remove(_attrNames.size() - 1); |
| continue attrs; |
| } |
| } |
| |
| _attrValues.add(_cur.getAttrValue()); |
| |
| ensureMapping(attrName.getNamespaceURI(), attrName.getPrefix(), false, true); |
| } |
| } |
| |
| _cur.pop(); |
| |
| // If I am doing aggressive namespaces and we're emitting a |
| // container which can contain content, add the namespaces |
| // we've computed. Basically, I'm making sure the pre-computed |
| // namespaces are mapped on the first container which has a name. |
| |
| if (_preComputedNamespaces != null) { |
| for (Iterator i = _preComputedNamespaces.keySet().iterator(); i.hasNext(); ) { |
| String uri = (String) i.next(); |
| String prefix = (String) _preComputedNamespaces.get(uri); |
| boolean considerDefault = prefix.length() == 0 && !ensureDefaultEmpty; |
| |
| ensureMapping(uri, prefix, considerDefault, false); |
| } |
| |
| // Set to null so we do this once at the top |
| _preComputedNamespaces = null; |
| } |
| |
| if (emitElement(_cur, _attrNames, _attrValues)) { |
| popMappings(); |
| _cur.toEnd(); |
| } |
| } |
| |
| // |
| // Layout of namespace stack: |
| // |
| // URI Undo |
| // URI Rename |
| // Prefix Undo |
| // Mapping |
| // |
| |
| boolean hasMappings() { |
| int i = _namespaceStack.size(); |
| |
| return i > 0 && _namespaceStack.get(i - 1) != null; |
| } |
| |
| void iterateMappings() { |
| _currentMapping = _namespaceStack.size(); |
| |
| while (_currentMapping > 0 && _namespaceStack.get(_currentMapping - 1) != null) |
| _currentMapping -= 8; |
| } |
| |
| boolean hasMapping() { |
| return _currentMapping < _namespaceStack.size(); |
| } |
| |
| void nextMapping() { |
| _currentMapping += 8; |
| } |
| |
| String mappingPrefix() { |
| assert hasMapping(); |
| return (String) _namespaceStack.get(_currentMapping + 6); |
| } |
| |
| String mappingUri() { |
| assert hasMapping(); |
| return (String) _namespaceStack.get(_currentMapping + 7); |
| } |
| |
| private final void pushMappings(SaveCur c, boolean ensureDefaultEmpty) { |
| assert c.isContainer(); |
| |
| _namespaceStack.add(null); |
| |
| c.push(); |
| |
| namespaces: |
| for (boolean A = c.toFirstAttr(); A; A = c.toNextAttr()) |
| if (c.isXmlns()) |
| addNewFrameMapping(c.getXmlnsPrefix(), c.getXmlnsUri(), ensureDefaultEmpty); |
| |
| c.pop(); |
| |
| if (_ancestorNamespaces != null) { |
| for (int i = 0; i < _ancestorNamespaces.size(); i += 2) { |
| String prefix = (String) _ancestorNamespaces.get(i); |
| String uri = (String) _ancestorNamespaces.get(i + 1); |
| |
| addNewFrameMapping(prefix, uri, ensureDefaultEmpty); |
| } |
| |
| _ancestorNamespaces = null; |
| } |
| |
| if (ensureDefaultEmpty) { |
| String defaultUri = (String) _prefixMap.get(""); |
| |
| // I map the default to "" at the very beginning |
| assert defaultUri != null; |
| |
| if (defaultUri.length() > 0) |
| addMapping("", ""); |
| } |
| } |
| |
| private final void addNewFrameMapping(String prefix, String uri, boolean ensureDefaultEmpty) { |
| // If the prefix maps to "", this don't include this mapping 'cause it's not well formed. |
| // Also, if we want to make sure that the default namespace is always "", then check that |
| // here as well. |
| |
| if ((prefix.length() == 0 || uri.length() > 0) && |
| (!ensureDefaultEmpty || prefix.length() > 0 || uri.length() == 0)) { |
| // Make sure the prefix is not already mapped in this frame |
| |
| for (iterateMappings(); hasMapping(); nextMapping()) |
| if (mappingPrefix().equals(prefix)) |
| return; |
| |
| // Also make sure that the prefix declaration is not redundant |
| // This has the side-effect of making it impossible to set a |
| // redundant prefix declaration, but seems that it's better |
| // to just never issue a duplicate prefix declaration. |
| if (uri.equals(getNamespaceForPrefix(prefix))) |
| return; |
| |
| addMapping(prefix, uri); |
| } |
| } |
| |
| private final void addMapping(String prefix, String uri) { |
| assert uri != null; |
| assert prefix != null; |
| |
| // If the prefix being mapped here is already mapped to a uri, |
| // that uri will either go out of scope or be mapped to another |
| // prefix. |
| |
| String renameUri = (String) _prefixMap.get(prefix); |
| String renamePrefix = null; |
| |
| if (renameUri != null) { |
| // See if this prefix is already mapped to this uri. If |
| // so, then add to the stack, but there is nothing to rename |
| |
| if (renameUri.equals(uri)) |
| renameUri = null; |
| else { |
| int i = _namespaceStack.size(); |
| |
| while (i > 0) { |
| if (_namespaceStack.get(i - 1) == null) { |
| i--; |
| continue; |
| } |
| |
| if (_namespaceStack.get(i - 7).equals(renameUri)) { |
| renamePrefix = (String) _namespaceStack.get(i - 8); |
| |
| if (renamePrefix == null || !renamePrefix.equals(prefix)) |
| break; |
| } |
| |
| i -= 8; |
| } |
| |
| assert i > 0; |
| } |
| } |
| |
| _namespaceStack.add(_uriMap.get(uri)); |
| _namespaceStack.add(uri); |
| |
| if (renameUri != null) { |
| _namespaceStack.add(_uriMap.get(renameUri)); |
| _namespaceStack.add(renameUri); |
| } else { |
| _namespaceStack.add(null); |
| _namespaceStack.add(null); |
| } |
| |
| _namespaceStack.add(prefix); |
| _namespaceStack.add(_prefixMap.get(prefix)); |
| |
| _namespaceStack.add(prefix); |
| _namespaceStack.add(uri); |
| |
| _uriMap.put(uri, prefix); |
| _prefixMap.put(prefix, uri); |
| |
| if (renameUri != null) |
| _uriMap.put(renameUri, renamePrefix); |
| } |
| |
| private final void popMappings() { |
| for (; ; ) { |
| int i = _namespaceStack.size(); |
| |
| if (i == 0) |
| break; |
| |
| if (_namespaceStack.get(i - 1) == null) { |
| _namespaceStack.remove(i - 1); |
| break; |
| } |
| |
| Object oldUri = _namespaceStack.get(i - 7); |
| Object oldPrefix = _namespaceStack.get(i - 8); |
| |
| if (oldPrefix == null) |
| _uriMap.remove(oldUri); |
| else |
| _uriMap.put(oldUri, oldPrefix); |
| |
| oldPrefix = _namespaceStack.get(i - 4); |
| oldUri = _namespaceStack.get(i - 3); |
| |
| if (oldUri == null) |
| _prefixMap.remove(oldPrefix); |
| else |
| _prefixMap.put(oldPrefix, oldUri); |
| |
| String uri = (String) _namespaceStack.get(i - 5); |
| |
| if (uri != null) |
| _uriMap.put(uri, _namespaceStack.get(i - 6)); |
| |
| // Hahahahahaha -- :-( |
| _namespaceStack.remove(i - 1); |
| _namespaceStack.remove(i - 2); |
| _namespaceStack.remove(i - 3); |
| _namespaceStack.remove(i - 4); |
| _namespaceStack.remove(i - 5); |
| _namespaceStack.remove(i - 6); |
| _namespaceStack.remove(i - 7); |
| _namespaceStack.remove(i - 8); |
| } |
| } |
| |
| private final void dumpMappings() { |
| for (int i = _namespaceStack.size(); i > 0; ) { |
| if (_namespaceStack.get(i - 1) == null) { |
| System.out.println("----------------"); |
| i--; |
| continue; |
| } |
| |
| System.out.print("Mapping: "); |
| System.out.print(_namespaceStack.get(i - 2)); |
| System.out.print(" -> "); |
| System.out.print(_namespaceStack.get(i - 1)); |
| System.out.println(); |
| |
| System.out.print("Prefix Undo: "); |
| System.out.print(_namespaceStack.get(i - 4)); |
| System.out.print(" -> "); |
| System.out.print(_namespaceStack.get(i - 3)); |
| System.out.println(); |
| |
| System.out.print("Uri Rename: "); |
| System.out.print(_namespaceStack.get(i - 5)); |
| System.out.print(" -> "); |
| System.out.print(_namespaceStack.get(i - 6)); |
| System.out.println(); |
| |
| System.out.print("UriUndo: "); |
| System.out.print(_namespaceStack.get(i - 7)); |
| System.out.print(" -> "); |
| System.out.print(_namespaceStack.get(i - 8)); |
| System.out.println(); |
| |
| System.out.println(); |
| |
| i -= 8; |
| } |
| } |
| |
| private final String ensureMapping( |
| String uri, String candidatePrefix, |
| boolean considerCreatingDefault, boolean mustHavePrefix) { |
| assert uri != null; |
| |
| // Can be called for no-namespaced things |
| |
| if (uri.length() == 0) |
| return null; |
| |
| String prefix = (String) _uriMap.get(uri); |
| |
| if (prefix != null && (prefix.length() > 0 || !mustHavePrefix)) |
| return prefix; |
| |
| // |
| // I try prefixes from a number of places, in order: |
| // |
| // 1) What was passed in |
| // 2) The optional suggestions (for uri's) |
| // 3) The default mapping is allowed |
| // 4) ns#++ |
| // |
| |
| if (candidatePrefix != null && candidatePrefix.length() == 0) |
| candidatePrefix = null; |
| |
| if (candidatePrefix == null || !tryPrefix(candidatePrefix)) { |
| if (_suggestedPrefixes != null && |
| _suggestedPrefixes.containsKey(uri) && |
| tryPrefix((String) _suggestedPrefixes.get(uri))) { |
| candidatePrefix = (String) _suggestedPrefixes.get(uri); |
| } else if (considerCreatingDefault && _useDefaultNamespace && tryPrefix("")) |
| candidatePrefix = ""; |
| else { |
| String basePrefix = QNameHelper.suggestPrefix(uri); |
| candidatePrefix = basePrefix; |
| |
| for (int i = 1; ; i++) { |
| if (tryPrefix(candidatePrefix)) |
| break; |
| |
| candidatePrefix = basePrefix + i; |
| } |
| } |
| } |
| |
| assert candidatePrefix != null; |
| |
| syntheticNamespace(candidatePrefix, uri, considerCreatingDefault); |
| |
| addMapping(candidatePrefix, uri); |
| |
| return candidatePrefix; |
| } |
| |
| protected final String getUriMapping(String uri) { |
| assert _uriMap.get(uri) != null; |
| return (String) _uriMap.get(uri); |
| } |
| |
| String getNonDefaultUriMapping(String uri) { |
| String prefix = (String) _uriMap.get(uri); |
| |
| if (prefix != null && prefix.length() > 0) |
| return prefix; |
| |
| for (Iterator keys = _prefixMap.keySet().iterator(); keys.hasNext(); ) { |
| prefix = (String) keys.next(); |
| |
| if (prefix.length() > 0 && _prefixMap.get(prefix).equals(uri)) |
| return prefix; |
| } |
| |
| assert false : "Could not find non-default mapping"; |
| |
| return null; |
| } |
| |
| private final boolean tryPrefix(String prefix) { |
| if (prefix == null || Locale.beginsWithXml(prefix)) |
| return false; |
| |
| String existingUri = (String) _prefixMap.get(prefix); |
| |
| // If the prefix is currently mapped, then try another prefix. A |
| // special case is that of trying to map the default prefix (""). |
| // Here, there always exists a default mapping. If this is the |
| // mapping we found, then remap it anyways. I use != to compare |
| // strings because I want to test for the specific initial default |
| // uri I added when I initialized the saver. |
| |
| if (existingUri != null && (prefix.length() > 0 || existingUri != _initialDefaultUri)) |
| return false; |
| |
| return true; |
| } |
| |
| public final String getNamespaceForPrefix(String prefix) { |
| assert !prefix.equals("xml") || _prefixMap.get(prefix).equals(Locale._xml1998Uri); |
| |
| return (String) _prefixMap.get(prefix); |
| } |
| |
| protected Map getPrefixMap() { |
| return _prefixMap; |
| } |
| |
| // |
| // |
| // |
| |
| static final class SynthNamespaceSaver extends Saver { |
| LinkedHashMap _synthNamespaces = new LinkedHashMap(); |
| |
| SynthNamespaceSaver(Cur c, XmlOptions options) { |
| super(c, options); |
| } |
| |
| protected void syntheticNamespace( |
| String prefix, String uri, boolean considerCreatingDefault) { |
| _synthNamespaces.put(uri, considerCreatingDefault ? "" : prefix); |
| } |
| |
| protected boolean emitElement( |
| SaveCur c, ArrayList attrNames, ArrayList attrValues) { |
| return false; |
| } |
| |
| protected void emitFinish(SaveCur c) { |
| } |
| |
| protected void emitText(SaveCur c) { |
| } |
| |
| protected void emitComment(SaveCur c) { |
| } |
| |
| protected void emitProcinst(SaveCur c) { |
| } |
| |
| protected void emitDocType(String docTypeName, String publicId, String systemId) { |
| } |
| |
| protected void emitStartDoc(SaveCur c) { |
| } |
| |
| protected void emitEndDoc(SaveCur c) { |
| } |
| } |
| |
| // |
| // |
| // |
| |
| static final class TextSaver extends Saver { |
| TextSaver(Cur c, XmlOptions options, String encoding) { |
| super(c, options); |
| |
| boolean noSaveDecl = |
| options != null && options.hasOption(XmlOptions.SAVE_NO_XML_DECL); |
| |
| if (options != null && options.hasOption(XmlOptions.SAVE_CDATA_LENGTH_THRESHOLD)) |
| _cdataLengthThreshold = ((Integer) options.get(XmlOptions.SAVE_CDATA_LENGTH_THRESHOLD)).intValue(); |
| |
| if (options != null && options.hasOption(XmlOptions.SAVE_CDATA_ENTITY_COUNT_THRESHOLD)) |
| _cdataEntityCountThreshold = ((Integer) options.get(XmlOptions.SAVE_CDATA_ENTITY_COUNT_THRESHOLD)).intValue(); |
| |
| if (options != null && options.hasOption(XmlOptions.LOAD_SAVE_CDATA_BOOKMARKS)) |
| _useCDataBookmarks = true; |
| |
| if (options != null && options.hasOption(XmlOptions.SAVE_PRETTY_PRINT)) |
| _isPrettyPrint = true; |
| |
| _in = _out = 0; |
| _free = 0; |
| |
| assert _buf == null || |
| (_out < _in && _free == _buf.length - (_in - _out)) || // data in the middle, free on the edges |
| (_out > _in && _free == _out - _in) || // data on the edges, free in the middle |
| (_out == _in && _free == _buf.length) || // no data, all buffer free |
| (_out == _in && _free == 0) // buffer full |
| : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free; |
| |
| if (encoding != null && !noSaveDecl) { |
| XmlDocumentProperties props = Locale.getDocProps(c, false); |
| |
| String version = props == null ? null : props.getVersion(); |
| |
| if (version == null) |
| version = "1.0"; |
| |
| Boolean standalone = null; |
| if (props != null && props.get(XmlDocumentProperties.STANDALONE) != null) |
| standalone = props.getStandalone(); |
| |
| emit("<?xml version=\""); |
| emit(version); |
| emit( "\" encoding=\"" + encoding + "\""); |
| if (standalone != null) |
| emit( " standalone=\"" + (standalone.booleanValue() ? "yes" : "no") + "\""); |
| emit( "?>" + _newLine ); |
| } |
| } |
| |
| protected boolean emitElement(SaveCur c, ArrayList attrNames, ArrayList attrValues) { |
| assert c.isElem(); |
| |
| emit('<'); |
| emitName(c.getName(), false); |
| |
| if (saveNamespacesFirst()) |
| emitNamespacesHelper(); |
| |
| for (int i = 0; i < attrNames.size(); i++) |
| emitAttrHelper((QName) attrNames.get(i), (String) attrValues.get(i)); |
| |
| if (!saveNamespacesFirst()) |
| emitNamespacesHelper(); |
| |
| if (!c.hasChildren() && !c.hasText()) { |
| emit('/', '>'); |
| return true; |
| } else { |
| emit('>'); |
| return false; |
| } |
| } |
| |
| protected void emitFinish(SaveCur c) { |
| emit('<', '/'); |
| emitName(c.getName(), false); |
| emit('>'); |
| } |
| |
| protected void emitXmlns(String prefix, String uri) { |
| assert prefix != null; |
| assert uri != null; |
| |
| emit("xmlns"); |
| |
| if (prefix.length() > 0) { |
| emit(':'); |
| emit(prefix); |
| } |
| |
| emit('=', '\"'); |
| |
| // TODO - must encode uri properly |
| |
| emit(uri); |
| entitizeAttrValue(false); |
| |
| emit('"'); |
| } |
| |
| private void emitNamespacesHelper() { |
| for (iterateMappings(); hasMapping(); nextMapping()) { |
| emit(' '); |
| emitXmlns(mappingPrefix(), mappingUri()); |
| } |
| } |
| |
| private void emitAttrHelper(QName attrName, String attrValue) { |
| emit(' '); |
| emitName(attrName, true); |
| emit('=', '\"'); |
| emit(attrValue); |
| entitizeAttrValue(true); |
| emit('"'); |
| } |
| |
| protected void emitText(SaveCur c) { |
| assert c.isText(); |
| |
| // c.isTextCData() is expensive do it only if useCDataBookmarks option is enabled |
| boolean forceCData = _useCDataBookmarks && c.isTextCData(); |
| |
| emit(c); |
| |
| entitizeContent(forceCData); |
| } |
| |
| protected void emitComment(SaveCur c) { |
| assert c.isComment(); |
| |
| emit("<!--"); |
| |
| c.push(); |
| c.next(); |
| |
| emit(c); |
| |
| c.pop(); |
| |
| entitizeComment(); |
| emit("-->"); |
| } |
| |
| protected void emitProcinst(SaveCur c) { |
| assert c.isProcinst(); |
| |
| emit("<?"); |
| |
| // TODO - encoding issues here? |
| emit(c.getName().getLocalPart()); |
| |
| c.push(); |
| |
| c.next(); |
| |
| if (c.isText()) { |
| emit(" "); |
| emit(c); |
| entitizeProcinst(); |
| } |
| |
| c.pop(); |
| |
| emit("?>"); |
| } |
| |
| private void emitLiteral(String literal) { |
| // TODO: systemId production http://www.w3.org/TR/REC-xml/#NT-SystemLiteral |
| // TODO: publicId production http://www.w3.org/TR/REC-xml/#NT-PubidLiteral |
| if (literal.indexOf("\"") < 0) { |
| emit('\"'); |
| emit(literal); |
| emit('\"'); |
| } else { |
| emit('\''); |
| emit(literal); |
| emit('\''); |
| } |
| } |
| |
| protected void emitDocType(String docTypeName, String publicId, String systemId) { |
| assert docTypeName != null; |
| |
| emit("<!DOCTYPE "); |
| emit(docTypeName); |
| |
| if (publicId == null && systemId != null) { |
| emit(" SYSTEM "); |
| emitLiteral(systemId); |
| } else if (publicId != null) { |
| emit(" PUBLIC "); |
| emitLiteral(publicId); |
| emit(" "); |
| emitLiteral(systemId); |
| } |
| |
| emit(">"); |
| emit(_newLine); |
| } |
| |
| protected void emitStartDoc(SaveCur c) { |
| } |
| |
| protected void emitEndDoc(SaveCur c) { |
| } |
| |
| // |
| // |
| // |
| |
| private void emitName(QName name, boolean needsPrefix) { |
| assert name != null; |
| |
| String uri = name.getNamespaceURI(); |
| |
| assert uri != null; |
| |
| if (uri.length() != 0) { |
| String prefix = name.getPrefix(); |
| String mappedUri = getNamespaceForPrefix(prefix); |
| |
| if (mappedUri == null || !mappedUri.equals(uri)) |
| prefix = getUriMapping(uri); |
| |
| // Attrs need a prefix. If I have not found one, then there must be a default |
| // prefix obscuring the prefix needed for this attr. Find it manually. |
| |
| // NOTE - Consider keeping the currently mapped default URI separate fromn the |
| // _urpMap and _prefixMap. This way, I would not have to look it up manually |
| // here |
| |
| if (needsPrefix && prefix.length() == 0) |
| prefix = getNonDefaultUriMapping(uri); |
| |
| if (prefix.length() > 0) { |
| emit(prefix); |
| emit(':'); |
| } |
| } |
| |
| assert name.getLocalPart().length() > 0; |
| |
| emit(name.getLocalPart()); |
| } |
| |
| private void emit(char ch) { |
| assert _buf == null || |
| (_out < _in && _free == _buf.length - (_in - _out)) || // data in the middle, free on the edges |
| (_out > _in && _free == _out - _in) || // data on the edges, free in the middle |
| (_out == _in && _free == _buf.length) || // no data, all buffer free |
| (_out == _in && _free == 0) // buffer full |
| : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free; |
| |
| preEmit(1); |
| |
| _buf[_in] = ch; |
| |
| _in = (_in + 1) % _buf.length; |
| |
| assert _buf == null || |
| (_out < _in && _free == _buf.length - (_in - _out)) || // data in the middle, free on the edges |
| (_out > _in && _free == _out - _in) || // data on the edges, free in the middle |
| (_out == _in && _free == _buf.length) || // no data, all buffer free |
| (_out == _in && _free == 0) // buffer full |
| : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free; |
| } |
| |
| private void emit(char ch1, char ch2) { |
| if (preEmit(2)) |
| return; |
| |
| _buf[_in] = ch1; |
| _in = (_in + 1) % _buf.length; |
| |
| _buf[_in] = ch2; |
| _in = (_in + 1) % _buf.length; |
| |
| assert _buf == null || |
| (_out < _in && _free == _buf.length - (_in - _out)) || // data in the middle, free on the edges |
| (_out > _in && _free == _out - _in) || // data on the edges, free in the middle |
| (_out == _in && _free == _buf.length) || // no data, all buffer free |
| (_out == _in && _free == 0) // buffer full |
| : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free; |
| } |
| |
| private void emit(String s) { |
| assert _buf == null || |
| (_out < _in && _free == _buf.length - (_in - _out)) || // data in the middle, free on the edges |
| (_out > _in && _free == _out - _in) || // data on the edges, free in the middle |
| (_out == _in && _free == _buf.length) || // no data, all buffer free |
| (_out == _in && _free == 0) // buffer full |
| : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free; |
| |
| int cch = s == null ? 0 : s.length(); |
| |
| if (preEmit(cch)) |
| return; |
| |
| int chunk; |
| |
| if (_in <= _out || cch < (chunk = _buf.length - _in)) { |
| s.getChars(0, cch, _buf, _in); |
| _in += cch; |
| } else { |
| s.getChars(0, chunk, _buf, _in); |
| s.getChars(chunk, cch, _buf, 0); |
| _in = (_in + cch) % _buf.length; |
| } |
| |
| assert _buf == null || |
| (_out < _in && _free == _buf.length - (_in - _out)) || // data in the middle, free on the edges |
| (_out > _in && _free == _out - _in) || // data on the edges, free in the middle |
| (_out == _in && _free == _buf.length) || // no data, all buffer free |
| (_out == _in && _free == 0) // buffer full |
| : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free; |
| } |
| |
| private void emit(SaveCur c) { |
| if (c.isText()) { |
| Object src = c.getChars(); |
| int cch = c._cchSrc; |
| |
| if (preEmit(cch)) |
| return; |
| |
| int chunk; |
| |
| if (_in <= _out || cch < (chunk = _buf.length - _in)) { |
| CharUtil.getChars(_buf, _in, src, c._offSrc, cch); |
| _in += cch; |
| } else { |
| CharUtil.getChars(_buf, _in, src, c._offSrc, chunk); |
| CharUtil.getChars(_buf, 0, src, c._offSrc + chunk, cch - chunk); |
| _in = (_in + cch) % _buf.length; |
| } |
| } else |
| preEmit(0); |
| } |
| |
| private boolean preEmit(int cch) { |
| assert cch >= 0; |
| assert _buf == null || |
| (_out < _in && _free == _buf.length - (_in - _out)) || // data in the middle, free on the edges |
| (_out > _in && _free == _out - _in) || // data on the edges, free in the middle |
| (_out == _in && _free == _buf.length) || // no data, all buffer free |
| (_out == _in && _free == 0) // buffer full |
| : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free; |
| |
| _lastEmitCch = cch; |
| |
| if (cch == 0) |
| return true; |
| |
| if (_free <= cch) |
| resize(cch, -1); |
| |
| assert cch <= _free; |
| |
| int used = getAvailable(); |
| |
| // if we are about to emit and there is noting in the buffer, reset |
| // the buffer to be at the beginning so as to not grow it anymore |
| // than needed. |
| |
| if (used == 0) { |
| assert _in == _out; |
| assert _free == _buf.length; |
| _in = _out = 0; |
| } |
| |
| _lastEmitIn = _in; |
| |
| _free -= cch; |
| |
| assert _free >= 0; |
| assert _buf == null || _free == (_in >= _out ? _buf.length - (_in - _out) : _out - _in) - cch : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free; |
| assert _buf == null || |
| (_out < _in && _free == _buf.length - (_in - _out) - cch) || // data in the middle, free on the edges |
| (_out > _in && _free == _out - _in - cch) || // data on the edges, free in the middle |
| (_out == _in && _free == _buf.length - cch) || // no data, all buffer free |
| (_out == _in && _free == 0) // buffer full |
| : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free; |
| |
| return false; |
| } |
| |
| private void entitizeContent(boolean forceCData) { |
| assert _free >= 0; |
| |
| if (_lastEmitCch == 0) |
| return; |
| |
| int i = _lastEmitIn; |
| final int n = _buf.length; |
| |
| boolean hasCharToBeReplaced = false; |
| |
| int count = 0; |
| char prevChar = 0; |
| char prevPrevChar = 0; |
| for (int cch = _lastEmitCch; cch > 0; cch--) { |
| char ch = _buf[i]; |
| |
| if (ch == '<' || ch == '&') |
| count++; |
| else if (prevPrevChar == ']' && prevChar == ']' && ch == '>') |
| hasCharToBeReplaced = true; |
| else if (isBadChar(ch) || isEscapedChar(ch) || (!_isPrettyPrint && ch == '\r')) |
| hasCharToBeReplaced = true; |
| |
| if (++i == n) |
| i = 0; |
| |
| prevPrevChar = prevChar; |
| prevChar = ch; |
| } |
| |
| if (!forceCData && count == 0 && !hasCharToBeReplaced && count < _cdataEntityCountThreshold) |
| return; |
| |
| i = _lastEmitIn; |
| |
| // |
| // Heuristic for knowing when to save out stuff as a CDATA. |
| // |
| if (forceCData || (_lastEmitCch > _cdataLengthThreshold && count > _cdataEntityCountThreshold)) { |
| boolean lastWasBracket = _buf[i] == ']'; |
| |
| i = replace(i, "<![CDATA[" + _buf[i]); |
| |
| boolean secondToLastWasBracket = lastWasBracket; |
| |
| lastWasBracket = _buf[i] == ']'; |
| |
| if (++i == _buf.length) |
| i = 0; |
| |
| for (int cch = _lastEmitCch - 2; cch > 0; cch--) { |
| char ch = _buf[i]; |
| |
| if (ch == '>' && secondToLastWasBracket && lastWasBracket) |
| i = replace(i, "]]>><![CDATA["); |
| else if (isBadChar(ch)) |
| i = replace(i, "?"); |
| else |
| i++; |
| |
| secondToLastWasBracket = lastWasBracket; |
| lastWasBracket = ch == ']'; |
| |
| if (i == _buf.length) |
| i = 0; |
| } |
| |
| emit("]]>"); |
| } else { |
| char ch = 0, ch_1 = 0, ch_2; |
| for (int cch = _lastEmitCch; cch > 0; cch--) { |
| ch_2 = ch_1; |
| ch_1 = ch; |
| ch = _buf[i]; |
| |
| if (ch == '<') |
| i = replace(i, "<"); |
| else if (ch == '&') |
| i = replace(i, "&"); |
| else if (ch == '>' && ch_1 == ']' && ch_2 == ']') |
| i = replace(i, ">"); |
| else if (isBadChar(ch)) |
| i = replace(i, "?"); |
| else if (!_isPrettyPrint && ch == '\r') |
| i = replace(i, " "); |
| else if (isEscapedChar(ch)) |
| i = replace(i, _replaceChar.getEscapedString(ch)); |
| else |
| i++; |
| |
| if (i == _buf.length) |
| i = 0; |
| } |
| } |
| } |
| |
| private void entitizeAttrValue(boolean replaceEscapedChar) { |
| if (_lastEmitCch == 0) |
| return; |
| |
| int i = _lastEmitIn; |
| |
| for (int cch = _lastEmitCch; cch > 0; cch--) { |
| char ch = _buf[i]; |
| |
| if (ch == '<') |
| i = replace(i, "<"); |
| else if (ch == '&') |
| i = replace(i, "&"); |
| else if (ch == '"') |
| i = replace(i, """); |
| else if (isEscapedChar(ch)) { |
| if (replaceEscapedChar) |
| i = replace(i, _replaceChar.getEscapedString(ch)); |
| } else |
| i++; |
| |
| if (i == _buf.length) |
| i = 0; |
| } |
| } |
| |
| private void entitizeComment() { |
| if (_lastEmitCch == 0) |
| return; |
| |
| int i = _lastEmitIn; |
| |
| boolean lastWasDash = false; |
| |
| for (int cch = _lastEmitCch; cch > 0; cch--) { |
| char ch = _buf[i]; |
| |
| if (isBadChar(ch)) |
| i = replace(i, "?"); |
| else if (ch == '-') { |
| if (lastWasDash) { |
| // Replace "--" with "- " to make well formed |
| i = replace(i, " "); |
| lastWasDash = false; |
| } else { |
| lastWasDash = true; |
| i++; |
| } |
| } else { |
| lastWasDash = false; |
| i++; |
| } |
| |
| if (i == _buf.length) |
| i = 0; |
| } |
| |
| // Because I have only replaced chars with single chars, |
| // _lastEmitIn will still be ok |
| |
| int offset = (_lastEmitIn + _lastEmitCch - 1) % _buf.length; |
| if (_buf[offset] == '-') |
| i = replace(offset, " "); |
| } |
| |
| private void entitizeProcinst() { |
| if (_lastEmitCch == 0) |
| return; |
| |
| int i = _lastEmitIn; |
| |
| boolean lastWasQuestion = false; |
| |
| for (int cch = _lastEmitCch; cch > 0; cch--) { |
| char ch = _buf[i]; |
| |
| if (isBadChar(ch)) |
| i = replace(i, "?"); |
| |
| if (ch == '>') { |
| // TODO - Had to convert to a space here ... imples not well formed XML |
| if (lastWasQuestion) |
| i = replace(i, " "); |
| else |
| i++; |
| |
| lastWasQuestion = false; |
| } else { |
| lastWasQuestion = ch == '?'; |
| i++; |
| } |
| |
| if (i == _buf.length) |
| i = 0; |
| } |
| } |
| |
| /** |
| * Test if a character is to be replaced with an escaped value |
| */ |
| private boolean isEscapedChar(char ch) { |
| return (null != _replaceChar && _replaceChar.containsChar(ch)); |
| } |
| |
| private int replace(int i, String replacement) { |
| assert replacement.length() > 0; |
| |
| int dCch = replacement.length() - 1; |
| |
| if (dCch == 0) { |
| _buf[i] = replacement.charAt(0); |
| return i + 1; |
| } |
| |
| assert _free >= 0; |
| |
| if (dCch > _free) |
| i = resize(dCch, i); |
| |
| assert _free >= 0; |
| |
| assert _free >= dCch; |
| assert getAvailable() > 0; |
| |
| int charsToCopy = dCch + 1; |
| |
| if (_out > _in && i >= _out) { |
| System.arraycopy(_buf, _out, _buf, _out - dCch, i - _out); |
| _out -= dCch; |
| i -= dCch; |
| } else { |
| assert i < _in; |
| int availableEndChunk = _buf.length - _in; |
| if (dCch <= availableEndChunk) { |
| System.arraycopy(_buf, i, _buf, i + dCch, _in - i); |
| _in = (_in + dCch) % _buf.length; |
| } else if (dCch <= availableEndChunk + _in - i - 1) { |
| int numToCopyToStart = dCch - availableEndChunk; |
| System.arraycopy(_buf, _in - numToCopyToStart, _buf, 0, numToCopyToStart); |
| System.arraycopy(_buf, i + 1, _buf, i + 1 + dCch, _in - i - 1 - numToCopyToStart); |
| |
| _in = numToCopyToStart; |
| } else { |
| int numToCopyToStart = _in - i - 1; |
| charsToCopy = availableEndChunk + _in - i; |
| |
| System.arraycopy(_buf, _in - numToCopyToStart, _buf, dCch - charsToCopy + 1, numToCopyToStart); |
| replacement.getChars(charsToCopy, dCch + 1, _buf, 0); |
| |
| _in = numToCopyToStart + dCch - charsToCopy + 1; |
| } |
| } |
| |
| replacement.getChars(0, charsToCopy, _buf, i); |
| |
| _free -= dCch; |
| |
| assert _free >= 0; |
| assert _buf == null || |
| (_out < _in && _free == _buf.length - (_in - _out)) || // data in the middle, free on the edges |
| (_out > _in && _free == _out - _in) || // data on the edges, free in the middle |
| (_out == _in && _free == _buf.length) || // no data, all buffer free |
| (_out == _in && _free == 0) // buffer full |
| : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free; |
| |
| return (i + dCch + 1) % _buf.length; |
| } |
| // |
| // |
| // |
| |
| private int ensure(int cch) { |
| // Even if we're asked to ensure nothing, still try to ensure |
| // atleast one character so we can determine if we're at the |
| // end of the stream. |
| |
| if (cch <= 0) |
| cch = 1; |
| |
| int available = getAvailable(); |
| |
| for (; available < cch; available = getAvailable()) |
| if (!process()) |
| break; |
| |
| assert available == getAvailable(); |
| |
| // if (available == 0) |
| // return 0; |
| |
| return available; |
| } |
| |
| int getAvailable() { |
| return _buf == null ? 0 : _buf.length - _free; |
| } |
| |
| private int resize(int cch, int i) { |
| assert _free >= 0; |
| assert cch > 0; |
| assert cch >= _free; |
| assert _buf == null || |
| (_out < _in && _free == _buf.length - (_in - _out)) || // data in the middle, free on the edges |
| (_out > _in && _free == _out - _in) || // data on the edges, free in the middle |
| (_out == _in && _free == _buf.length) || // no data, all buffer free |
| (_out == _in && _free == 0) // buffer full |
| : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free; |
| |
| int newLen = _buf == null ? _initialBufSize : _buf.length * 2; |
| int used = getAvailable(); |
| |
| while (newLen - used < cch) |
| newLen *= 2; |
| |
| char[] newBuf = new char[newLen]; |
| |
| if (used > 0) { |
| if (_in > _out) { |
| assert i == -1 || (i >= _out && i < _in); |
| System.arraycopy(_buf, _out, newBuf, 0, used); |
| i -= _out; |
| } else { |
| assert i == -1 || (i >= _out || i < _in); |
| System.arraycopy(_buf, _out, newBuf, 0, used - _in); |
| System.arraycopy(_buf, 0, newBuf, used - _in, _in); |
| i = i >= _out ? i - _out : i + _out; |
| } |
| |
| _out = 0; |
| _in = used; |
| _free += newBuf.length - _buf.length; |
| } else { |
| _free = newBuf.length; |
| assert _in == 0 && _out == 0; |
| assert i == -1; |
| } |
| |
| _buf = newBuf; |
| |
| assert _free >= 0; |
| assert _buf == null || |
| (_out < _in && _free == _buf.length - (_in - _out)) || // data in the middle, free on the edges |
| (_out > _in && _free == _out - _in) || // data on the edges, free in the middle |
| (_out == _in && _free == _buf.length) || // no data, all buffer free |
| (_out == _in && _free == 0) // buffer full |
| : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free; |
| |
| return i; |
| } |
| |
| public int read() { |
| if (ensure(1) == 0) |
| return -1; |
| |
| assert getAvailable() > 0; |
| |
| int ch = _buf[_out]; |
| |
| _out = (_out + 1) % _buf.length; |
| _free++; |
| |
| assert _buf == null || |
| (_out < _in && _free == _buf.length - (_in - _out)) || // data in the middle, free on the edges |
| (_out > _in && _free == _out - _in) || // data on the edges, free in the middle |
| (_out == _in && _free == _buf.length) || // no data, all buffer free |
| (_out == _in && _free == 0) // buffer full |
| : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free; |
| |
| return ch; |
| } |
| |
| public int read(char[] cbuf, int off, int len) { |
| // Check for end of stream even if there is no way to return |
| // characters because the Reader doc says to return -1 at end of |
| // stream. |
| |
| int n; |
| |
| if ((n = ensure(len)) == 0) |
| return -1; |
| |
| if (cbuf == null || len <= 0) |
| return 0; |
| |
| if (n < len) |
| len = n; |
| |
| if (_out < _in) { |
| System.arraycopy(_buf, _out, cbuf, off, len); |
| } else { |
| int chunk = _buf.length - _out; |
| |
| if (chunk >= len) |
| System.arraycopy(_buf, _out, cbuf, off, len); |
| else { |
| System.arraycopy(_buf, _out, cbuf, off, chunk); |
| System.arraycopy(_buf, 0, cbuf, off + chunk, len - chunk); |
| } |
| } |
| |
| _out = (_out + len) % _buf.length; |
| _free += len; |
| |
| assert _buf == null || |
| (_out < _in && _free == _buf.length - (_in - _out)) || // data in the middle, free on the edges |
| (_out > _in && _free == _out - _in) || // data on the edges, free in the middle |
| (_out == _in && _free == _buf.length) || // no data, all buffer free |
| (_out == _in && _free == 0) // buffer full |
| : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free; |
| |
| assert _free >= 0; |
| |
| return len; |
| } |
| |
| public int write(Writer writer, int cchMin) { |
| while (getAvailable() < cchMin) { |
| if (!process()) |
| break; |
| } |
| |
| int charsAvailable = getAvailable(); |
| |
| if (charsAvailable > 0) { |
| // I don't want to deal with the circular cases |
| |
| assert _out == 0; |
| assert _in >= _out : "_in:" + _in + " < _out:" + _out; |
| assert _free == _buf.length - _in; |
| |
| try { |
| //System.out.println("-------------\nWriting in corverter: TextSaver.write():1703 " + charsAvailable + " chars\n" + new String(_buf, 0, charsAvailable)); |
| writer.write(_buf, 0, charsAvailable); |
| writer.flush(); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| |
| _free += charsAvailable; |
| |
| assert _free >= 0; |
| |
| _in = 0; |
| } |
| assert _buf == null || |
| (_out < _in && _free == _buf.length - (_in - _out)) || // data in the middle, free on the edges |
| (_out > _in && _free == _out - _in) || // data on the edges, free in the middle |
| (_out == _in && _free == _buf.length) || // no data, all buffer free |
| (_out == _in && _free == 0) // buffer full |
| : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free; |
| |
| return charsAvailable; |
| } |
| |
| public String saveToString() { |
| // We're gonna build a string. Instead of using StringBuffer, may |
| // as well use my buffer here. Fill the whole sucker up and |
| // create a String! |
| |
| while (process()) |
| ; |
| |
| assert _out == 0; |
| |
| int available = getAvailable(); |
| |
| return available == 0 ? "" : new String(_buf, _out, available); |
| } |
| |
| // |
| // |
| // |
| |
| private static final int _initialBufSize = 4096; |
| private int _cdataLengthThreshold = 32; |
| private int _cdataEntityCountThreshold = 5; |
| private boolean _useCDataBookmarks = false; |
| private boolean _isPrettyPrint = false; |
| |
| private int _lastEmitIn; |
| private int _lastEmitCch; |
| |
| private int _free; |
| private int _in; |
| private int _out; |
| private char[] _buf; |
| /* |
| _buf is a circular buffer, useful data is before _in up to _out, there are 2 posible configurations: |
| 1: _in<=_out |data|_in empty _out|data| |
| 2: _out<_in |empty _out|data|_in empty| |
| _free is used to keep around the remaining empty space in the bufer so assert _buf==null || _free == (_in>=_out ? _buf.length - (_in - _out) : _out - _in ) ; |
| */ |
| } |
| |
| static final class OptimizedForSpeedSaver |
| extends Saver { |
| Writer _w; |
| private char[] _buf = new char[1024]; |
| |
| |
| static private class SaverIOException |
| extends RuntimeException { |
| SaverIOException(IOException e) { |
| super(e); |
| } |
| } |
| |
| |
| OptimizedForSpeedSaver(Cur cur, Writer writer) { |
| super(cur, XmlOptions.maskNull(null)); |
| _w = writer; |
| } |
| |
| static void save(Cur cur, Writer writer) |
| throws IOException { |
| try { |
| Saver saver = new OptimizedForSpeedSaver(cur, writer); |
| while (saver.process()) { |
| } |
| } catch (SaverIOException e) { |
| throw (IOException) e.getCause(); |
| } |
| } |
| |
| private void emit(String s) { |
| try { |
| _w.write(s); |
| } catch (IOException e) { |
| throw new SaverIOException(e); |
| } |
| } |
| |
| private void emit(char c) { |
| try { |
| _buf[0] = c; |
| _w.write(_buf, 0, 1); |
| } catch (IOException e) { |
| throw new SaverIOException(e); |
| } |
| } |
| |
| private void emit(char c1, char c2) { |
| try { |
| _buf[0] = c1; |
| _buf[1] = c2; |
| _w.write(_buf, 0, 2); |
| } catch (IOException e) { |
| throw new SaverIOException(e); |
| } |
| } |
| |
| private void emit(char[] buf, int start, int len) { |
| try { |
| _w.write(buf, start, len); |
| } catch (IOException e) { |
| throw new SaverIOException(e); |
| } |
| } |
| |
| protected boolean emitElement(SaveCur c, ArrayList attrNames, ArrayList attrValues) { |
| assert c.isElem(); |
| |
| emit('<'); |
| emitName(c.getName(), false); |
| |
| for (int i = 0; i < attrNames.size(); i++) |
| emitAttrHelper((QName) attrNames.get(i), (String) attrValues.get(i)); |
| |
| if (!saveNamespacesFirst()) |
| emitNamespacesHelper(); |
| |
| if (!c.hasChildren() && !c.hasText()) { |
| emit('/', '>'); |
| return true; |
| } else { |
| emit('>'); |
| return false; |
| } |
| } |
| |
| protected void emitFinish(SaveCur c) { |
| emit('<', '/'); |
| emitName(c.getName(), false); |
| emit('>'); |
| } |
| |
| protected void emitXmlns(String prefix, String uri) { |
| assert prefix != null; |
| assert uri != null; |
| |
| emit("xmlns"); |
| |
| if (prefix.length() > 0) { |
| emit(':'); |
| emit(prefix); |
| } |
| |
| emit('=', '\"'); |
| |
| // TODO - must encode uri properly |
| emitAttrValue(uri); |
| |
| emit('"'); |
| } |
| |
| private void emitNamespacesHelper() { |
| for (iterateMappings(); hasMapping(); nextMapping()) { |
| emit(' '); |
| emitXmlns(mappingPrefix(), mappingUri()); |
| } |
| } |
| |
| private void emitAttrHelper(QName attrName, String attrValue) { |
| emit(' '); |
| emitName(attrName, true); |
| emit('=', '\"'); |
| emitAttrValue(attrValue); |
| |
| emit('"'); |
| } |
| |
| protected void emitComment(SaveCur c) { |
| assert c.isComment(); |
| |
| emit("<!--"); |
| |
| c.push(); |
| c.next(); |
| |
| emitCommentText(c); |
| |
| c.pop(); |
| |
| emit("-->"); |
| } |
| |
| protected void emitProcinst(SaveCur c) { |
| assert c.isProcinst(); |
| |
| emit("<?"); |
| |
| // TODO - encoding issues here? |
| emit(c.getName().getLocalPart()); |
| |
| c.push(); |
| |
| c.next(); |
| |
| if (c.isText()) { |
| emit(' '); |
| emitPiText(c); |
| } |
| |
| c.pop(); |
| |
| emit("?>"); |
| } |
| |
| protected void emitDocType(String docTypeName, String publicId, String systemId) { |
| assert docTypeName != null; |
| |
| emit("<!DOCTYPE "); |
| emit(docTypeName); |
| |
| if (publicId == null && systemId != null) { |
| emit(" SYSTEM "); |
| emitLiteral(systemId); |
| } else if (publicId != null) { |
| emit(" PUBLIC "); |
| emitLiteral(publicId); |
| emit(' '); |
| emitLiteral(systemId); |
| } |
| |
| emit('>'); |
| emit(_newLine); |
| } |
| |
| protected void emitStartDoc(SaveCur c) { |
| } |
| |
| protected void emitEndDoc(SaveCur c) { |
| } |
| |
| // |
| // |
| // |
| |
| private void emitName(QName name, boolean needsPrefix) { |
| assert name != null; |
| |
| String uri = name.getNamespaceURI(); |
| |
| assert uri != null; |
| |
| if (uri.length() != 0) { |
| String prefix = name.getPrefix(); |
| String mappedUri = getNamespaceForPrefix(prefix); |
| |
| if (mappedUri == null || !mappedUri.equals(uri)) |
| prefix = getUriMapping(uri); |
| |
| // Attrs need a prefix. If I have not found one, then there must be a default |
| // prefix obscuring the prefix needed for this attr. Find it manually. |
| |
| // NOTE - Consider keeping the currently mapped default URI separate fromn the |
| // _urpMap and _prefixMap. This way, I would not have to look it up manually |
| // here |
| |
| if (needsPrefix && prefix.length() == 0) |
| prefix = getNonDefaultUriMapping(uri); |
| |
| if (prefix.length() > 0) { |
| emit(prefix); |
| emit(':'); |
| } |
| } |
| |
| assert name.getLocalPart().length() > 0; |
| |
| emit(name.getLocalPart()); |
| } |
| |
| private void emitAttrValue(CharSequence attVal) { |
| int len = attVal.length(); |
| |
| for (int i = 0; i < len; i++) { |
| char ch = attVal.charAt(i); |
| |
| if (ch == '<') |
| emit("<"); |
| else if (ch == '&') |
| emit("&"); |
| else if (ch == '"') |
| emit("""); |
| else |
| emit(ch); |
| } |
| } |
| |
| private void emitLiteral(String literal) { |
| // TODO: systemId production http://www.w3.org/TR/REC-xml/#NT-SystemLiteral |
| // TODO: publicId production http://www.w3.org/TR/REC-xml/#NT-PubidLiteral |
| if (literal.indexOf("\"") < 0) { |
| emit('\"'); |
| emit(literal); |
| emit('\"'); |
| } else { |
| emit('\''); |
| emit(literal); |
| emit('\''); |
| } |
| } |
| |
| protected void emitText(SaveCur c) { |
| assert c.isText(); |
| |
| Object src = c.getChars(); |
| int cch = c._cchSrc; |
| int off = c._offSrc; |
| int index = 0; |
| int indexLimit = 0; |
| while (index < cch) { |
| indexLimit = index + 512 > cch ? cch : index + 512; |
| CharUtil.getChars(_buf, 0, src, off + index, indexLimit - index); |
| entitizeAndWriteText(indexLimit - index); |
| index = indexLimit; |
| } |
| } |
| |
| protected void emitPiText(SaveCur c) { |
| assert c.isText(); |
| |
| Object src = c.getChars(); |
| int cch = c._cchSrc; |
| int off = c._offSrc; |
| int index = 0; |
| int indexLimit = 0; |
| while (index < cch) { |
| indexLimit = index + 512 > cch ? cch : 512; |
| CharUtil.getChars(_buf, 0, src, off + index, indexLimit); |
| entitizeAndWritePIText(indexLimit - index); |
| index = indexLimit; |
| } |
| } |
| |
| protected void emitCommentText(SaveCur c) { |
| assert c.isText(); |
| |
| Object src = c.getChars(); |
| int cch = c._cchSrc; |
| int off = c._offSrc; |
| int index = 0; |
| int indexLimit = 0; |
| while (index < cch) { |
| indexLimit = index + 512 > cch ? cch : 512; |
| CharUtil.getChars(_buf, 0, src, off + index, indexLimit); |
| entitizeAndWriteCommentText(indexLimit - index); |
| index = indexLimit; |
| } |
| } |
| |
| private void entitizeAndWriteText(int bufLimit) { |
| int index = 0; |
| for (int i = 0; i < bufLimit; i++) { |
| char c = _buf[i]; |
| switch (c) { |
| case '<': |
| emit(_buf, index, i - index); |
| emit("<"); |
| index = i + 1; |
| break; |
| case '&': |
| emit(_buf, index, i - index); |
| emit("&"); |
| index = i + 1; |
| break; |
| } |
| } |
| emit(_buf, index, bufLimit - index); |
| } |
| |
| private void entitizeAndWriteCommentText(int bufLimit) { |
| boolean lastWasDash = false; |
| |
| for (int i = 0; i < bufLimit; i++) { |
| char ch = _buf[i]; |
| |
| if (isBadChar(ch)) |
| _buf[i] = '?'; |
| else if (ch == '-') { |
| if (lastWasDash) { |
| // Replace "--" with "- " to make well formed |
| _buf[i] = ' '; |
| lastWasDash = false; |
| } else { |
| lastWasDash = true; |
| } |
| } else { |
| lastWasDash = false; |
| } |
| |
| if (i == _buf.length) |
| i = 0; |
| } |
| |
| if (_buf[bufLimit - 1] == '-') |
| _buf[bufLimit - 1] = ' '; |
| |
| emit(_buf, 0, bufLimit); |
| } |
| |
| private void entitizeAndWritePIText(int bufLimit) { |
| boolean lastWasQuestion = false; |
| |
| for (int i = 0; i < bufLimit; i++) { |
| char ch = _buf[i]; |
| |
| if (isBadChar(ch)) { |
| _buf[i] = '?'; |
| ch = '?'; |
| } |
| |
| if (ch == '>') { |
| // Had to convert to a space here ... imples not well formed XML |
| if (lastWasQuestion) |
| _buf[i] = ' '; |
| |
| lastWasQuestion = false; |
| } else { |
| lastWasQuestion = ch == '?'; |
| } |
| } |
| emit(_buf, 0, bufLimit); |
| } |
| } |
| |
| static final class TextReader extends Reader { |
| TextReader(Cur c, XmlOptions options) { |
| _textSaver = new TextSaver(c, options, null); |
| _locale = c._locale; |
| _closed = false; |
| } |
| |
| public void close() throws IOException { |
| _closed = true; |
| } |
| |
| public boolean ready() throws IOException { |
| return !_closed; |
| } |
| |
| public int read() throws IOException { |
| checkClosed(); |
| |
| if (_locale.noSync()) { |
| _locale.enter(); |
| try { |
| return _textSaver.read(); |
| } finally { |
| _locale.exit(); |
| } |
| } else synchronized (_locale) { |
| _locale.enter(); |
| try { |
| return _textSaver.read(); |
| } finally { |
| _locale.exit(); |
| } |
| } |
| } |
| |
| public int read(char[] cbuf) throws IOException { |
| checkClosed(); |
| |
| if (_locale.noSync()) { |
| _locale.enter(); |
| try { |
| return _textSaver.read(cbuf, 0, cbuf == null ? 0 : cbuf.length); |
| } finally { |
| _locale.exit(); |
| } |
| } else synchronized (_locale) { |
| _locale.enter(); |
| try { |
| return _textSaver.read(cbuf, 0, cbuf == null ? 0 : cbuf.length); |
| } finally { |
| _locale.exit(); |
| } |
| } |
| } |
| |
| public int read(char[] cbuf, int off, int len) throws IOException { |
| checkClosed(); |
| |
| if (_locale.noSync()) { |
| _locale.enter(); |
| try { |
| return _textSaver.read(cbuf, off, len); |
| } finally { |
| _locale.exit(); |
| } |
| } else synchronized (_locale) { |
| _locale.enter(); |
| try { |
| return _textSaver.read(cbuf, off, len); |
| } finally { |
| _locale.exit(); |
| } |
| } |
| } |
| |
| private void checkClosed() throws IOException { |
| if (_closed) |
| throw new IOException("Reader has been closed"); |
| } |
| |
| private Locale _locale; |
| private TextSaver _textSaver; |
| private boolean _closed; |
| } |
| |
| static final class InputStreamSaver extends InputStream { |
| InputStreamSaver(Cur c, XmlOptions options) { |
| _locale = c._locale; |
| |
| _closed = false; |
| |
| assert _locale.entered(); |
| |
| options = XmlOptions.maskNull(options); |
| |
| _outStreamImpl = new OutputStreamImpl(); |
| |
| String encoding = null; |
| |
| XmlDocumentProperties props = Locale.getDocProps(c, false); |
| |
| if (props != null && props.getEncoding() != null) |
| encoding = EncodingMap.getIANA2JavaMapping(props.getEncoding()); |
| |
| if (options.hasOption(XmlOptions.CHARACTER_ENCODING)) |
| encoding = (String) options.get(XmlOptions.CHARACTER_ENCODING); |
| |
| if (encoding != null) { |
| String ianaEncoding = EncodingMap.getJava2IANAMapping(encoding); |
| |
| if (ianaEncoding != null) |
| encoding = ianaEncoding; |
| } |
| |
| if (encoding == null) |
| encoding = EncodingMap.getJava2IANAMapping("UTF8"); |
| |
| String javaEncoding = EncodingMap.getIANA2JavaMapping(encoding); |
| |
| if (javaEncoding == null) |
| throw new IllegalStateException("Unknown encoding: " + encoding); |
| |
| try { |
| _converter = new OutputStreamWriter(_outStreamImpl, javaEncoding); |
| } catch (UnsupportedEncodingException e) { |
| throw new RuntimeException(e); |
| } |
| |
| _textSaver = new TextSaver(c, options, encoding); |
| } |
| |
| public void close() throws IOException { |
| _closed = true; |
| } |
| |
| private void checkClosed() throws IOException { |
| if (_closed) |
| throw new IOException("Stream closed"); |
| } |
| |
| // Having the gateway here is kinda slow for the single character case. It may be possible |
| // to only enter the gate when there are no chars in the buffer. |
| |
| public int read() throws IOException { |
| checkClosed(); |
| |
| if (_locale.noSync()) { |
| _locale.enter(); |
| try { |
| return _outStreamImpl.read(); |
| } finally { |
| _locale.exit(); |
| } |
| } else synchronized (_locale) { |
| _locale.enter(); |
| try { |
| return _outStreamImpl.read(); |
| } finally { |
| _locale.exit(); |
| } |
| } |
| } |
| |
| public int read(byte[] bbuf, int off, int len) throws IOException { |
| checkClosed(); |
| |
| if (bbuf == null) |
| throw new NullPointerException("buf to read into is null"); |
| |
| if (off < 0 || off > bbuf.length) |
| throw new IndexOutOfBoundsException("Offset is not within buf"); |
| |
| if (_locale.noSync()) { |
| _locale.enter(); |
| try { |
| return _outStreamImpl.read(bbuf, off, len); |
| } finally { |
| _locale.exit(); |
| } |
| } else synchronized (_locale) { |
| _locale.enter(); |
| try { |
| return _outStreamImpl.read(bbuf, off, len); |
| } finally { |
| _locale.exit(); |
| } |
| } |
| } |
| |
| private int ensure(int cbyte) { |
| // Even if we're asked to ensure nothing, still try to ensure |
| // atleast one byte so we can determine if we're at the |
| // end of the stream. |
| |
| if (cbyte <= 0) |
| cbyte = 1; |
| |
| int bytesAvailable = _outStreamImpl.getAvailable(); |
| |
| for (; bytesAvailable < cbyte; |
| bytesAvailable = _outStreamImpl.getAvailable()) { |
| if (_textSaver.write(_converter, 2048) < 2048) |
| break; |
| } |
| |
| bytesAvailable = _outStreamImpl.getAvailable(); |
| |
| // if (bytesAvailable == 0) |
| // return 0; |
| |
| return bytesAvailable; |
| } |
| |
| public int available() |
| throws IOException { |
| if (_locale.noSync()) { |
| _locale.enter(); |
| try { |
| return ensure(1024); |
| } finally { |
| _locale.exit(); |
| } |
| } else |
| synchronized (_locale) { |
| _locale.enter(); |
| try { |
| return ensure(1024); |
| } finally { |
| _locale.exit(); |
| } |
| } |
| } |
| |
| private final class OutputStreamImpl extends OutputStream { |
| int read() { |
| if (InputStreamSaver.this.ensure(1) == 0) |
| return -1; |
| |
| assert getAvailable() > 0; |
| |
| int bite = _buf[_out]; |
| |
| _out = (_out + 1) % _buf.length; |
| _free++; |
| |
| return bite; |
| } |
| |
| int read(byte[] bbuf, int off, int len) { |
| // Check for end of stream even if there is no way to return |
| // characters because the Reader doc says to return -1 at end of |
| // stream. |
| |
| int n; |
| |
| if ((n = ensure(len)) == 0) |
| return -1; |
| |
| if (bbuf == null || len <= 0) |
| return 0; |
| |
| if (n < len) |
| len = n; |
| |
| if (_out < _in) { |
| System.arraycopy(_buf, _out, bbuf, off, len); |
| } else { |
| int chunk = _buf.length - _out; |
| |
| if (chunk >= len) |
| System.arraycopy(_buf, _out, bbuf, off, len); |
| else { |
| System.arraycopy(_buf, _out, bbuf, off, chunk); |
| |
| System.arraycopy( |
| _buf, 0, bbuf, off + chunk, len - chunk); |
| } |
| } |
| _out = (_out + len) % _buf.length; |
| _free += len; |
| |
| //System.out.println("------------------------\nRead out of queue: Saver:2440 InputStreamSaver.read() bbuf " + len + " bytes :\n" + new String(bbuf, off, len)); |
| return len; |
| } |
| |
| int getAvailable() { |
| return _buf == null ? 0 : _buf.length - _free; |
| } |
| |
| public void write(int bite) { |
| if (_free == 0) |
| resize(1); |
| |
| assert _free > 0; |
| |
| _buf[_in] = (byte) bite; |
| |
| _in = (_in + 1) % _buf.length; |
| _free--; |
| } |
| |
| public void write(byte[] buf, int off, int cbyte) { |
| assert cbyte >= 0; |
| //System.out.println("---------\nAfter converter, write in queue: OutputStreamImpl.write():Saver:2469 " + cbyte + " bytes \n" + new String(buf, off, cbyte)); |
| if (cbyte == 0) |
| return; |
| |
| if (_free < cbyte) |
| resize(cbyte); |
| |
| if (_in == _out) { |
| assert getAvailable() == 0; |
| assert _free == _buf.length - getAvailable(); |
| _in = _out = 0; |
| } |
| |
| int chunk = _buf.length - _in; |
| |
| if (_in <= _out || cbyte < chunk) { |
| System.arraycopy(buf, off, _buf, _in, cbyte); |
| _in += cbyte; |
| } else { |
| System.arraycopy(buf, off, _buf, _in, chunk); |
| |
| System.arraycopy( |
| buf, off + chunk, _buf, 0, cbyte - chunk); |
| |
| _in = (_in + cbyte) % _buf.length; |
| } |
| |
| _free -= cbyte; |
| } |
| |
| void resize(int cbyte) { |
| assert cbyte > _free : cbyte + " !> " + _free; |
| |
| int newLen = _buf == null ? _initialBufSize : _buf.length * 2; |
| int used = getAvailable(); |
| |
| while (newLen - used < cbyte) |
| newLen *= 2; |
| |
| byte[] newBuf = new byte[newLen]; |
| |
| if (used > 0) { |
| if (_in > _out) |
| System.arraycopy(_buf, _out, newBuf, 0, used); |
| else { |
| System.arraycopy( |
| _buf, _out, newBuf, 0, used - _in); |
| |
| System.arraycopy( |
| _buf, 0, newBuf, used - _in, _in); |
| } |
| |
| _out = 0; |
| _in = used; |
| _free += newBuf.length - _buf.length; |
| } else { |
| _free = newBuf.length; |
| assert _in == _out; |
| } |
| |
| _buf = newBuf; |
| } |
| |
| private static final int _initialBufSize = 4096; |
| |
| private int _free; |
| private int _in; |
| private int _out; |
| private byte[] _buf; |
| } |
| |
| private Locale _locale; |
| private boolean _closed; |
| private OutputStreamImpl _outStreamImpl; |
| private TextSaver _textSaver; |
| private OutputStreamWriter _converter; |
| } |
| |
| static final class XmlInputStreamSaver extends Saver { |
| XmlInputStreamSaver(Cur c, XmlOptions options) { |
| super(c, options); |
| } |
| |
| protected boolean emitElement(SaveCur c, ArrayList attrNames, ArrayList attrValues) { |
| assert c.isElem(); |
| |
| for (iterateMappings(); hasMapping(); nextMapping()) { |
| enqueue(new StartPrefixMappingImpl(mappingPrefix(), mappingUri())); |
| } |
| |
| StartElementImpl.AttributeImpl lastAttr = null; |
| StartElementImpl.AttributeImpl attributes = null; |
| StartElementImpl.AttributeImpl namespaces = null; |
| |
| for (int i = 0; i < attrNames.size(); i++) { |
| XMLName attXMLName = computeName((QName) attrNames.get(i), this, true); |
| StartElementImpl.AttributeImpl attr = |
| new StartElementImpl.NormalAttributeImpl(attXMLName, (String) attrValues.get(i)); |
| |
| if (attributes == null) |
| attributes = attr; |
| else |
| lastAttr._next = attr; |
| |
| lastAttr = attr; |
| } |
| |
| lastAttr = null; |
| |
| for (iterateMappings(); hasMapping(); nextMapping()) { |
| String prefix = mappingPrefix(); |
| String uri = mappingUri(); |
| |
| StartElementImpl.AttributeImpl attr = |
| new StartElementImpl.XmlnsAttributeImpl(prefix, uri); |
| |
| if (namespaces == null) |
| namespaces = attr; |
| else |
| lastAttr._next = attr; |
| |
| lastAttr = attr; |
| } |
| |
| |
| QName name = c.getName(); |
| enqueue(new StartElementImpl(computeName(name, this, false), attributes, namespaces, getPrefixMap())); |
| |
| return false; // still need to be called on end element |
| } |
| |
| protected void emitFinish(SaveCur c) { |
| if (c.isRoot()) |
| enqueue(new EndDocumentImpl()); |
| else { |
| XMLName xmlName = computeName(c.getName(), this, false); |
| enqueue(new EndElementImpl(xmlName)); |
| } |
| |
| emitEndPrefixMappings(); |
| } |
| |
| protected void emitText(SaveCur c) { |
| assert c.isText(); |
| Object src = c.getChars(); |
| int cch = c._cchSrc; |
| int off = c._offSrc; |
| |
| enqueue(new CharacterDataImpl(src, cch, off)); |
| } |
| |
| protected void emitComment(SaveCur c) { |
| enqueue(new CommentImpl(c.getChars(), c._cchSrc, c._offSrc)); |
| } |
| |
| protected void emitProcinst(SaveCur c) { |
| String target = null; |
| QName name = c.getName(); |
| |
| if (name != null) |
| target = name.getLocalPart(); |
| |
| enqueue(new ProcessingInstructionImpl(target, c.getChars(), c._cchSrc, c._offSrc)); |
| } |
| |
| protected void emitDocType(String doctypeName, String publicID, String systemID) { |
| enqueue(new StartDocumentImpl(systemID, null, true, null)); //todo |
| } |
| |
| protected void emitStartDoc(SaveCur c) { |
| emitDocType(null, null, null); |
| } |
| |
| protected void emitEndDoc(SaveCur c) { |
| enqueue(new EndDocumentImpl()); |
| } |
| |
| XMLEvent dequeue() { |
| if (_out == null) { |
| enterLocale(); |
| try { |
| if (!process()) |
| return null; |
| } finally { |
| exitLocale(); |
| } |
| } |
| |
| if (_out == null) |
| return null; |
| |
| XmlEventImpl e = _out; |
| |
| if ((_out = _out._next) == null) |
| _in = null; |
| |
| return e; |
| } |
| |
| private void enqueue(XmlEventImpl e) { |
| assert e._next == null; |
| |
| if (_in == null) { |
| assert _out == null; |
| _out = _in = e; |
| } else { |
| _in._next = e; |
| _in = e; |
| } |
| } |
| |
| // |
| // |
| // |
| |
| protected void emitEndPrefixMappings() { |
| for (iterateMappings(); hasMapping(); nextMapping()) { |
| String prevPrefixUri = null; // todo mappingPrevPrefixUri(); |
| String prefix = mappingPrefix(); |
| String uri = mappingUri(); |
| |
| if (prevPrefixUri == null) |
| enqueue(new EndPrefixMappingImpl(prefix)); |
| else { |
| enqueue(new ChangePrefixMappingImpl(prefix, uri, prevPrefixUri)); |
| } |
| } |
| } |
| |
| // |
| // |
| // |
| |
| private static XMLName computeName(QName name, Saver saver, boolean needsPrefix) { |
| String uri = name.getNamespaceURI(); |
| String local = name.getLocalPart(); |
| |
| assert uri != null; |
| assert local.length() > 0; |
| |
| String prefix = null; |
| |
| if (uri != null && uri.length() != 0) { |
| prefix = name.getPrefix(); |
| String mappedUri = saver.getNamespaceForPrefix(prefix); |
| |
| if (mappedUri == null || !mappedUri.equals(uri)) |
| prefix = saver.getUriMapping(uri); |
| |
| // Attrs need a prefix. If I have not found one, then there must be a default |
| // prefix obscuring the prefix needed for this attr. Find it manually. |
| |
| // NOTE - Consider keeping the currently mapped default URI separate fromn the |
| // _urpMap and _prefixMap. This way, I would not have to look it up manually |
| // here |
| |
| if (needsPrefix && prefix.length() == 0) |
| prefix = saver.getNonDefaultUriMapping(uri); |
| |
| } |
| |
| return new XmlNameImpl(uri, local, prefix); |
| } |
| |
| private static abstract class XmlEventImpl extends XmlEventBase { |
| XmlEventImpl(int type) { |
| super(type); |
| } |
| |
| public XMLName getName() { |
| return null; |
| } |
| |
| public XMLName getSchemaType() { |
| throw new RuntimeException("NYI"); |
| } |
| |
| public boolean hasName() { |
| return false; |
| } |
| |
| public final Location getLocation() { |
| // (orig v1 comment)TODO - perhaps I can save a location goober sometimes? |
| return null; |
| } |
| |
| XmlEventImpl _next; |
| } |
| |
| private static class StartDocumentImpl |
| extends XmlEventImpl implements StartDocument { |
| StartDocumentImpl(String systemID, String encoding, boolean isStandAlone, String version) { |
| super(XMLEvent.START_DOCUMENT); |
| _systemID = systemID; |
| _encoding = encoding; |
| _standAlone = isStandAlone; |
| _version = version; |
| } |
| |
| public String getSystemId() { |
| return _systemID; |
| } |
| |
| public String getCharacterEncodingScheme() { |
| return _encoding; |
| } |
| |
| public boolean isStandalone() { |
| return _standAlone; |
| } |
| |
| public String getVersion() { |
| return _version; |
| } |
| |
| String _systemID; |
| String _encoding; |
| boolean _standAlone; |
| String _version; |
| } |
| |
| private static class StartElementImpl |
| extends XmlEventImpl implements StartElement { |
| StartElementImpl(XMLName name, AttributeImpl attributes, AttributeImpl namespaces, Map prefixMap) { |
| super(XMLEvent.START_ELEMENT); |
| |
| _name = name; |
| _attributes = attributes; |
| _namespaces = namespaces; |
| _prefixMap = prefixMap; |
| } |
| |
| public boolean hasName() { |
| return true; |
| } |
| |
| public XMLName getName() { |
| return _name; |
| } |
| |
| public AttributeIterator getAttributes() { |
| return new AttributeIteratorImpl(_attributes, null); |
| } |
| |
| public AttributeIterator getNamespaces() { |
| return new AttributeIteratorImpl(null, _namespaces); |
| } |
| |
| public AttributeIterator getAttributesAndNamespaces() { |
| return new AttributeIteratorImpl(_attributes, _namespaces); |
| } |
| |
| public Attribute getAttributeByName(XMLName xmlName) { |
| for (AttributeImpl a = _attributes; a != null; a = a._next) { |
| if (xmlName.equals(a.getName())) |
| return a; |
| } |
| |
| return null; |
| } |
| |
| public String getNamespaceUri(String prefix) { |
| return (String) _prefixMap.get(prefix == null ? "" : prefix); |
| } |
| |
| public Map getNamespaceMap() { |
| return _prefixMap; |
| } |
| |
| private static class AttributeIteratorImpl |
| implements AttributeIterator { |
| AttributeIteratorImpl(AttributeImpl attributes, AttributeImpl namespaces) { |
| _attributes = attributes; |
| _namespaces = namespaces; |
| } |
| |
| public Object monitor() { |
| return this; |
| } |
| |
| public Attribute next() { |
| synchronized (monitor()) { |
| checkVersion(); |
| |
| AttributeImpl attr = null; |
| |
| if (_attributes != null) { |
| attr = _attributes; |
| _attributes = attr._next; |
| } else if (_namespaces != null) { |
| attr = _namespaces; |
| _namespaces = attr._next; |
| } |
| |
| return attr; |
| } |
| } |
| |
| public boolean hasNext() { |
| synchronized (monitor()) { |
| checkVersion(); |
| |
| return _attributes != null || _namespaces != null; |
| } |
| } |
| |
| public Attribute peek() { |
| synchronized (monitor()) { |
| checkVersion(); |
| |
| if (_attributes != null) |
| return _attributes; |
| else if (_namespaces != null) |
| return _namespaces; |
| |
| return null; |
| } |
| } |
| |
| public void skip() { |
| synchronized (monitor()) { |
| checkVersion(); |
| |
| if (_attributes != null) |
| _attributes = _attributes._next; |
| else if (_namespaces != null) |
| _namespaces = _namespaces._next; |
| } |
| } |
| |
| private final void checkVersion() { |
| // if (_version != _root.getVersion()) |
| // throw new IllegalStateException( "Document changed" ); |
| } |
| |
| // private long _version; |
| private AttributeImpl _attributes; |
| private AttributeImpl _namespaces; |
| } |
| |
| private static abstract class AttributeImpl implements Attribute { |
| /** |
| * Don't forget to set _name |
| */ |
| AttributeImpl() { |
| } |
| |
| public XMLName getName() { |
| return _name; |
| } |
| |
| public String getType() { |
| // (from v1 impl) TODO - Make sure throwing away this DTD info is ok. |
| // (from v1 impl) Is there schema info which can return more useful info? |
| return "CDATA"; |
| } |
| |
| public XMLName getSchemaType() { |
| // (from v1 impl) TODO - Can I return something reasonable here? |
| return null; |
| } |
| |
| AttributeImpl _next; |
| |
| protected XMLName _name; |
| } |
| |
| private static class XmlnsAttributeImpl extends AttributeImpl { |
| XmlnsAttributeImpl(String prefix, String uri) { |
| super(); |
| _uri = uri; |
| |
| String local; |
| |
| if (prefix.length() == 0) { |
| prefix = null; |
| local = "xmlns"; |
| } else { |
| local = prefix; |
| prefix = "xmlns"; |
| } |
| |
| _name = new XmlNameImpl(null, local, prefix); |
| } |
| |
| public String getValue() { |
| return _uri; |
| } |
| |
| private String _uri; |
| } |
| |
| private static class NormalAttributeImpl extends AttributeImpl { |
| NormalAttributeImpl(XMLName name, String value) { |
| _name = name; |
| _value = value; |
| } |
| |
| public String getValue() { |
| return _value; |
| } |
| |
| private String _value; // If invalid in the store |
| } |
| |
| private XMLName _name; |
| private Map _prefixMap; |
| |
| private AttributeImpl _attributes; |
| private AttributeImpl _namespaces; |
| } |
| |
| private static class StartPrefixMappingImpl |
| extends XmlEventImpl implements StartPrefixMapping { |
| StartPrefixMappingImpl(String prefix, String uri) { |
| super(XMLEvent.START_PREFIX_MAPPING); |
| |
| _prefix = prefix; |
| _uri = uri; |
| } |
| |
| public String getNamespaceUri() { |
| return _uri; |
| } |
| |
| public String getPrefix() { |
| return _prefix; |
| } |
| |
| private String _prefix, _uri; |
| } |
| |
| private static class ChangePrefixMappingImpl |
| extends XmlEventImpl implements ChangePrefixMapping { |
| ChangePrefixMappingImpl(String prefix, String oldUri, String newUri) { |
| super(XMLEvent.CHANGE_PREFIX_MAPPING); |
| |
| _oldUri = oldUri; |
| _newUri = newUri; |
| _prefix = prefix; |
| } |
| |
| public String getOldNamespaceUri() { |
| return _oldUri; |
| } |
| |
| public String getNewNamespaceUri() { |
| return _newUri; |
| } |
| |
| public String getPrefix() { |
| return _prefix; |
| } |
| |
| private String _oldUri, _newUri, _prefix; |
| } |
| |
| private static class EndPrefixMappingImpl |
| extends XmlEventImpl implements EndPrefixMapping { |
| EndPrefixMappingImpl(String prefix) { |
| super(XMLEvent.END_PREFIX_MAPPING); |
| _prefix = prefix; |
| } |
| |
| public String getPrefix() { |
| return _prefix; |
| } |
| |
| private String _prefix; |
| } |
| |
| private static class EndElementImpl |
| extends XmlEventImpl implements EndElement { |
| EndElementImpl(XMLName name) { |
| super(XMLEvent.END_ELEMENT); |
| |
| _name = name; |
| } |
| |
| public boolean hasName() { |
| return true; |
| } |
| |
| public XMLName getName() { |
| return _name; |
| } |
| |
| private XMLName _name; |
| } |
| |
| private static class EndDocumentImpl |
| extends XmlEventImpl implements EndDocument { |
| EndDocumentImpl() { |
| super(XMLEvent.END_DOCUMENT); |
| } |
| } |
| |
| private static class TripletEventImpl |
| extends XmlEventImpl implements CharacterData { |
| TripletEventImpl(int eventType, Object obj, int cch, int off) { |
| super(eventType); |
| _obj = obj; |
| _cch = cch; |
| _off = off; |
| } |
| |
| public String getContent() { |
| return CharUtil.getString(_obj, _off, _cch); |
| } |
| |
| public boolean hasContent() { |
| return _cch > 0; |
| } |
| |
| private Object _obj; |
| private int _cch; |
| private int _off; |
| } |
| |
| private static class CharacterDataImpl |
| extends TripletEventImpl implements CharacterData { |
| CharacterDataImpl(Object obj, int cch, int off) { |
| super(XMLEvent.CHARACTER_DATA, obj, cch, off); |
| } |
| } |
| |
| private static class CommentImpl |
| extends TripletEventImpl implements Comment { |
| CommentImpl(Object obj, int cch, int off) { |
| super(XMLEvent.COMMENT, obj, cch, off); |
| } |
| } |
| |
| private static class ProcessingInstructionImpl |
| extends TripletEventImpl implements ProcessingInstruction { |
| ProcessingInstructionImpl(String target, Object obj, int cch, int off) { |
| super(XMLEvent.PROCESSING_INSTRUCTION, obj, cch, off); |
| _target = target; |
| } |
| |
| public String getTarget() { |
| return _target; |
| } |
| |
| public String getData() { |
| return getContent(); |
| } |
| |
| private String _target; |
| } |
| |
| private XmlEventImpl _in, _out; |
| } |
| |
| static final class XmlInputStreamImpl extends GenericXmlInputStream { |
| XmlInputStreamImpl(Cur cur, XmlOptions options) { |
| _xmlInputStreamSaver = |
| new XmlInputStreamSaver(cur, options); |
| |
| // Make the saver grind away just a bit to throw any exceptions |
| // related to the inability to create a stream on this xml |
| |
| _xmlInputStreamSaver.process(); |
| } |
| |
| protected XMLEvent nextEvent() throws XMLStreamException { |
| return _xmlInputStreamSaver.dequeue(); |
| } |
| |
| private XmlInputStreamSaver _xmlInputStreamSaver; |
| } |
| |
| static final class SaxSaver extends Saver { |
| SaxSaver(Cur c, XmlOptions options, ContentHandler ch, LexicalHandler lh) |
| throws SAXException { |
| super(c, options); |
| |
| _contentHandler = ch; |
| _lexicalHandler = lh; |
| |
| _attributes = new AttributesImpl(); |
| _nsAsAttrs = !options.hasOption(XmlOptions.SAVE_SAX_NO_NSDECLS_IN_ATTRIBUTES); |
| |
| _contentHandler.startDocument(); |
| |
| try { |
| while (process()) |
| ; |
| } catch (SaverSAXException e) { |
| throw e._saxException; |
| } |
| |
| _contentHandler.endDocument(); |
| } |
| |
| private class SaverSAXException extends RuntimeException { |
| SaverSAXException(SAXException e) { |
| _saxException = e; |
| } |
| |
| SAXException _saxException; |
| } |
| |
| private String getPrefixedName(QName name) { |
| String uri = name.getNamespaceURI(); |
| String local = name.getLocalPart(); |
| |
| if (uri.length() == 0) |
| return local; |
| |
| String prefix = getUriMapping(uri); |
| |
| if (prefix.length() == 0) |
| return local; |
| |
| return prefix + ":" + local; |
| } |
| |
| private void emitNamespacesHelper() { |
| for (iterateMappings(); hasMapping(); nextMapping()) { |
| String prefix = mappingPrefix(); |
| String uri = mappingUri(); |
| |
| try { |
| _contentHandler.startPrefixMapping(prefix, uri); |
| } catch (SAXException e) { |
| throw new SaverSAXException(e); |
| } |
| |
| if (_nsAsAttrs) |
| if (prefix == null || prefix.length() == 0) |
| _attributes.addAttribute("http://www.w3.org/2000/xmlns/", "xmlns", "xmlns", "CDATA", uri); |
| else |
| _attributes.addAttribute("http://www.w3.org/2000/xmlns/", prefix, "xmlns:" + prefix, "CDATA", uri); |
| } |
| } |
| |
| protected boolean emitElement(SaveCur c, ArrayList attrNames, ArrayList attrValues) { |
| _attributes.clear(); |
| |
| if (saveNamespacesFirst()) |
| emitNamespacesHelper(); |
| |
| for (int i = 0; i < attrNames.size(); i++) { |
| QName name = (QName) attrNames.get(i); |
| |
| _attributes.addAttribute( |
| name.getNamespaceURI(), name.getLocalPart(), getPrefixedName(name), |
| "CDATA", (String) attrValues.get(i)); |
| } |
| |
| if (!saveNamespacesFirst()) |
| emitNamespacesHelper(); |
| |
| QName elemName = c.getName(); |
| |
| try { |
| _contentHandler.startElement( |
| elemName.getNamespaceURI(), elemName.getLocalPart(), |
| getPrefixedName(elemName), _attributes); |
| } catch (SAXException e) { |
| throw new SaverSAXException(e); |
| } |
| |
| return false; |
| } |
| |
| protected void emitFinish(SaveCur c) { |
| QName name = c.getName(); |
| |
| try { |
| _contentHandler.endElement( |
| name.getNamespaceURI(), name.getLocalPart(), getPrefixedName(name)); |
| |
| for (iterateMappings(); hasMapping(); nextMapping()) |
| _contentHandler.endPrefixMapping(mappingPrefix()); |
| } catch (SAXException e) { |
| throw new SaverSAXException(e); |
| } |
| } |
| |
| protected void emitText(SaveCur c) { |
| assert c.isText(); |
| |
| Object src = c.getChars(); |
| |
| try { |
| if (src instanceof char[]) { |
| // Pray the user does not modify the buffer .... |
| _contentHandler.characters((char[]) src, c._offSrc, c._cchSrc); |
| } else { |
| if (_buf == null) |
| _buf = new char[1024]; |
| |
| while (c._cchSrc > 0) { |
| int cch = java.lang.Math.min(_buf.length, c._cchSrc); |
| |
| CharUtil.getChars(_buf, 0, src, c._offSrc, cch); |
| |
| _contentHandler.characters(_buf, 0, cch); |
| |
| c._offSrc += cch; |
| c._cchSrc -= cch; |
| } |
| } |
| } catch (SAXException e) { |
| throw new SaverSAXException(e); |
| } |
| } |
| |
| protected void emitComment(SaveCur c) { |
| if (_lexicalHandler != null) { |
| c.push(); |
| |
| c.next(); |
| |
| try { |
| if (!c.isText()) |
| _lexicalHandler.comment(null, 0, 0); |
| else { |
| Object src = c.getChars(); |
| |
| if (src instanceof char[]) { |
| // Pray the user does not modify the buffer .... |
| _lexicalHandler.comment((char[]) src, c._offSrc, c._cchSrc); |
| } else { |
| if (_buf == null || _buf.length < c._cchSrc) |
| _buf = new char[java.lang.Math.max(1024, c._cchSrc)]; |
| |
| CharUtil.getChars(_buf, 0, src, c._offSrc, c._cchSrc); |
| |
| _lexicalHandler.comment(_buf, 0, c._cchSrc); |
| } |
| } |
| } catch (SAXException e) { |
| throw new SaverSAXException(e); |
| } |
| |
| c.pop(); |
| } |
| } |
| |
| protected void emitProcinst(SaveCur c) { |
| String target = c.getName().getLocalPart(); |
| |
| c.push(); |
| |
| c.next(); |
| |
| String value = CharUtil.getString(c.getChars(), c._offSrc, c._cchSrc); |
| |
| c.pop(); |
| |
| try { |
| _contentHandler.processingInstruction(c.getName().getLocalPart(), value); |
| } catch (SAXException e) { |
| throw new SaverSAXException(e); |
| } |
| } |
| |
| protected void emitDocType(String docTypeName, String publicId, String systemId) { |
| if (_lexicalHandler != null) { |
| try { |
| _lexicalHandler.startDTD(docTypeName, publicId, systemId); |
| _lexicalHandler.endDTD(); |
| } catch (SAXException e) { |
| throw new SaverSAXException(e); |
| } |
| } |
| } |
| |
| protected void emitStartDoc(SaveCur c) { |
| } |
| |
| protected void emitEndDoc(SaveCur c) { |
| } |
| |
| private ContentHandler _contentHandler; |
| private LexicalHandler _lexicalHandler; |
| |
| private AttributesImpl _attributes; |
| |
| private char[] _buf; |
| private boolean _nsAsAttrs; |
| } |
| |
| // |
| // |
| // |
| |
| static abstract class SaveCur { |
| final boolean isRoot() { |
| return kind() == ROOT; |
| } |
| |
| final boolean isElem() { |
| return kind() == ELEM; |
| } |
| |
| final boolean isAttr() { |
| return kind() == ATTR; |
| } |
| |
| final boolean isText() { |
| return kind() == TEXT; |
| } |
| |
| final boolean isComment() { |
| return kind() == COMMENT; |
| } |
| |
| final boolean isProcinst() { |
| return kind() == PROCINST; |
| } |
| |
| final boolean isFinish() { |
| return Cur.kindIsFinish(kind()); |
| } |
| |
| final boolean isContainer() { |
| return Cur.kindIsContainer(kind()); |
| } |
| |
| final boolean isNormalAttr() { |
| return kind() == ATTR && !isXmlns(); |
| } |
| |
| final boolean skip() { |
| toEnd(); |
| return next(); |
| } |
| |
| abstract void release(); |
| |
| abstract int kind(); |
| |
| abstract QName getName(); |
| |
| abstract String getXmlnsPrefix(); |
| |
| abstract String getXmlnsUri(); |
| |
| abstract boolean isXmlns(); |
| |
| abstract boolean hasChildren(); |
| |
| abstract boolean hasText(); |
| |
| abstract boolean isTextCData(); |
| |
| abstract boolean toFirstAttr(); |
| |
| abstract boolean toNextAttr(); |
| |
| abstract String getAttrValue(); |
| |
| abstract boolean next(); |
| |
| abstract void toEnd(); |
| |
| abstract void push(); |
| |
| abstract void pop(); |
| |
| abstract Object getChars(); |
| |
| abstract List getAncestorNamespaces(); |
| |
| abstract XmlDocumentProperties getDocProps(); |
| |
| int _offSrc; |
| int _cchSrc; |
| } |
| |
| // TODO - saving a fragment need to take namesapces from root and |
| // reflect them on the document element |
| |
| private static final class DocSaveCur extends SaveCur { |
| DocSaveCur(Cur c) { |
| assert c.isRoot(); |
| _cur = c.weakCur(this); |
| } |
| |
| void release() { |
| _cur.release(); |
| _cur = null; |
| } |
| |
| int kind() { |
| return _cur.kind(); |
| } |
| |
| QName getName() { |
| return _cur.getName(); |
| } |
| |
| String getXmlnsPrefix() { |
| return _cur.getXmlnsPrefix(); |
| } |
| |
| String getXmlnsUri() { |
| return _cur.getXmlnsUri(); |
| } |
| |
| boolean isXmlns() { |
| return _cur.isXmlns(); |
| } |
| |
| boolean hasChildren() { |
| return _cur.hasChildren(); |
| } |
| |
| boolean hasText() { |
| return _cur.hasText(); |
| } |
| |
| boolean isTextCData() { |
| return _cur.isTextCData(); |
| } |
| |
| boolean toFirstAttr() { |
| return _cur.toFirstAttr(); |
| } |
| |
| boolean toNextAttr() { |
| return _cur.toNextAttr(); |
| } |
| |
| String getAttrValue() { |
| assert _cur.isAttr(); |
| return _cur.getValueAsString(); |
| } |
| |
| void toEnd() { |
| _cur.toEnd(); |
| } |
| |
| boolean next() { |
| return _cur.next(); |
| } |
| |
| void push() { |
| _cur.push(); |
| } |
| |
| void pop() { |
| _cur.pop(); |
| } |
| |
| List getAncestorNamespaces() { |
| return null; |
| } |
| |
| Object getChars() { |
| Object o = _cur.getChars(-1); |
| |
| _offSrc = _cur._offSrc; |
| _cchSrc = _cur._cchSrc; |
| |
| return o; |
| } |
| |
| XmlDocumentProperties getDocProps() { |
| return Locale.getDocProps(_cur, false); |
| } |
| |
| private Cur _cur; |
| } |
| |
| private static abstract class FilterSaveCur extends SaveCur { |
| FilterSaveCur(SaveCur c) { |
| assert c.isRoot(); |
| _cur = c; |
| } |
| |
| // Can filter anything by root and attributes and text |
| protected abstract boolean filter(); |
| |
| void release() { |
| _cur.release(); |
| _cur = null; |
| } |
| |
| int kind() { |
| return _cur.kind(); |
| } |
| |
| QName getName() { |
| return _cur.getName(); |
| } |
| |
| String getXmlnsPrefix() { |
| return _cur.getXmlnsPrefix(); |
| } |
| |
| String getXmlnsUri() { |
| return _cur.getXmlnsUri(); |
| } |
| |
| boolean isXmlns() { |
| return _cur.isXmlns(); |
| } |
| |
| boolean hasChildren() { |
| return _cur.hasChildren(); |
| } |
| |
| boolean hasText() { |
| return _cur.hasText(); |
| } |
| |
| boolean isTextCData() { |
| return _cur.isTextCData(); |
| } |
| |
| boolean toFirstAttr() { |
| return _cur.toFirstAttr(); |
| } |
| |
| boolean toNextAttr() { |
| return _cur.toNextAttr(); |
| } |
| |
| String getAttrValue() { |
| return _cur.getAttrValue(); |
| } |
| |
| void toEnd() { |
| _cur.toEnd(); |
| } |
| |
| boolean next() { |
| if (!_cur.next()) |
| return false; |
| |
| if (!filter()) |
| return true; |
| |
| assert !isRoot() && !isText() && !isAttr(); |
| |
| toEnd(); |
| |
| return next(); |
| } |
| |
| void push() { |
| _cur.push(); |
| } |
| |
| void pop() { |
| _cur.pop(); |
| } |
| |
| List getAncestorNamespaces() { |
| return _cur.getAncestorNamespaces(); |
| } |
| |
| Object getChars() { |
| Object o = _cur.getChars(); |
| |
| _offSrc = _cur._offSrc; |
| _cchSrc = _cur._cchSrc; |
| |
| return o; |
| } |
| |
| XmlDocumentProperties getDocProps() { |
| return _cur.getDocProps(); |
| } |
| |
| private SaveCur _cur; |
| } |
| |
| private static final class FilterPiSaveCur extends FilterSaveCur { |
| FilterPiSaveCur(SaveCur c, String target) { |
| super(c); |
| |
| _piTarget = target; |
| } |
| |
| protected boolean filter() { |
| return kind() == PROCINST && getName().getLocalPart().equals(_piTarget); |
| } |
| |
| private String _piTarget; |
| } |
| |
| private static final class FragSaveCur extends SaveCur { |
| FragSaveCur(Cur start, Cur end, QName synthElem) { |
| _saveAttr = start.isAttr() && start.isSamePos(end); |
| |
| _cur = start.weakCur(this); |
| _end = end.weakCur(this); |
| |
| _elem = synthElem; |
| |
| _state = ROOT_START; |
| |
| _stateStack = new int[8]; |
| |
| start.push(); |
| computeAncestorNamespaces(start); |
| start.pop(); |
| } |
| |
| List getAncestorNamespaces() { |
| return _ancestorNamespaces; |
| } |
| |
| private void computeAncestorNamespaces(Cur c) { |
| _ancestorNamespaces = new ArrayList(); |
| |
| while (c.toParentRaw()) { |
| if (c.toFirstAttr()) { |
| do { |
| if (c.isXmlns()) { |
| String prefix = c.getXmlnsPrefix(); |
| String uri = c.getXmlnsUri(); |
| |
| // Don't let xmlns:foo="" get used |
| |
| if (uri.length() > 0 || prefix.length() == 0) { |
| _ancestorNamespaces.add(c.getXmlnsPrefix()); |
| _ancestorNamespaces.add(c.getXmlnsUri()); |
| } |
| } |
| } |
| while (c.toNextAttr()); |
| |
| c.toParent(); |
| } |
| } |
| } |
| |
| // |
| // |
| // |
| |
| void release() { |
| _cur.release(); |
| _cur = null; |
| |
| _end.release(); |
| _end = null; |
| } |
| |
| int kind() { |
| switch (_state) { |
| case ROOT_START: |
| return ROOT; |
| case ELEM_START: |
| return ELEM; |
| case ELEM_END: |
| return -ELEM; |
| case ROOT_END: |
| return -ROOT; |
| } |
| |
| assert _state == CUR; |
| |
| return _cur.kind(); |
| } |
| |
| QName getName() { |
| switch (_state) { |
| case ROOT_START: |
| case ROOT_END: |
| return null; |
| case ELEM_START: |
| case ELEM_END: |
| return _elem; |
| } |
| |
| assert _state == CUR; |
| |
| return _cur.getName(); |
| } |
| |
| String getXmlnsPrefix() { |
| assert _state == CUR && _cur.isAttr(); |
| return _cur.getXmlnsPrefix(); |
| } |
| |
| String getXmlnsUri() { |
| assert _state == CUR && _cur.isAttr(); |
| return _cur.getXmlnsUri(); |
| } |
| |
| boolean isXmlns() { |
| assert _state == CUR && _cur.isAttr(); |
| return _cur.isXmlns(); |
| } |
| |
| boolean hasChildren() { |
| boolean hasChildren = false; |
| |
| if (isContainer()) { // is there a faster way to do this? |
| push(); |
| next(); |
| |
| if (!isText() && !isFinish()) |
| hasChildren = true; |
| |
| pop(); |
| } |
| |
| return hasChildren; |
| } |
| |
| boolean hasText() { |
| boolean hasText = false; |
| |
| if (isContainer()) { |
| push(); |
| next(); |
| |
| if (isText()) |
| hasText = true; |
| |
| pop(); |
| } |
| |
| return hasText; |
| } |
| |
| boolean isTextCData() { |
| return _cur.isTextCData(); |
| } |
| |
| Object getChars() { |
| assert _state == CUR && _cur.isText(); |
| |
| Object src = _cur.getChars(-1); |
| |
| _offSrc = _cur._offSrc; |
| _cchSrc = _cur._cchSrc; |
| |
| return src; |
| } |
| |
| boolean next() { |
| switch (_state) { |
| case ROOT_START: { |
| _state = _elem == null ? CUR : ELEM_START; |
| break; |
| } |
| |
| case ELEM_START: { |
| if (_saveAttr) |
| _state = ELEM_END; |
| else { |
| if (_cur.isAttr()) { |
| _cur.toParent(); |
| _cur.next(); |
| } |
| |
| if (_cur.isSamePos(_end)) |
| _state = ELEM_END; |
| else |
| _state = CUR; |
| } |
| |
| break; |
| } |
| |
| case CUR: { |
| assert !_cur.isAttr(); |
| |
| _cur.next(); |
| |
| if (_cur.isSamePos(_end)) |
| _state = _elem == null ? ROOT_END : ELEM_END; |
| |
| break; |
| } |
| |
| case ELEM_END: { |
| _state = ROOT_END; |
| break; |
| } |
| case ROOT_END: |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void toEnd() { |
| switch (_state) { |
| case ROOT_START: |
| _state = ROOT_END; |
| return; |
| case ELEM_START: |
| _state = ELEM_END; |
| return; |
| case ROOT_END: |
| case ELEM_END: |
| return; |
| } |
| |
| assert _state == CUR && !_cur.isAttr() && !_cur.isText(); |
| |
| _cur.toEnd(); |
| } |
| |
| boolean toFirstAttr() { |
| switch (_state) { |
| case ROOT_END: |
| case ELEM_END: |
| case ROOT_START: |
| return false; |
| case CUR: |
| return _cur.toFirstAttr(); |
| } |
| |
| assert _state == ELEM_START; |
| |
| if (!_cur.isAttr()) |
| return false; |
| |
| _state = CUR; |
| |
| return true; |
| } |
| |
| boolean toNextAttr() { |
| assert _state == CUR; |
| return !_saveAttr && _cur.toNextAttr(); |
| } |
| |
| String getAttrValue() { |
| assert _state == CUR && _cur.isAttr(); |
| return _cur.getValueAsString(); |
| } |
| |
| void push() { |
| if (_stateStackSize == _stateStack.length) { |
| int[] newStateStack = new int[_stateStackSize * 2]; |
| System.arraycopy(_stateStack, 0, newStateStack, 0, _stateStackSize); |
| _stateStack = newStateStack; |
| } |
| |
| _stateStack[_stateStackSize++] = _state; |
| _cur.push(); |
| } |
| |
| void pop() { |
| _cur.pop(); |
| _state = _stateStack[--_stateStackSize]; |
| } |
| |
| XmlDocumentProperties getDocProps() { |
| return Locale.getDocProps(_cur, false); |
| } |
| |
| // |
| // |
| // |
| |
| private Cur _cur; |
| private Cur _end; |
| |
| private ArrayList _ancestorNamespaces; |
| |
| private QName _elem; |
| |
| private boolean _saveAttr; |
| |
| private static final int ROOT_START = 1; |
| private static final int ELEM_START = 2; |
| private static final int ROOT_END = 3; |
| private static final int ELEM_END = 4; |
| private static final int CUR = 5; |
| |
| private int _state; |
| |
| private int[] _stateStack; |
| private int _stateStackSize; |
| } |
| |
| private static final class PrettySaveCur extends SaveCur { |
| PrettySaveCur(SaveCur c, XmlOptions options) { |
| _sb = new StringBuffer(); |
| _stack = new ArrayList(); |
| |
| _cur = c; |
| |
| assert options != null; |
| |
| _prettyIndent = 2; |
| |
| if (options.hasOption(XmlOptions.SAVE_PRETTY_PRINT_INDENT)) { |
| _prettyIndent = |
| ((Integer) options.get(XmlOptions.SAVE_PRETTY_PRINT_INDENT)).intValue(); |
| } |
| |
| if (options.hasOption(XmlOptions.SAVE_PRETTY_PRINT_OFFSET)) { |
| _prettyOffset = |
| ((Integer) options.get(XmlOptions.SAVE_PRETTY_PRINT_OFFSET)).intValue(); |
| } |
| |
| if (options.hasOption(XmlOptions.LOAD_SAVE_CDATA_BOOKMARKS)) { |
| _useCDataBookmarks = true; |
| } |
| } |
| |
| List getAncestorNamespaces() { |
| return _cur.getAncestorNamespaces(); |
| } |
| |
| void release() { |
| _cur.release(); |
| } |
| |
| int kind() { |
| return _txt == null ? _cur.kind() : TEXT; |
| } |
| |
| QName getName() { |
| assert _txt == null; |
| return _cur.getName(); |
| } |
| |
| String getXmlnsPrefix() { |
| assert _txt == null; |
| return _cur.getXmlnsPrefix(); |
| } |
| |
| String getXmlnsUri() { |
| assert _txt == null; |
| return _cur.getXmlnsUri(); |
| } |
| |
| boolean isXmlns() { |
| return _txt == null ? _cur.isXmlns() : false; |
| } |
| |
| boolean hasChildren() { |
| return _txt == null ? _cur.hasChildren() : false; |
| } |
| |
| boolean hasText() { |
| return _txt == null ? _cur.hasText() : false; |
| } |
| |
| // _cur.isTextCData() is expensive do it only if useCDataBookmarks option is enabled |
| boolean isTextCData() { |
| return _txt == null ? (_useCDataBookmarks && _cur.isTextCData()) |
| : _isTextCData; |
| } |
| |
| boolean toFirstAttr() { |
| assert _txt == null; |
| return _cur.toFirstAttr(); |
| } |
| |
| boolean toNextAttr() { |
| assert _txt == null; |
| return _cur.toNextAttr(); |
| } |
| |
| String getAttrValue() { |
| assert _txt == null; |
| return _cur.getAttrValue(); |
| } |
| |
| void toEnd() { |
| assert _txt == null; |
| _cur.toEnd(); |
| |
| if (_cur.kind() == -ELEM) |
| _depth--; |
| } |
| |
| boolean next() { |
| int k; |
| |
| if (_txt != null) { |
| assert _txt.length() > 0; |
| assert !_cur.isText(); |
| _txt = null; |
| _isTextCData = false; |
| k = _cur.kind(); |
| } else { |
| int prevKind = k = _cur.kind(); |
| |
| if (!_cur.next()) |
| return false; |
| |
| _sb.delete(0, _sb.length()); |
| |
| assert _txt == null; |
| |
| // place any text encountered in the buffer |
| if (_cur.isText()) { |
| // _cur.isTextCData() is expensive do it only if useCDataBookmarks option is enabled |
| _isTextCData = _useCDataBookmarks && _cur.isTextCData(); |
| CharUtil.getString(_sb, _cur.getChars(), _cur._offSrc, _cur._cchSrc); |
| _cur.next(); |
| k = _cur.kind(); |
| if (prevKind != ELEM || k != -ELEM) { |
| trim(_sb); |
| } |
| } |
| |
| k = _cur.kind(); |
| |
| // Check for non leaf, _prettyIndent < 0 means that the save is all on one line |
| |
| if (_prettyIndent >= 0 && |
| prevKind != COMMENT && prevKind != PROCINST && (prevKind != ELEM || k != -ELEM)) { |
| if (_sb.length() > 0) { |
| _sb.insert(0, _newLine); |
| spaces(_sb, _newLine.length(), _prettyOffset + _prettyIndent * _depth); |
| } |
| |
| if (k != -ROOT) { |
| if (prevKind != ROOT) |
| _sb.append(_newLine); |
| |
| int d = k < 0 ? _depth - 1 : _depth; |
| spaces(_sb, _sb.length(), _prettyOffset + _prettyIndent * d); |
| } |
| } |
| |
| if (_sb.length() > 0) { |
| _txt = _sb.toString(); |
| k = TEXT; |
| } |
| } |
| |
| if (k == ELEM) |
| _depth++; |
| else if (k == -ELEM) |
| _depth--; |
| |
| return true; |
| } |
| |
| void push() { |
| _cur.push(); |
| _stack.add(_txt); |
| _stack.add(new Integer(_depth)); |
| _isTextCData = false; |
| } |
| |
| void pop() { |
| _cur.pop(); |
| _depth = ((Integer) _stack.remove(_stack.size() - 1)).intValue(); |
| _txt = (String) _stack.remove(_stack.size() - 1); |
| _isTextCData = false; |
| } |
| |
| Object getChars() { |
| if (_txt != null) { |
| _offSrc = 0; |
| _cchSrc = _txt.length(); |
| return _txt; |
| } |
| |
| Object o = _cur.getChars(); |
| |
| _offSrc = _cur._offSrc; |
| _cchSrc = _cur._cchSrc; |
| |
| return o; |
| } |
| |
| XmlDocumentProperties getDocProps() { |
| return _cur.getDocProps(); |
| } |
| |
| static void spaces(StringBuffer sb, int offset, int count) { |
| while (count-- > 0) |
| sb.insert(offset, ' '); |
| } |
| |
| static void trim(StringBuffer sb) { |
| int i; |
| |
| for (i = 0; i < sb.length(); i++) |
| if (!CharUtil.isWhiteSpace(sb.charAt(i))) |
| break; |
| |
| sb.delete(0, i); |
| |
| for (i = sb.length(); i > 0; i--) |
| if (!CharUtil.isWhiteSpace(sb.charAt(i - 1))) |
| break; |
| |
| sb.delete(i, sb.length()); |
| } |
| |
| private SaveCur _cur; |
| |
| private int _prettyIndent; |
| private int _prettyOffset; |
| |
| private String _txt; |
| private StringBuffer _sb; |
| |
| private int _depth; |
| |
| private ArrayList _stack; |
| private boolean _isTextCData = false; |
| private boolean _useCDataBookmarks = false; |
| } |
| |
| |
| // |
| // |
| // |
| |
| private final Locale _locale; |
| private final long _version; |
| |
| private SaveCur _cur; |
| |
| private List _ancestorNamespaces; |
| private Map _suggestedPrefixes; |
| protected XmlOptionCharEscapeMap _replaceChar; |
| private boolean _useDefaultNamespace; |
| private Map _preComputedNamespaces; |
| private boolean _saveNamespacesFirst; |
| |
| private ArrayList _attrNames; |
| private ArrayList _attrValues; |
| |
| private ArrayList _namespaceStack; |
| private int _currentMapping; |
| private HashMap _uriMap; |
| private HashMap _prefixMap; |
| private String _initialDefaultUri; |
| |
| static final String _newLine = |
| SystemProperties.getProperty("line.separator") == null |
| ? "\n" |
| : SystemProperties.getProperty("line.separator"); |
| } |