All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tuwien.auto.calimero.xml.DefaultXmlReader Maven / Gradle / Ivy

The newest version!
/*
    Calimero 2 - A library for KNX network access
    Copyright (c) 2006, 2023 B. Malinowsky

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Linking this library statically or dynamically with other modules is
    making a combined work based on this library. Thus, the terms and
    conditions of the GNU General Public License cover the whole
    combination.

    As a special exception, the copyright holders of this library give you
    permission to link this library with independent modules to produce an
    executable, regardless of the license terms of these independent
    modules, and to copy and distribute the resulting executable under terms
    of your choice, provided that you also meet, for each linked independent
    module, the terms and conditions of the license of that module. An
    independent module is a module which is not derived from or based on
    this library. If you modify this library, you may extend this exception
    to your version of the library, but you are not obligated to do so. If
    you do not wish to do so, delete this exception statement from your
    version.
*/

package tuwien.auto.calimero.xml;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

/**
 * Default XML reader implementation of the XmlReader interface.
 * 

* Does not add any feature not already documented in the implemented interface. No support for XML namespaces and * processing instructions. This reader is not thread safe. * * @author B. Malinowsky */ class DefaultXmlReader implements XmlReader { private final Map config = new HashMap<>(); private final Reader r; private final boolean close; private String elemName; private List attributeName; private List attributeValue; private String elemText; private boolean emptyTag; private String currentNamespace; private final Stack openElems = new Stack<>(); private int event; private int line; // variables introduced for StaX API private boolean standalone; private String version; private String encoding; // TODO set variables private int textStart; private int textLength; /** * Creates a new XML reader with input {@code r}. *

* If necessary, the {@link Reader} should already be buffered or wrapped with a buffered reader, e.g., when reading * from a file on disk. * * @param r a {@link Reader} for input * @param closeReader close {@code r} when XML reader is closed */ DefaultXmlReader(final Reader r, final boolean closeReader) { this.r = r; close = closeReader; event = XmlReader.START_DOCUMENT; line = 0; } private void setElement(final String name) { elemName = name; attributeName = new ArrayList<>(); attributeValue = new ArrayList<>(); elemText = null; emptyTag = false; } private int read() throws KNXMLException { while (canRead()) { // init line counter on first read if (line == 0) line = 1; if (emptyTag) { emptyTag = false; event = XmlReader.END_ELEMENT; return event; } read('<'); String str = read('>'); // if no comment and no closing tag if (str.isEmpty() || skipComment(str) || skipInstruction(str)) continue; final StringBuilder buf = new StringBuilder(); if (readCDATASection(str, buf)) { setElement(openElems.peek()); elemText = buf.toString(); event = XmlReader.CHARACTERS; return event; } str = str.trim(); // extract element name String name = splitOnSpace(str); if (name.charAt(name.length() - 1) == '/') name = name.substring(0, name.length() - 1); if (name.charAt(0) == '/') { if (!name.substring(1).equals(openElems.peek())) throw new KNXMLException("element end tag does not match start tag", name.substring(1), line); setElement(openElems.pop()); event = XmlReader.END_ELEMENT; return event; } setElement(name); extractAttributes(str.substring(name.length())); if (!emptyTag) openElems.push(name); event = XmlReader.START_ELEMENT; return event; } if (!openElems.empty()) throw new KNXMLException("end of XML input with elements left open"); event = XmlReader.END_DOCUMENT; return event; } private void complete(final String name) throws KNXMLException { if (emptyTag) return; final int index = openElems.lastIndexOf(name); if (index == -1) throw new KNXMLException("no matching element open tag: " + name, this); String end = null; final StringBuilder content = new StringBuilder(50); while (canRead()) { // read text content final String s = read('<'); final boolean current = openElems.peek().equals(name); // if character data is for current element, append it if (current && s.length() > 0) content.append(References.replace(s, false)); // read a possible end tag end = read('>'); if (skipComment(end)) ; else if (current && readCDATASection(end, content)) ; else if (end.length() > 0 && end.charAt(0) == '/') { // actually, no white space is allowed between '/' and tag name // but there might be some between end of name and '>' final String tag = end.substring(1).trim(); // got end tag? if (!tag.equals(openElems.peek())) throw new KNXMLException("element end tag does not match start tag", tag, line); openElems.pop(); if (tag.equals(name)) { elemText = content.toString(); event = XmlReader.END_ELEMENT; return; } } else if (end.length() > 0) // don't push empty element tags if (end.charAt(end.length() - 1) != '/') { final String tag = splitOnSpace(end); if (tag.length() > 0) openElems.push(tag); } } throw new KNXMLException("end of XML input with elements left open", end, line); } @Override public void close() { try { if (close) r.close(); } catch (final IOException ignore) {} } private boolean canRead() { try { return r.ready(); } catch (final IOException e) { return false; } } private String read(final char delimiter) throws KNXMLException { final StringBuilder buf = new StringBuilder(50); try { boolean cr = false; for (int c = r.read(); c != -1 && c != delimiter; c = r.read()) { if (c == '\n') ++line; else if (cr) { ++line; buf.append('\n'); } cr = c == '\r'; if (!cr) buf.append((char) c); } } catch (final IOException e) { throw new KNXMLException(e.getMessage(), buf.toString(), line); } return buf.toString(); } // adds all available attributes to current element private void extractAttributes(final String attributes) { String s = attributes.trim(); if (s.length() > 0 && s.charAt(s.length() - 1) == '/') emptyTag = true; while (s.length() != 0) { final int equal = s.indexOf('='); if (equal == -1 || equal == s.length() - 1) break; // cut off attribute name final String att = s.substring(0, equal).trim(); s = s.substring(equal + 1).trim(); if (s.isEmpty()) break; final boolean quote = s.charAt(0) == '\'' || s.charAt(0) == '\"'; if (att.length() > 0 && quote) { String value = s.substring(1); final int end = value.indexOf(s.charAt(0)); if (end >= 0) value = value.substring(0, end); attributeName.add(att); attributeValue.add(References.replace(value, false)); if ("xmlns".equals(att)) { currentNamespace = value; } } final int i = s.indexOf(quote ? s.charAt(0) : ' ', 1); s = i == -1 ? "" : s.substring(i + 1); } } private boolean readCDATASection(final String s, final StringBuilder buf) throws KNXMLException { if (!s.startsWith("![CDATA[")) return false; buf.append(s.substring(8)); String cdata = s; while (!cdata.endsWith("]]")) { buf.append('>'); cdata = read('>'); buf.append(cdata); } // trim CDEnd ('>' is not included) buf.delete(buf.length() - 2, buf.length()); return true; } // checks if '<' marks begin of a comment, and if so skips over it private boolean skipComment(final String s) throws KNXMLException { if (s.startsWith("!--")) { String comment = s; while (canRead() && !comment.endsWith("--")) comment = read('>'); return true; } return false; } private boolean skipInstruction(final String tag) { // is this a processing instruction if (tag.charAt(0) == '?' && tag.charAt(tag.length() - 1) == '?') { // check for decl section // TODO detection is already implemented in entity resolver, use that int idx = tag.indexOf("version"); if (idx != -1) { idx = tag.indexOf("=", idx); version = tag.substring(idx + 2, tag.indexOf("\"", idx + 2)); } idx = tag.indexOf("encoding"); if (idx != -1) { idx = tag.indexOf("=", idx); encoding = tag.substring(idx + 2, tag.indexOf("\"", idx + 2)); } return true; } return false; } private static String splitOnSpace(final String s) { for (int i = 0; i < s.length(); ++i) if (Character.isSpaceChar(s.charAt(i))) return s.substring(0, i); return s; } @Override public Object getProperty(final String name) throws IllegalArgumentException { return config.get(name); } @Override public int next() { return read(); } @Override public void require(final int type, final String namespaceURI, final String localName) {} @Override public String getElementText() { complete(elemName); return elemText == null ? "" : elemText; } @Override public int nextTag() { return read(); } @Override public boolean hasNext() { return event != XmlReader.END_DOCUMENT; } @Override public String getNamespaceURI(final String prefix) { return null; } @Override public boolean isWhiteSpace() { return false; } @Override public String getAttributeValue(final String namespaceURI, final String localName) { inStartEvent(); final int i = attributeName.indexOf(localName); if (i != -1) return attributeValue.get(i); return null; } private void inStartEvent() { if (event != XmlReader.START_ELEMENT) throw new IllegalStateException("not at XML start element"); } @Override public int getAttributeCount() { inStartEvent(); return attributeName.size(); } @Override public String getAttributeNamespace(final int index) { inStartEvent(); return null; } @Override public String getAttributeLocalName(final int index) { inStartEvent(); return attributeName.get(index); } @Override public String getAttributePrefix(final int index) { inStartEvent(); return null; } @Override public String getAttributeType(final int index) { inStartEvent(); return "CDATA"; } @Override public String getAttributeValue(final int index) { inStartEvent(); return attributeValue.get(index); } @Override public boolean isAttributeSpecified(final int index) { inStartEvent(); return attributeName.size() > index; } @Override public int getNamespaceCount() { return 0; } @Override public String getNamespacePrefix(final int index) { return null; } @Override public String getNamespaceURI(final int index) { return null; } @Override public int getEventType() { return event; } @Override public String getText() { return new String(getTextCharacters()); } @Override public char[] getTextCharacters() { complete(elemName); return elemText.toCharArray(); } @Override public int getTextStart() { return textStart; } @Override public int getTextLength() { return textLength; } @Override public String getEncoding() { if (r instanceof final InputStreamReader isr) return isr.getEncoding(); return encoding; } @Override public StreamLocation getLocation() { return new StreamLocation(line); } @Override public String getLocalName() { if (event != XmlReader.START_ELEMENT && event != XmlReader.END_ELEMENT && event != XmlReader.ENTITY_REFERENCE) throw new IllegalStateException("no XML start/end element or entity reference"); return elemName; } @Override public String getNamespaceURI() { return currentNamespace; } @Override public String getPrefix() { return null; } @Override public String getVersion() { return version; } @Override public boolean isStandalone() { return standalone; } @Override public boolean standaloneSet() { return config.containsKey("standaloneSet"); } @Override public String getCharacterEncodingScheme() { return encoding; } @Override public String getPITarget() { return null; } @Override public String getPIData() { return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy