XmlUtil.java
/**
* Copyright (c) 2016, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* SPDX-License-Identifier: MPL-2.0
*/
package com.powsybl.commons.xml;
import com.google.common.base.Suppliers;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.exceptions.UncheckedXmlStreamException;
import com.powsybl.commons.io.TreeDataReader;
import javanet.staxutils.IndentingXMLStreamWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.stream.*;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.function.Supplier;
/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
*/
public final class XmlUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(XmlUtil.class);
private static final Supplier<XMLOutputFactory> XML_OUTPUT_FACTORY_SUPPLIER = Suppliers.memoize(XMLOutputFactory::newFactory);
private XmlUtil() {
}
public static void readUntilStartElement(String path, XMLStreamReader reader, TreeDataReader.ChildNodeReader handler) throws XMLStreamException {
Objects.requireNonNull(path);
String[] elements = path.split("/");
readUntilStartElement(elements, reader, handler);
}
public static void readUntilStartElement(String[] elements, XMLStreamReader reader, TreeDataReader.ChildNodeReader handler) throws XMLStreamException {
Objects.requireNonNull(elements);
if (elements.length == 0) {
throw new PowsyblException("Empty element list");
}
StringBuilder currentPath = new StringBuilder();
for (int i = 1; i < elements.length; ++i) {
currentPath.append("/").append(elements[i]);
if (!readUntilStartElement(elements[i], elements[i - 1], reader)) {
throw new PowsyblException("Unable to find " + currentPath.toString() + ": parent element " + elements[i - 1] + " has been closed");
}
}
if (handler != null) {
handler.onStartNode(elements[elements.length - 1]);
}
}
private static boolean readUntilStartElement(String startElement, String endElement, XMLStreamReader reader) throws XMLStreamException {
int event;
while ((event = reader.next()) != XMLStreamConstants.END_DOCUMENT) {
switch (event) {
case XMLStreamConstants.START_ELEMENT -> {
if (reader.getLocalName().equals(startElement)) {
return true;
} else {
// Skip the current element
skipSubElements(reader);
}
}
case XMLStreamConstants.END_ELEMENT -> {
if (reader.getLocalName().equals(endElement)) {
return false;
}
}
default -> {
// Do nothing
}
}
}
throw new PowsyblException("Unable to find " + startElement + ": end of document has been reached");
}
public static void skipSubElements(XMLStreamReader reader) {
readSubElements(reader, elementName -> skipSubElements(reader));
}
public static void readSubElements(XMLStreamReader reader, TreeDataReader.ChildNodeReader childNodeReader) {
Objects.requireNonNull(reader);
Objects.requireNonNull(childNodeReader);
try {
int event;
while ((event = reader.next()) != XMLStreamConstants.END_ELEMENT) {
if (event == XMLStreamConstants.START_ELEMENT) {
childNodeReader.onStartNode(reader.getLocalName());
}
}
} catch (XMLStreamException e) {
throw new UncheckedXmlStreamException(e);
}
}
public static String readText(XMLStreamReader reader) throws XMLStreamException {
String text = reader.getElementText();
return text.isEmpty() ? null : text;
}
public static Integer readIntegerAttribute(XMLStreamReader reader, String name) {
String attributeValue = reader.getAttributeValue(null, name);
return attributeValue != null ? Integer.valueOf(attributeValue) : null;
}
public static OptionalInt readOptionalIntegerAttribute(XMLStreamReader reader, String name) {
String attributeValue = reader.getAttributeValue(null, name);
return attributeValue != null ? OptionalInt.of(Integer.parseInt(attributeValue)) : OptionalInt.empty();
}
public static int readIntAttribute(XMLStreamReader reader, String name, int defaultValue) {
String attributeValue = reader.getAttributeValue(null, name);
return attributeValue != null ? Integer.parseInt(attributeValue) : defaultValue;
}
public static Boolean readBooleanAttribute(XMLStreamReader reader, String name) {
String attributeValue = reader.getAttributeValue(null, name);
return attributeValue != null ? Boolean.valueOf(attributeValue) : null;
}
public static boolean readBooleanAttribute(XMLStreamReader reader, String name, boolean defaultValue) {
String attributeValue = reader.getAttributeValue(null, name);
return attributeValue != null ? Boolean.parseBoolean(attributeValue) : defaultValue;
}
public static double readDoubleAttribute(XMLStreamReader reader, String name, double defaultValue) {
String attributeValue = reader.getAttributeValue(null, name);
return attributeValue != null ? Double.parseDouble(attributeValue) : defaultValue;
}
public static OptionalDouble readOptionalDoubleAttribute(XMLStreamReader reader, String name) {
String attributeValue = reader.getAttributeValue(null, name);
return attributeValue != null ? OptionalDouble.of(Double.parseDouble(attributeValue)) : OptionalDouble.empty();
}
public static float readFloatAttribute(XMLStreamReader reader, String name, float defaultValue) {
String attributeValue = reader.getAttributeValue(null, name);
return attributeValue != null ? Float.parseFloat(attributeValue) : defaultValue;
}
public static XMLStreamWriter initializeWriter(boolean indent, String indentString, OutputStream os) throws XMLStreamException {
XMLStreamWriter writer = XML_OUTPUT_FACTORY_SUPPLIER.get().createXMLStreamWriter(os, StandardCharsets.UTF_8.toString());
return initializeWriter(indent, indentString, writer);
}
public static XMLStreamWriter initializeWriter(boolean indent, String indentString, OutputStream os, Charset charset) throws XMLStreamException {
XMLStreamWriter writer = XML_OUTPUT_FACTORY_SUPPLIER.get().createXMLStreamWriter(os, charset.name());
return initializeWriter(indent, indentString, writer, charset);
}
public static XMLStreamWriter initializeWriter(boolean indent, String indentString, Writer writer) throws XMLStreamException {
XMLStreamWriter xmlWriter = XML_OUTPUT_FACTORY_SUPPLIER.get().createXMLStreamWriter(writer);
return initializeWriter(indent, indentString, xmlWriter);
}
private static XMLStreamWriter initializeWriter(boolean indent, String indentString, XMLStreamWriter initialXmlWriter) throws XMLStreamException {
return initializeWriter(indent, indentString, initialXmlWriter, StandardCharsets.UTF_8);
}
private static XMLStreamWriter initializeWriter(boolean indent, String indentString, XMLStreamWriter initialXmlWriter, Charset charset) throws XMLStreamException {
XMLStreamWriter xmlWriter;
if (indent) {
IndentingXMLStreamWriter indentingWriter = new IndentingXMLStreamWriter(initialXmlWriter);
indentingWriter.setIndent(indentString);
xmlWriter = indentingWriter;
} else {
xmlWriter = initialXmlWriter;
}
xmlWriter.writeStartDocument(charset.name(), "1.0");
return xmlWriter;
}
public static void gcXmlInputFactory(XMLInputFactory xmlInputFactory) {
// Workaround: Manually force XMLInputFactory and XmlStreamReader to clear the reference to the last inputstream.
// jdk xerces XmlInputFactory keeps a ref to the last created XmlStreamReader (so that the factory
// can optionally reuse it. But the XmlStreamReader keeps a ref to it's inputstream. There is
// no public API in XmlStreamReader to clear the previous input stream, close doesn't do it).
try (InputStream is = new ByteArrayInputStream(new byte[] {})) {
XMLStreamReader xmlsr = xmlInputFactory.createXMLStreamReader(is);
xmlsr.close();
} catch (XMLStreamException | IOException e) {
LOGGER.error(e.toString(), e);
}
}
public static void readEndElementOrThrow(XMLStreamReader reader) throws XMLStreamException {
if (reader.next() != XMLStreamConstants.END_ELEMENT) {
throw new PowsyblException("XMLStreamConstants.END_ELEMENT expected but found another event (eventType = '" + reader.getEventType() + "')");
}
}
}