/* * eXist Open Source Native XML Database * Copyright (C) 2001-2014 The eXist Project * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * $Id$ */ package org.exist.dom.memtree; import org.exist.dom.QName; import org.exist.numbering.NodeId; import org.exist.stax.ExtendedXMLStreamReader; import org.w3c.dom.Node; import javax.xml.namespace.NamespaceContext; import javax.xml.stream.Location; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; /** * Implementation of a StAX {@link javax.xml.stream.XMLStreamReader}, which wraps around eXist's in-memory DOM. * This class complements {@link org.exist.stax.EmbeddedXMLStreamReader} which reads persistent documents. */ public class InMemoryXMLStreamReader implements ExtendedXMLStreamReader { private static final String NOT_START_ELEMENT = "Cursor is not at the start of an element"; private final DocumentImpl doc; private final NodeImpl rootNode; private int currentNode; private int state = XMLStreamReader.START_DOCUMENT; public InMemoryXMLStreamReader(final DocumentImpl doc, final NodeImpl node) { this.doc = doc; this.rootNode = node; this.currentNode = -1; } @Override public Object getProperty(final String name) throws IllegalArgumentException { if(name.equals(PROPERTY_NODE_ID)) { if(currentNode < 0 || currentNode >= doc.size) { return null; } doc.expand(); return doc.nodeId[currentNode]; } return null; } @Override public int next() throws XMLStreamException { if(currentNode > -1) { int next = -1; if(state == XMLStreamReader.START_ELEMENT || state == XMLStreamReader.START_DOCUMENT) { next = doc.getFirstChildFor(currentNode); if(next < 0) { // no child nodes state = XMLStreamReader.END_ELEMENT; return state; } } if(next < 0) { next = doc.next[currentNode]; if(next < currentNode) { if(next == 0) { state = XMLStreamReader.END_DOCUMENT; } else { state = XMLStreamReader.END_ELEMENT; } currentNode = next; return state; } } currentNode = next; } else { currentNode = rootNode.getNodeNumber(); } switch(doc.nodeKind[currentNode]) { case Node.TEXT_NODE: { state = XMLStreamReader.CHARACTERS; break; } case Node.CDATA_SECTION_NODE: { state = XMLStreamReader.CDATA; break; } case Node.COMMENT_NODE: { state = XMLStreamReader.COMMENT; break; } case Node.PROCESSING_INSTRUCTION_NODE: { state = XMLStreamReader.PROCESSING_INSTRUCTION; break; } case Node.ELEMENT_NODE: { state = XMLStreamReader.START_ELEMENT; break; } } return state; } @Override public void require(final int type, final String namespaceURI, final String localName) throws XMLStreamException { } @Override public String getElementText() throws XMLStreamException { if(getEventType() != START_ELEMENT) { throw new XMLStreamException("parser must be on START_ELEMENT to read next text"); } int eventType = next(); final StringBuilder content = new StringBuilder(); while(eventType != END_ELEMENT) { if(eventType == CHARACTERS || eventType == CDATA || eventType == SPACE || eventType == ENTITY_REFERENCE) { content.append(getText()); } else if(eventType == PROCESSING_INSTRUCTION || eventType == COMMENT) { // skipping } else if(eventType == END_DOCUMENT) { throw new XMLStreamException("unexpected end of document when reading element text content"); } else if(eventType == START_ELEMENT) { throw new XMLStreamException("element text content may not contain START_ELEMENT"); } else { throw new XMLStreamException("Unexpected event type " + eventType); } eventType = next(); } return content.toString(); } @Override public int nextTag() throws XMLStreamException { throw new UnsupportedOperationException(); } @Override public boolean hasNext() throws XMLStreamException { return currentNode != rootNode.getNodeNumber() || state == XMLStreamReader.START_DOCUMENT || state == XMLStreamReader.START_ELEMENT; } @Override public void close() throws XMLStreamException { } @Override public String getNamespaceURI(final String prefix) { return null; } @Override public boolean isStartElement() { return state == XMLStreamReader.START_ELEMENT; } @Override public boolean isEndElement() { return state == XMLStreamReader.END_ELEMENT; } @Override public boolean isCharacters() { return state == XMLStreamReader.CHARACTERS; } @Override public boolean isWhiteSpace() { return false; } @Override public String getAttributeValue(final String namespaceURI, final String localName) { final int attrCount = doc.getAttributesCountFor(currentNode); if(attrCount == 0) { return null; } final int attrStart = doc.alpha[currentNode]; for(int i = 0; i < attrCount; i++) { final QName qname = doc.attrName[attrStart + i]; if((namespaceURI == null || namespaceURI.equals(qname.getNamespaceURI())) && localName.equals(qname.getLocalPart())) { return doc.attrValue[attrStart + i]; } } return null; } @Override public int getAttributeCount() { if(state != START_ELEMENT) { throw new IllegalStateException(NOT_START_ELEMENT); } return doc.getAttributesCountFor(currentNode); } @Override public QName getAttributeQName(final int index) { if(state != START_ELEMENT) { throw new IllegalStateException(NOT_START_ELEMENT); } if(index > getAttributeCount()) { throw new ArrayIndexOutOfBoundsException(); } final int attr = doc.alpha[currentNode]; return doc.attrName[attr + index]; } @Override public javax.xml.namespace.QName getAttributeName(final int index) { return getAttributeQName(index).toJavaQName(); } @Override public String getAttributeNamespace(final int index) { if(state != START_ELEMENT) { throw new IllegalStateException(NOT_START_ELEMENT); } return getAttributeQName(index).getNamespaceURI(); } @Override public String getAttributeLocalName(final int index) { if(state != START_ELEMENT) { throw new IllegalStateException(NOT_START_ELEMENT); } return getAttributeQName(index).getLocalPart(); } @Override public String getAttributePrefix(final int index) { if(state != START_ELEMENT) { throw new IllegalStateException(NOT_START_ELEMENT); } return getAttributeQName(index).getPrefix(); } @Override public NodeId getAttributeId(final int index) { if(state != START_ELEMENT) { throw new IllegalStateException(NOT_START_ELEMENT); } if(index > getAttributeCount()) { throw new ArrayIndexOutOfBoundsException(); } doc.expand(); final int attr = doc.alpha[currentNode]; return doc.attrNodeId[attr + index]; } @Override public String getAttributeType(final int index) { if(state != START_ELEMENT) { throw new IllegalStateException(NOT_START_ELEMENT); } if(index > getAttributeCount()) { throw new ArrayIndexOutOfBoundsException(); } final int attr = doc.alpha[currentNode]; final int type = doc.attrType[attr + index]; switch(type) { case AttrImpl.ATTR_ID_TYPE: { return "ID"; } case AttrImpl.ATTR_IDREF_TYPE: { return "IDREF"; } case AttrImpl.ATTR_IDREFS_TYPE: { return "IDREFS"; } default: { return "CDATA"; } } } @Override public String getAttributeValue(final int index) { if(state != START_ELEMENT) { throw new IllegalStateException(NOT_START_ELEMENT); } if(index > getAttributeCount()) { throw new ArrayIndexOutOfBoundsException(); } final int attr = doc.alpha[currentNode]; return doc.attrValue[attr + index]; } @Override public boolean isAttributeSpecified(final int index) { if(state != START_ELEMENT) { throw new IllegalStateException(NOT_START_ELEMENT); } return true; } @Override public int getNamespaceCount() { if(state != START_ELEMENT && state != END_ELEMENT && state != NAMESPACE) { throw new IllegalStateException("Cursor is not at an element or namespace"); } return doc.getNamespacesCountFor(currentNode); } @Override public String getNamespacePrefix(final int index) { if(index > getNamespaceCount()) { throw new ArrayIndexOutOfBoundsException(); } final int ns = doc.alphaLen[currentNode]; final QName nsQName = doc.namespaceCode[ns + index]; return nsQName.getLocalPart(); } @Override public String getNamespaceURI(final int index) { if(index > getNamespaceCount()) { throw new ArrayIndexOutOfBoundsException(); } final int ns = doc.alphaLen[currentNode]; final QName nsQName = doc.namespaceCode[ns + index]; return nsQName.getNamespaceURI(); } @Override public NamespaceContext getNamespaceContext() { throw new UnsupportedOperationException(); } @Override public int getEventType() { return state; } @Override public String getText() { if(state == CHARACTERS || state == COMMENT || state == CDATA) { return new String(doc.characters, doc.alpha[currentNode], doc.alphaLen[currentNode]); } return ""; } @Override public char[] getTextCharacters() { final char[] ch = new char[doc.alphaLen[currentNode]]; System.arraycopy(doc.characters, doc.alpha[currentNode], ch, 0, ch.length); return ch; } @Override public int getTextCharacters(final int sourceStart, final char[] target, final int targetStart, final int length) throws XMLStreamException { throw new UnsupportedOperationException(); } @Override public int getTextStart() { throw new UnsupportedOperationException(); } @Override public int getTextLength() { throw new UnsupportedOperationException(); } @Override public String getEncoding() { throw new UnsupportedOperationException(); } @Override public boolean hasText() { return state == CHARACTERS || state == COMMENT || state == CDATA; } @Override public Location getLocation() { throw new UnsupportedOperationException(); } @Override public QName getQName() { if(state == START_ELEMENT || state == END_ELEMENT) { return doc.nodeName[currentNode]; } throw new IllegalStateException("Cursor is not at the start of end of an element"); } @Override public javax.xml.namespace.QName getName() { return getQName().toJavaQName(); } @Override public String getLocalName() { return getQName().getLocalPart(); } @Override public boolean hasName() { return state == START_ELEMENT || state == END_ELEMENT; } @Override public String getNamespaceURI() { return getQName().getNamespaceURI(); } @Override public String getPrefix() { return getQName().getPrefix(); } @Override public String getVersion() { return "1.0"; } @Override public boolean isStandalone() { return false; } @Override public boolean standaloneSet() { return false; } @Override public String getCharacterEncodingScheme() { return null; } @Override public String getPITarget() { final QName qn = doc.nodeName[currentNode]; return qn != null ? qn.getLocalPart() : null; } @Override public String getPIData() { return new String(doc.characters, doc.alpha[currentNode], doc.alphaLen[currentNode]); } }