gov.nist.secauto.metaschema.model.common.util.XmlEventUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of metaschema-model-common Show documentation
Show all versions of metaschema-model-common Show documentation
The core API for working with Metaschema-based models.
/*
* Portions of this software was developed by employees of the National Institute
* of Standards and Technology (NIST), an agency of the Federal Government and is
* being made available as a public service. Pursuant to title 17 United States
* Code Section 105, works of NIST employees are not subject to copyright
* protection in the United States. This software may be subject to foreign
* copyright. Permission in the United States and in foreign countries, to the
* extent that NIST may hold copyright, to use, copy, modify, create derivative
* works, and distribute this software and its documentation without fee is hereby
* granted on a non-exclusive basis, provided that this notice and disclaimer
* of warranty appears in all copies.
*
* THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
* EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
* THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
* INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
* SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT
* SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
* INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
* OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
* CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
* PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
* OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
*/
package gov.nist.secauto.metaschema.model.common.util;
import org.codehaus.stax2.XMLEventReader2;
import org.codehaus.stax2.XMLStreamReader2;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
public final class XmlEventUtil { // NOPMD this is a set of utility methods
// private static final Logger LOGGER = LogManager.getLogger(XmlEventUtil.class);
private static final Pattern WHITESPACE_ONLY = Pattern.compile("^\\s+$");
private static final Map EVENT_NAME_MAP = new HashMap<>(); // NOPMD - this value is immutable
static {
EVENT_NAME_MAP.put(XMLStreamConstants.START_ELEMENT, "START_ELEMENT");
EVENT_NAME_MAP.put(XMLStreamConstants.END_ELEMENT, "END_ELEMENT");
EVENT_NAME_MAP.put(XMLStreamConstants.PROCESSING_INSTRUCTION, "PROCESSING_INSTRUCTION");
EVENT_NAME_MAP.put(XMLStreamConstants.CHARACTERS, "CHARACTERS");
EVENT_NAME_MAP.put(XMLStreamConstants.COMMENT, "COMMENT");
EVENT_NAME_MAP.put(XMLStreamConstants.SPACE, "SPACE");
EVENT_NAME_MAP.put(XMLStreamConstants.START_DOCUMENT, "START_DOCUMENT");
EVENT_NAME_MAP.put(XMLStreamConstants.END_DOCUMENT, "END_DOCUMENT");
EVENT_NAME_MAP.put(XMLStreamConstants.ENTITY_REFERENCE, "ENTITY_REFERENCE");
EVENT_NAME_MAP.put(XMLStreamConstants.ATTRIBUTE, "ATTRIBUTE");
EVENT_NAME_MAP.put(XMLStreamConstants.DTD, "DTD");
EVENT_NAME_MAP.put(XMLStreamConstants.CDATA, "CDATA");
EVENT_NAME_MAP.put(XMLStreamConstants.NAMESPACE, "NAMESPACE");
EVENT_NAME_MAP.put(XMLStreamConstants.NOTATION_DECLARATION, "NOTATION_DECLARATION");
EVENT_NAME_MAP.put(XMLStreamConstants.ENTITY_DECLARATION, "ENTITY_DECLARATION");
}
private XmlEventUtil() {
// disable construction
}
@SuppressWarnings("null")
@NotNull
private static Object escape(@NotNull String data) {
return data.chars().mapToObj(c -> (char) c).map(c -> escape(c)).collect(Collectors.joining());
}
@SuppressWarnings("null")
@NotNull
private static String escape(char ch) {
String retval;
switch (ch) {
case '\n':
retval = "\\n";
break;
case '\r':
retval = "\\r";
break;
default:
retval = String.valueOf(ch);
break;
}
return retval;
}
/**
* Generate a message suitable for logging that describes the provided {@link XMLEvent}.
*
* @param xmlEvent
* the event to generate the message for
* @return the message
*/
@NotNull
public static CharSequence toString(XMLEvent xmlEvent) {
CharSequence retval;
if (xmlEvent == null) {
retval = "EOF";
} else {
@SuppressWarnings("null")
@NotNull
StringBuilder builder = new StringBuilder()
.append(toEventName(xmlEvent));
QName name = toQName(xmlEvent);
if (name != null) {
builder.append(": ").append(name.toString());
}
if (xmlEvent.isCharacters()) {
String text = xmlEvent.asCharacters().getData();
if (text != null) {
builder.append(" '").append(escape(text)).append('\'');
}
}
Location location = toLocation(xmlEvent);
if (location != null) {
builder.append(" at ").append(toString(location));
}
retval = builder;
}
return retval;
}
/**
* Generates a message for the provided {@link Location}.
*
* @param location
* the location to generate the message for
* @return the message
*/
@SuppressWarnings("null")
@NotNull
public static CharSequence toString(@NotNull Location location) {
return new StringBuilder()
.append(location.getLineNumber())
.append(':')
.append(location.getColumnNumber());
}
/**
* Generates a string containing the current event and location of the stream reader.
*
* @param reader
* the stream reader
* @return the generated string
*/
@NotNull
public static CharSequence toString(@NotNull XMLStreamReader2 reader) { // NO_UCD (unused code)
int type = reader.getEventType();
@SuppressWarnings("null")
@NotNull
StringBuilder builder = new StringBuilder().append(toEventName(type));
QName name = reader.getName();
if (name != null) {
builder.append(": ").append(name.toString());
}
if (XMLEvent.CHARACTERS == type) {
String text = reader.getText();
if (text != null) {
builder.append(" '").append(escape(text)).append('\'');
}
}
Location location = reader.getLocation();
if (location != null) {
builder.append(" at ").append(toString(location));
}
return builder;
}
/**
* Retrieve the resource location of {@code event}.
*
* @param event
* the event to identify the location for
* @return the location or {@code null} if the location is unknown
*/
public static Location toLocation(@NotNull XMLEvent event) {
Location retval = null;
if (event.isStartElement()) {
StartElement start = event.asStartElement();
retval = start.getLocation();
} else if (event.isEndElement()) {
EndElement end = event.asEndElement();
retval = end.getLocation();
} else if (event.isCharacters()) {
Characters characters = event.asCharacters();
retval = characters.getLocation();
}
return retval;
}
/**
* Retrieve the name of the node associated with {@code event}.
*
* @param event
* the event to get the {@link QName} for
* @return the name of the node
*/
public static QName toQName(@NotNull XMLEvent event) {
QName retval = null;
if (event.isStartElement()) {
StartElement start = event.asStartElement();
retval = start.getName();
} else if (event.isEndElement()) {
EndElement end = event.asEndElement();
retval = end.getName();
}
return retval;
}
/**
* Get the event name of the {@code event}.
*
* @param event
* the event to get the event name for
* @return the event name
*/
public static String toEventName(@NotNull XMLEvent event) {
return toEventName(event.getEventType());
}
/**
* Get the event name of the {@code eventType}, which is one of the types defined by
* {@link XMLStreamConstants}.
*
* @param eventType
* the event constant to get the event name for as defined by {@link XMLStreamConstants}
* @return the event name
*/
public static String toEventName(int eventType) {
String retval = EVENT_NAME_MAP.get(eventType);
if (retval == null) {
retval = "unknown event '" + Integer.toString(eventType) + "'";
}
return retval;
}
/**
* Advance through XMLEvents until the event type identified by {@code eventType} is reached or the
* end of stream is found.
*
* @param reader
* the event reader to advance
* @param eventType
* the event type to stop on as defined by {@link XMLStreamConstants}
* @return the next event of the specified type or {@code null} if the end of stream is reached
* @throws XMLStreamException
* if an error occurred while advancing the stream
*/
public static XMLEvent advanceTo(@NotNull XMLEventReader2 reader, int eventType)
throws XMLStreamException { // NO_UCD (unused code)
XMLEvent xmlEvent;
do {
xmlEvent = reader.nextEvent();
// if (LOGGER.isWarnEnabled()) {
// LOGGER.warn("skipping over: {}", XmlEventUtil.toString(xmlEvent));
// }
if (xmlEvent.isStartElement()) {
advanceTo(reader, XMLStreamConstants.END_ELEMENT);
// skip this end element
xmlEvent = reader.nextEvent();
// if (LOGGER.isDebugEnabled()) {
// LOGGER.debug("skipping over: {}", XmlEventUtil.toString(xmlEvent));
// }
}
} while (reader.hasNext() && (xmlEvent = reader.peek()).getEventType() != eventType);
return xmlEvent;
}
/**
* Skip over any processing instructions.
*
* @param reader
* the event reader to advance
* @return the last processing instruction event or the reader's next event if no processing
* instruction was found
* @throws XMLStreamException
* if an error occurred while advancing the stream
*/
public static XMLEvent skipProcessingInstructions(@NotNull XMLEventReader2 reader) throws XMLStreamException {
XMLEvent nextEvent;
while ((nextEvent = reader.peek()).isProcessingInstruction()) {
nextEvent = reader.nextEvent();
}
return nextEvent;
}
/**
* Skip over any whitespace.
*
* @param reader
* the event reader to advance
* @return the last character event containing whitespace or the reader's next event if no character
* event was found
* @throws XMLStreamException
* if an error occurred while advancing the stream
*/
public static XMLEvent skipWhitespace(@NotNull XMLEventReader2 reader) throws XMLStreamException {
XMLEvent nextEvent;
while ((nextEvent = reader.peek()).isCharacters()) {
Characters characters = nextEvent.asCharacters();
String data = characters.getData();
if (WHITESPACE_ONLY.matcher(data).matches()) {
nextEvent = reader.nextEvent();
} else {
break;
}
}
return nextEvent;
}
/**
* Determine if the next event from {@code reader} is an end element whose name matches the provided
* {@code name}.
*
* @param reader
* the event reader
* @param name
* the expected element name
* @return {@code true} if the next event matches the {@code name}
* @throws XMLStreamException
* if an error occurred while looking at the next event
*/
@SuppressWarnings("null")
public static boolean isNextEventEndElement(@NotNull XMLEventReader2 reader, @NotNull QName name)
throws XMLStreamException {
return isNextEventEndElement(reader, name.getLocalPart(), name.getNamespaceURI());
}
/**
* Determine if the next event from {@code reader} is an end element whose name matches the provided
* {@code expectedLocalName} and {@code expectedNamespace}.
*
* @param reader
* the event reader
* @param expectedLocalName
* the expected element name
* @param expectedNamespace
* the expected element namespace
* @return {@code true} if the next event matches the {@code name}
* @throws XMLStreamException
* if an error occurred while looking at the next event
*/
public static boolean isNextEventEndElement(@NotNull XMLEventReader2 reader, @NotNull String expectedLocalName,
String expectedNamespace) throws XMLStreamException {
Objects.requireNonNull(reader, "reader");
Objects.requireNonNull(expectedLocalName, "expectedLocalName");
XMLEvent event = reader.peek();
boolean retval;
if (event.isEndElement()) {
EndElement endElement = event.asEndElement();
QName name = endElement.getName();
retval = expectedLocalName.equals(name.getLocalPart())
&& (expectedNamespace == null || expectedNamespace.equals(name.getNamespaceURI()));
} else {
retval = false;
}
return retval;
}
/**
* Determine if the next event from {@code reader} is a start element whose name matches the
* provided {@code name}.
*
* @param reader
* the event reader
* @param name
* the expected element name
* @return {@code true} if the next event is a start element that matches the {@code name}
* @throws XMLStreamException
* if an error occurred while looking at the next event
*/
public static boolean isNextEventStartElement(XMLEventReader2 reader, QName name) throws XMLStreamException {
XMLEvent nextEvent = reader.peek();
return nextEvent.isStartElement() && name.equals(nextEvent.asStartElement().getName());
}
/**
* Determine if the next event from {@code reader} is an end of document event.
*
* @param reader
* the event reader
* @return {@code true} if the next event is an end of document event
* @throws XMLStreamException
* if an error occurred while looking at the next event
*/
public static boolean isNextEventEndDocument(XMLEventReader2 reader) throws XMLStreamException {
return reader.peek().isEndDocument();
}
/**
* Consume the next event from {@code reader} and assert that this event is of the type identified
* by {@code presumedEventType}.
*
* @param reader
* the event reader
* @param presumedEventType
* the expected event type as defined by {@link XMLStreamConstants}
* @return the next event
* @throws XMLStreamException
* if an error occurred while looking at the next event
*/
public static XMLEvent consumeAndAssert(XMLEventReader2 reader, int presumedEventType)
throws XMLStreamException {
return consumeAndAssert(reader, presumedEventType, null);
}
/**
* Consume the next event from {@code reader} and assert that this event is of the type identified
* by {@code presumedEventType} and has the name identified by {@code presumedName}.
*
* @param reader
* the event reader
* @param presumedEventType
* the expected event type as defined by {@link XMLStreamConstants}
* @param presumedName
* the expected name of the node associated with the event
* @return the next event
* @throws XMLStreamException
* if an error occurred while looking at the next event
*/
public static XMLEvent consumeAndAssert(XMLEventReader2 reader, int presumedEventType, QName presumedName)
throws XMLStreamException {
XMLEvent retval = reader.nextEvent();
int eventType = retval.getEventType();
QName name = toQName(retval);
assert eventType == presumedEventType
&& (presumedName == null
|| presumedName.equals(name)) : generateAssertMessage(
retval,
presumedEventType,
presumedName);
return retval;
}
/**
* Assert that the next event from {@code reader} is of the type identified by
* {@code presumedEventType}.
*
* @param reader
* the event reader
* @param presumedEventType
* the expected event type as defined by {@link XMLStreamConstants}
* @throws XMLStreamException
* if an error occurred while looking at the next event
*/
public static void assertNext(XMLEventReader2 reader, int presumedEventType)
throws XMLStreamException {
assertNext(reader, presumedEventType, null);
}
/**
* Assert that the next event from {@code reader} is of the type identified by
* {@code presumedEventType} and has the name identified by {@code presumedName}.
*
* @param reader
* the event reader
* @param presumedEventType
* the expected event type as defined by {@link XMLStreamConstants}
* @param presumedName
* the expected name of the node associated with the event
* @throws XMLStreamException
* if an error occurred while looking at the next event
*/
public static void assertNext(XMLEventReader2 reader, int presumedEventType, QName presumedName)
throws XMLStreamException {
XMLEvent nextEvent = reader.peek();
int eventType = nextEvent.getEventType();
QName name = toQName(nextEvent);
assert eventType == presumedEventType
&& (presumedName == null || presumedName.equals(name)) : generateAssertMessage(nextEvent, presumedEventType,
presumedName);
}
private static CharSequence generateAssertMessage(XMLEvent retval, int presumedEventType, QName presumedName) {
StringBuilder builder = new StringBuilder(30);
builder
.append("Expected XML ")
.append(toEventName(presumedEventType));
if (presumedName != null) {
builder.append(" for QName '")
.append(presumedName.toString());
}
builder.append("', instead found ")
.append(toString(retval));
return builder;
}
/**
* Skips events specified by {@code events}.
*
* @param reader
* the event reader
* @param events
* the events to skip
* @return the next non-mataching event returned by {@link XMLEventReader2#peek()}, or {@code null}
* if there was no next event
* @throws XMLStreamException
* if an error occurred while reading
*/
public static XMLEvent skipEvents(XMLEventReader2 reader, int... events) throws XMLStreamException {
Set skipEvents = IntStream.of(events).boxed().collect(Collectors.toSet());
XMLEvent nextEvent = null;
while (reader.hasNext()) {
nextEvent = reader.peek();
if (!skipEvents.contains(nextEvent.getEventType())) {
break;
}
reader.nextEvent();
}
return nextEvent;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy