package org.basex.api.xqj; import static org.basex.util.Token.*; import java.util.*; import javax.xml.namespace.*; import javax.xml.stream.*; import org.basex.api.jaxp.*; import org.basex.data.*; import org.basex.query.*; import org.basex.query.item.*; import org.basex.query.iter.*; import org.basex.query.util.*; import org.basex.util.*; import org.basex.util.list.*; /** * XML Stream Reader implementation. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ final class IterStreamReader implements XMLStreamReader { /** Properties. */ private static final Properties PROPS = new Properties(); /** Namespaces references. */ private final NSContext ns = new NSContext(); /** Result iterator. */ private final Iter result; /** Next flag. */ private boolean next; /** Node iterator. */ private NodeReader read; /** Attributes. */ private NodeCache atts; /** Current state. */ int kind = START_DOCUMENT; /** Current node. */ ANode node; /** * Constructor. * @param res result iterator */ IterStreamReader(final Iter res) { result = res; // included for wrapping the stream reader into an XML event reader PROPS.put(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE); } @Override public void close() { } @Override public int getAttributeCount() { return (int) attributes().size(); } @Override public String getAttributeLocalName(final int i) { return string(attributes().get(i).name()); } @Override public QName getAttributeName(final int i) { return attributes().get(i).qname().toJava(); } @Override public String getAttributeNamespace(final int i) { return string(attributes().get(i).qname().uri()); } @Override public String getAttributePrefix(final int i) { return string(attributes().get(i).qname().prefix()); } @Override public String getAttributeType(final int i) { final String name = getAttributeLocalName(i); for(final String a : ATTYPES) if(name.equals(a)) return name; return "CDATA"; } /** Attribute types. */ private static final String[] ATTYPES = { "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES" }; @Override public String getAttributeValue(final int i) { return string(attributes().get(i).string()); } @Override public String getAttributeValue(final String s, final String s1) { for(int a = 0; a < atts.size(); ++a) { if(!s1.equals(getAttributeLocalName(a))) continue; if(s == null || s.equals(getAttributeNamespace(a))) return getAttributeValue(a); } return null; } /** * Caches and returns the attributes for the current element. * @return node cache */ private NodeCache attributes() { if(atts == null) { checkType(START_ELEMENT, ATTRIBUTE); atts = new NodeCache(); final AxisIter ai = node.attributes(); for(ANode n; (n = ai.next()) != null;) atts.add(n.finish()); } return atts; } @Override public String getCharacterEncodingScheme() { return null; } @Override public String getElementText() throws XMLStreamException { checkType(START_ELEMENT); next(); final TokenBuilder tb = new TokenBuilder(); while(kind != END_ELEMENT) { if(isType(CHARACTERS, CDATA, SPACE, ENTITY_REFERENCE)) { tb.add(node.string()); } else if(isType(END_DOCUMENT)) { throw new XMLStreamException("Unexpected end of document."); } else if(isType(START_ELEMENT)) { throw new XMLStreamException("START_ELEMENT not expected."); } else { checkType(PROCESSING_INSTRUCTION, COMMENT); } next(); } return tb.toString(); } @Override public String getEncoding() { return null; } @Override public int getEventType() { return kind; } @Override public String getLocalName() { checkType(START_ELEMENT, END_ELEMENT, ENTITY_REFERENCE); return string(node.name()); } @Override public Location getLocation() { return new LocationImpl(); } @Override public QName getName() { checkType(START_ELEMENT, END_ELEMENT, ENTITY_REFERENCE); return node.qname().toJava(); } @Override public NamespaceContext getNamespaceContext() { return new BXNamespaceContext(ns); } @Override public int getNamespaceCount() { checkType(START_ELEMENT, END_ELEMENT, NAMESPACE); return 0; } @Override public String getNamespacePrefix(final int i) { checkType(START_ELEMENT, END_ELEMENT, NAMESPACE); return null; } @Override public String getNamespaceURI() { return null; } @Override public String getNamespaceURI(final String s) { if(s == null) throw new IllegalArgumentException(); checkType(START_ELEMENT, END_ELEMENT, NAMESPACE); final byte[] uri = ns.staticURI(token(s)); return uri == null ? null : string(uri); } @Override public String getNamespaceURI(final int i) { checkType(START_ELEMENT, END_ELEMENT, NAMESPACE); return null; } @Override public String getPIData() { checkType(PROCESSING_INSTRUCTION); final byte[] val = node.string(); final int i = indexOf(val, ' '); return string(i == -1 ? EMPTY : substring(val, i + 1)); } @Override public String getPITarget() { checkType(PROCESSING_INSTRUCTION); final byte[] val = node.string(); final int i = indexOf(val, ' '); return string(i == -1 ? val : substring(val, 0, i)); } @Override public String getPrefix() { checkType(START_ELEMENT, END_ELEMENT); final QNm qn = node.qname(); return !qn.hasPrefix() ? null : string(qn.prefix()); } @Override public Object getProperty(final String s) { if(s == null) throw new IllegalArgumentException(); return PROPS.get(s); } @Override public String getText() { checkType(CHARACTERS, COMMENT); return string(node.string()); } @Override public char[] getTextCharacters() { return getText().toCharArray(); } @Override public int getTextCharacters(final int ss, final char[] ac, final int ts, final int l) { checkType(CHARACTERS, COMMENT); final String value = getText(); final int vl = value.length(); if(ss >= vl) return 0; int se = ss + l; if(se > vl) se = value.length(); value.getChars(ss, se, ac, ts); return se - ss; } @Override public int getTextLength() { checkType(CHARACTERS, COMMENT); return node.string().length; } @Override public int getTextStart() { checkType(CHARACTERS, COMMENT); return 0; } @Override public String getVersion() { return "1.0"; } @Override public boolean hasName() { return isType(START_ELEMENT, END_ELEMENT); } @Override public boolean hasNext() throws XMLStreamException { if(next) return true; next = true; atts = null; try { if(read != null) { if(read.hasNext()) { read.next(); } else { read = null; kind = END_DOCUMENT; return true; } } if(read == null) { final Item it = result.next(); if(it == null) return false; if(!(it instanceof ANode)) throw new XMLStreamException(); node = (ANode) it; read = it instanceof DBNode ? new DBNodeReader() : new FNodeReader(); } } catch(final QueryException ex) { throw new XMLStreamException(ex); } return node != null; } @Override public boolean hasText() { return isType(CHARACTERS, DTD, ENTITY_REFERENCE, COMMENT, SPACE); } @Override public boolean isAttributeSpecified(final int i) { checkType(START_ELEMENT, ATTRIBUTE); return true; } @Override public boolean isCharacters() { return isType(CHARACTERS); } @Override public boolean isEndElement() { return isType(END_ELEMENT); } @Override public boolean isStandalone() { return false; } @Override public boolean isStartElement() { return isType(START_ELEMENT); } @Override public boolean isWhiteSpace() { return isCharacters() && ws(node.string()); } @Override public int next() throws XMLStreamException { if(next && node == null || !next && !hasNext()) throw new NoSuchElementException(); next = false; // disallow top level attributes if(node.type == NodeType.ATT && read == null) throw new XMLStreamException(); return kind; } @Override public int nextTag() throws XMLStreamException { next(); while(kind == CHARACTERS && isWhiteSpace() || kind == CDATA && isWhiteSpace() || kind == SPACE || kind == PROCESSING_INSTRUCTION || kind == COMMENT) { next(); } checkType(START_ELEMENT, END_ELEMENT); return kind; } @Override public void require(final int t, final String uri, final String ln) throws XMLStreamException { checkType(t); if(uri != null && !uri.equals(getNamespaceURI())) { throw new XMLStreamException(); } if(ln != null && !ln.equals(getLocalName())) { throw new XMLStreamException(); } } @Override public boolean standaloneSet() { return false; } /** * Sets the current event type. */ void type() { switch(node.nodeType()) { case DOC: kind = START_DOCUMENT; return; case ATT: kind = ATTRIBUTE; return; case ELM: kind = START_ELEMENT; return; case COM: kind = COMMENT; return; case PI : kind = PROCESSING_INSTRUCTION; return; default: kind = CHARACTERS; } } /** * Tests the validity of the specified types. * @param valid input types */ private void checkType(final int... valid) { if(!isType(valid)) throw new IllegalStateException("Invalid Type: " + kind); } /** * Tests if one of the specified values matches the current kind. * @param valid input types * @return result of check */ private boolean isType(final int... valid) { for(final int v : valid) if(kind == v) return true; return false; } /** * Reader for {@link FNode} instances. */ abstract static class NodeReader { /** * Checks if the node reader can return more nodes. * @return result of check */ abstract boolean hasNext(); /** * Checks if the node reader can return more nodes. */ abstract void next(); } /** Reader for traversing {@link DBNode} instances. */ private final class DBNodeReader extends NodeReader { /** Node reference. */ private final DBNode dbnode; /** Data size. */ private final int s; /** Parent stack. */ private final IntList parent = new IntList(); /** Pre stack. */ private final IntList pre = new IntList(); /** Current pre value. */ private int p; /** Constructor. */ DBNodeReader() { dbnode = ((DBNode) node).copy(); node = dbnode; p = dbnode.pre; final int k = dbnode.data.kind(p); s = p + dbnode.data.size(p, k); finish(k, 0); } @Override boolean hasNext() { return p < s || pre.size() > 0; } @Override void next() { if(p == s) { endElem(); return; } final Data data = dbnode.data; final int k = data.kind(p); final int pa = data.parent(p, k); if(parent.size() > 0 && parent.peek() >= pa) { endElem(); return; } finish(k, pa); } /** * Processes the end of an element. */ private void endElem() { dbnode.set(pre.pop(), Data.ELEM); parent.pop(); kind = END_ELEMENT; } /** * Finishing step. * @param k node kind * @param pa parent reference */ private void finish(final int k, final int pa) { dbnode.set(p, k); if(k == Data.ELEM) { pre.push(p); parent.push(pa); } p += dbnode.data.attSize(p, k); type(); } } /** Reader for traversing {@link FNode} instances. */ private final class FNodeReader extends NodeReader { /** Axis iterator. */ private final ArrayList<AxisIter> iter = new ArrayList<AxisIter>(); /** Node stack. */ private final ArrayList<ANode> nodes = new ArrayList<ANode>(); /** Stack level. */ private int l; /** Constructor. */ FNodeReader() { iter.add(node.self()); hasNext(); } @Override boolean hasNext() { final ANode n = iter.get(l).next(); if(n != null) { while(l >= nodes.size()) nodes.add(null); nodes.set(l, n); node = n; type(); if(kind == START_ELEMENT) { while(l + 1 >= iter.size()) iter.add(null); iter.set(++l, n.children()); } } else { if(--l < 0) return false; node = nodes.get(l); kind = END_ELEMENT; } return true; } @Override void next() { } } /** Dummy location implementation. */ static final class LocationImpl implements Location { @Override public int getCharacterOffset() { return -1; } @Override public int getColumnNumber() { return -1; } @Override public int getLineNumber() { return -1; } @Override public String getPublicId() { return null; } @Override public String getSystemId() { return null; } } }