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

com.davidbracewell.io.structured.xml.XMLReader Maven / Gradle / Ivy

There is a newer version: 0.5
Show newest version
/*
 * (c) 2005 David B. Bracewell
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 com.davidbracewell.io.structured.xml;

import com.davidbracewell.conversion.Val;
import com.davidbracewell.io.Resources;
import com.davidbracewell.io.resource.Resource;
import com.davidbracewell.io.structured.ElementType;
import com.davidbracewell.io.structured.StructuredReader;
import com.davidbracewell.string.StringUtils;
import com.davidbracewell.tuple.Tuple2;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import lombok.NonNull;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import java.io.IOException;
import java.io.Reader;
import java.util.NoSuchElementException;
import java.util.Stack;

/**
 * An implementation of a StructuredReader that reads xml.
 *
 * @author David B. Bracewell
 */
public class XMLReader extends StructuredReader {

  private final String documentTag;
  private final XMLEventReader reader;
  private final Stack> stack;
  private ElementType documentType;
  private String readerText;

  /**
   * Creates an XMLReader
   *
   * @param resource The resource to read
   * @throws IOException Something went wrong reading
   */
  public XMLReader(Resource resource) throws IOException {
    this("document", resource);
  }

  /**
   * Creates an XMLReader
   *
   * @param resource The resource to read
   * @throws IOException Something went wrong reading
   */
  public XMLReader(Reader resource) throws IOException {
    this("document", Resources.fromReader(resource));
  }

  /**
   * Creates an XMLReader
   *
   * @param documentTag The document tag
   * @param resource    The resource to read
   * @throws IOException Something went wrong reading
   */
  public XMLReader(String documentTag, @NonNull Resource resource) throws IOException {
    try {
      Preconditions.checkArgument(!Strings.isNullOrEmpty(documentTag));
      this.documentTag = documentTag;
      this.reader = XMLInputFactory.newFactory().createXMLEventReader(resource.inputStream(), "UTF-8");
      this.stack = new Stack<>();
    } catch (Exception e) {
      throw new IOException(e);
    }
  }

  private XMLEvent next() throws IOException {
    peek(); // move to the next real event
    try {
      return reader.nextEvent();
    } catch (XMLStreamException e) {
      throw new IOException(e);
    } catch (NoSuchElementException e) {
      return null;
    }
  }


  private XMLReader validate(XMLEvent event, ElementType expectedElement, Tuple2 expectedTopOfStack) throws IOException {
    if (event == null) {
      throw new IOException("Parsing error event was null");
    }
    ElementType element = xmlEventToStructuredElement(event);

    if (expectedElement != null && element != expectedElement) {
      throw new IOException("Parsing error: expected (" + expectedElement + ") found (" + element + ")");
    }

    if (expectedTopOfStack != null && (stack.isEmpty() || !stack.peek().equals(expectedTopOfStack))) {
      throw new IOException("Parsing error: expected (" + expectedTopOfStack + ") found (" + stack.peek() + ")");
    }

    return this;
  }

  @Override
  public XMLReader beginDocument() throws IOException {
    try {
      XMLEvent event = next();
      if (event == null) {
        throw new IOException();
      }
      if (event.isStartDocument()) {
        event = reader.nextTag();
      }
      if (!event.isStartElement() || !((StartElement) event).getName().toString().equals(documentTag)) {
        throw new IOException("document tag does not match : expected (<" + documentTag + ">)");
      }
      documentType = xmlEventToStructuredElement(event);
      stack.push(Tuple2.of(documentTag, ElementType.BEGIN_DOCUMENT));
    } catch (XMLStreamException e) {
      throw new IOException(e);
    }
    return this;
  }

  @Override
  public void endDocument() throws IOException {
    XMLEvent event = next();
    validate(event, ElementType.END_DOCUMENT, Tuple2.of(documentTag, ElementType.BEGIN_DOCUMENT));
  }

  private ElementType xmlEventToStructuredElement(XMLEvent event) {
    if (event == null) {
      return ElementType.END_DOCUMENT;
    }

    if (event.isStartDocument()) {
      return ElementType.BEGIN_DOCUMENT;
    }

    if (event.isEndDocument()) {
      return ElementType.END_DOCUMENT;
    }


    if (event.isStartElement()) {
      StartElement element = (StartElement) event;

      if (element.getName().getLocalPart().equals(documentTag)) {
        return ElementType.BEGIN_DOCUMENT;
      }

      QName elementType = new QName("", "type");

      if (element.getAttributeByName(elementType) == null) {
        return ElementType.NAME;
      }

      String typeName = element.getAttributeByName(elementType).getValue();
      if (typeName.equalsIgnoreCase("array") || element.getName().toString().equals("array")) {
        return ElementType.BEGIN_ARRAY;
      }
      if (typeName.equalsIgnoreCase("object") || element.getName().toString().equals("object")) {
        return ElementType.BEGIN_OBJECT;
      }
      if (typeName.equalsIgnoreCase("value") || element.getName().toString().equals("value")) {
        return ElementType.VALUE;
      }

      return ElementType.NAME;
    }

    if (event.isEndElement()) {
      EndElement element = (EndElement) event;
      String elementName = element.getName().getLocalPart();
      Tuple2 top = stack.peek();

      if (top.equals(Tuple2.of(elementName, ElementType.BEGIN_DOCUMENT))) {
        return ElementType.END_DOCUMENT;
      }
      if (top.equals(Tuple2.of(elementName, ElementType.BEGIN_ARRAY))) {
        return ElementType.END_ARRAY;
      }
      if (top.equals(Tuple2.of(elementName, ElementType.BEGIN_OBJECT))) {
        return ElementType.END_OBJECT;
      }

      return ElementType.END_KEY_VALUE;
    }

    return ElementType.OTHER;
  }

  public ElementType peek() throws IOException {
    if (!StringUtils.isNullOrBlank(readerText)) {
      return ElementType.END_KEY_VALUE;
    }
    while (true) {
      try {
        XMLEvent event = reader.peek();
        ElementType element = xmlEventToStructuredElement(event);
        if (element != ElementType.OTHER) {
          return element;
        }
        reader.nextEvent();
      } catch (XMLStreamException e) {
        throw new IOException(e);
      }
    }
  }


  @Override
  public String beginObject() throws IOException {
    XMLEvent event = next();
    validate(event, ElementType.BEGIN_OBJECT, null);
    String name = ((StartElement) event).getName().getLocalPart();
    stack.push(Tuple2.of(name, ElementType.BEGIN_OBJECT));
    return name;
  }

  @Override
  public StructuredReader endObject() throws IOException {
    XMLEvent event = next();
    validate(event, ElementType.END_OBJECT, Tuple2.of(((EndElement) event).getName().getLocalPart(), ElementType.BEGIN_OBJECT));
    stack.pop();
    return this;
  }

  @Override
  public ElementType getDocumentType() {
    return documentType;
  }

  @Override
  public String beginArray() throws IOException {
    XMLEvent event = next();
    validate(event, ElementType.BEGIN_ARRAY, null);
    String name = ((StartElement) event).getName().getLocalPart();
    stack.push(Tuple2.of(name, ElementType.BEGIN_ARRAY));
    return name;
  }

  @Override
  public StructuredReader endArray() throws IOException {
    XMLEvent event = next();
    validate(event, ElementType.END_ARRAY, Tuple2.of(((EndElement) event).getName().getLocalPart(), ElementType.BEGIN_ARRAY));
    stack.pop();
    return this;
  }

  @Override
  public boolean hasNext() throws IOException {
    return reader.hasNext();
  }

  private String handleNullables(String text) {
    if (text == null || text.equals("null")) {
      return null;
    }
    return text;
  }

  @Override
  public Tuple2 nextKeyValue() throws IOException {
    XMLEvent event = next();
    validate(event, ElementType.NAME, null);
    String key = ((StartElement) event).getName().getLocalPart();
    try {
      this.readerText = reader.getElementText();
    } catch (XMLStreamException e) {

    }
    return Tuple2.of(key, nextValue());
  }

  @Override
  public  Tuple2 nextKeyValue(Class clazz) throws IOException {
    XMLEvent event = next();
    validate(event, ElementType.NAME, null);
    String key = ((StartElement) event).getName().getLocalPart();
    return Tuple2.of(key, nextValue(clazz));
  }

  public ElementType skip() throws IOException {
    ElementType element = peek();
    switch (element) {
      case BEGIN_ARRAY:
        beginArray();
        while (peek() != ElementType.END_ARRAY) {
          skip();
        }
        endArray();
        break;
      case BEGIN_OBJECT:
        beginObject();
        while (peek() != ElementType.END_OBJECT) {
          skip();
        }
        endObject();
        break;
      default:
        next();
    }

    return element;
  }

  @Override
  protected Val nextSimpleValue() throws IOException {
    Val v;
    if (!StringUtils.isNullOrBlank(readerText)) {
      v = Val.of(readerText);
      readerText = null;
    } else if (peek() == ElementType.VALUE) {
      next();
      try {
        return Val.of(reader.getElementText());
      } catch (XMLStreamException e) {
        throw new IOException(e);
      }
    } else {
      throw new IOException("Error");
    }
    return v;
  }

  @Override
  public void close() throws IOException {
    try {
      reader.close();
    } catch (XMLStreamException e) {
      throw new IOException(e);
    }
  }

}// END OF XMLReader




© 2015 - 2025 Weber Informatics LLC | Privacy Policy