ch.rabanti.nanoxlsx4j.lowLevel.XmlDocument Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nanoxlsx4j Show documentation
Show all versions of nanoxlsx4j Show documentation
NanoXLSX4j is a small Java library to create and read XLSX files (Microsoft Excel 2007 or newer) in an
easy and native way. The library is originated form PicoXLSX4j and has basic support of reading spreadsheets
/*
* NanoXLSX4j is a small Java library to write and read XLSX (Microsoft Excel 2007 or newer) files in an easy and native way
* Copyright Raphael Stoeckli © 2024
* This library is licensed under the MIT License.
* You find a copy of the license in project folder or on: http://opensource.org/licenses/MIT
*/
package ch.rabanti.nanoxlsx4j.lowLevel;
import ch.rabanti.nanoxlsx4j.exceptions.IOException;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static javax.xml.stream.XMLStreamConstants.CHARACTERS;
import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
/**
* Class for simplified XML access without dealing with start and end elements or streams. It loads an XML file from a
* stream and processes it in a structured document, similar to the XmlDocument class in .NET (System.Xml)
*/
public class XmlDocument {
private static final String NO_SUCH_ELEMENT_MESSAGE = "The next element cannot be returned, since out of range";
private XmlNode documentElement;
/**
* Gets the root node of the XML document
*
* @return XML node (top level)
*/
public XmlNode getDocumentElement() {
return this.documentElement;
}
/**
* Loads an XML document from a stream
*
* @param stream Input Stream
* @throws IOException Throws IOException in case of an error, caught by the library
* @throws java.io.IOException Throws IOException in case of a stream error
*/
public void load(InputStream stream) throws IOException, java.io.IOException {
XMLStreamReader xr;
XMLInputFactory factory = XMLInputFactory.newFactory();
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
try {
xr = factory.createXMLStreamReader(stream);
while (xr.hasNext()) {
int nodeType = xr.next();
if (nodeType == START_ELEMENT) {
this.documentElement = XmlNode.loadXmlNode(xr);
}
}
}
catch (Exception ex) {
throw new IOException("The XML entry could not be read from the input stream. Please see the inner exception:", ex);
}
finally {
if (stream != null) {
stream.close();
}
}
}
/**
* Class representing an iterable list of XML nodes
*/
public static class XmlNodeList implements Iterable, Iterator {
private final ArrayList items = new ArrayList<>();
private int count = 0;
@Override
public boolean hasNext() {
return this.count < items.size();
}
@Override
public XmlNode next() {
if (!hasNext()) {
throw new NoSuchElementException(NO_SUCH_ELEMENT_MESSAGE);
}
return this.items.get(count++);
}
@Override
public Iterator iterator() {
return new Iterator<>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < items.size();
}
@Override
public XmlNode next() {
if (!hasNext()) {
throw new NoSuchElementException(NO_SUCH_ELEMENT_MESSAGE);
}
return items.get(index++);
}
};
}
@Override
public void forEach(Consumer super XmlNode> action) {
Objects.requireNonNull(action);
for (XmlNode node : this) {
action.accept(node);
}
}
/**
* Gets the size of XML node list
*
* @return Number of elements
*/
public int size() {
return items.size();
}
/**
* Adds an XML node to the node list
*
* @param node Item to add
*/
public void add(XmlNode node) {
this.items.add(node);
}
/**
* Adds a list of XML nodes to the node list
*
* @param list List of items
*/
public void addRange(List list) {
this.items.addAll(list);
}
/**
* Gets an XML node at the given index
*
* @param index Index of the node
* @return XML node
*/
public XmlNode get(int index) {
return this.items.get(index);
}
}
/**
* Class representing a single XML node with possible attributes and sub-nodes
*/
public static class XmlNode {
private final String name;
private String innerText;
private final XmlAttributeCollection attributes = new XmlAttributeCollection();
private final XmlNodeList nodeList = new XmlNodeList();
/**
* Gets the child nodes of this node
*
* @return List of XML nodes
*/
public XmlNodeList getChildNodes() {
return this.nodeList;
}
/**
* Gets the (tag) name of the XML node
*
* @return Name as string
*/
public String getName() {
return name;
}
/**
* Gets the inner plain text of the node, if available
*
* @return Text between the start and end tag of this XML node
*/
public String getInnerText() {
return innerText;
}
/**
* Gets whether child nodes are available in this node
*
* @return True if sub-nodes exist
*/
public boolean hasChildNodes() {
return nodeList.size() > 0;
}
/**
* Constructor with definition of the node (tag) name
*
* @param name Name of the node
*/
public XmlNode(String name) {
this.name = name;
}
/**
* Gets the XML attribute of the current node by its name
*
* @param targetName Name of the target attribute
* @return Attribute value as string or default value if not found (can be null)
*/
public String getAttribute(String targetName) {
return getAttribute(targetName, null);
}
/**
* Gets the XML attribute of the current node by its name
*
* @param targetName Name of the target attribute
* @param fallbackValue Optional fallback value if the attribute was not found
* @return Attribute value as string or default value if not found (can be null)
*/
public String getAttribute(String targetName, String fallbackValue) {
XmlAttribute attribute = this.attributes.getByName(targetName);
if (attribute == null) {
return fallbackValue;
}
return attribute.value;
}
/**
* Gets a list of sub-nodes with the defined name
*
* @param name (Tag) name of the XML node
* @param recursively If true, all levels are considered, otherwise only the current level (sub-nodes of current
* node)
* @return XmlNodeList object
*/
public XmlNodeList getElementsByTagName(String name, boolean recursively) {
XmlNodeList list = new XmlNodeList();
List result = this.getChildNodes().items.stream().filter(x -> x.getName().equals(name)).collect(Collectors.toList());
list.addRange(result);
if (recursively) {
for (XmlNode node : this.getChildNodes()) {
list.addRange(node.getElementsByTagName(name, true).items);
}
}
return list;
}
/**
* Static method to resolve attributes and sub-nodes recursively
*
* @param reader XML stream reader (reference)
* @return Resolved XML node with possible attributes and sub-nodes
* @throws XMLStreamException Throws IOException in case of a stream error
*/
public static XmlNode loadXmlNode(XMLStreamReader reader) throws XMLStreamException {
String elementName = reader.getName().getLocalPart();
XmlNode node = new XmlNode(elementName);
int attributeCount = reader.getAttributeCount();
for (int i = 0; i < attributeCount; i++) {
String attributeName = reader.getAttributeName(i).getLocalPart();
String attributeValue = reader.getAttributeValue(i);
XmlAttribute attribute = new XmlAttribute(attributeName, attributeValue);
node.attributes.add(attribute);
}
StringBuilder innerText = new StringBuilder();
while (reader.hasNext()) {
int nodeType = reader.next();
if (nodeType == END_ELEMENT && reader.getName().getLocalPart().equals(elementName)) {
if (innerText.length() > 0) {
node.innerText = innerText.toString();
}
return node;
}
else if (nodeType == START_ELEMENT) {
XmlNode childNode = loadXmlNode(reader);
node.nodeList.add(childNode);
}
else if (nodeType == CHARACTERS) {
innerText.append(reader.getText());
}
}
return node;
}
}
/**
* Class representing an XML attribute
*/
public static class XmlAttribute {
private final String name;
private final String value;
/**
* Constructor with full declaration
*
* @param name Attribute name
* @param value Attribute value
*/
public XmlAttribute(String name, String value) {
this.name = name;
this.value = value;
}
}
/**
* Class representing an iterable list of XML attributes
*/
public static class XmlAttributeCollection implements Iterable, Iterator {
private final ArrayList items = new ArrayList<>();
private int count = 0;
@Override
public boolean hasNext() {
return this.count < items.size();
}
@Override
public XmlAttribute next() {
if (!hasNext()) {
throw new NoSuchElementException(NO_SUCH_ELEMENT_MESSAGE);
}
return this.items.get(count++);
}
@Override
public Iterator iterator() {
return new Iterator<>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < items.size();
}
@Override
public XmlAttribute next() {
if (!hasNext()) {
throw new NoSuchElementException(NO_SUCH_ELEMENT_MESSAGE);
}
return items.get(index++);
}
};
}
@Override
public void forEach(Consumer super XmlAttribute> action) {
Objects.requireNonNull(action);
for (XmlAttribute attribute : this) {
action.accept(attribute);
}
}
/**
* Ads an XML attribute to the node list
*
* @param attribute Item to add
*/
public void add(XmlAttribute attribute) {
this.items.add(attribute);
}
/**
* Finds the attribute with the defined name (can be null)
*
* @param name Name of the attribute
* @return XM L attribute or null if the attribute was not found
*/
public XmlAttribute getByName(String name) {
return this.items.stream().filter(x -> x.name.equals(name)).findFirst().orElse(null);
}
}
}