XMLEventReader.getElementText() properly checks for start element

Issue: SPR-17233
This commit is contained in:
Juergen Hoeller 2018-09-07 12:56:15 +02:00
parent 8d45e3e7ef
commit 84ec382201
3 changed files with 115 additions and 83 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2016 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,18 +18,15 @@ package org.springframework.util.xml;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
/** /**
* Abstract base class for {@code XMLEventReader}s. * Abstract base class for {@code XMLEventReader}s.
* *
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Juergen Hoeller
* @since 5.0 * @since 5.0
*/ */
abstract class AbstractXMLEventReader implements XMLEventReader { abstract class AbstractXMLEventReader implements XMLEventReader {
@ -43,7 +40,7 @@ abstract class AbstractXMLEventReader implements XMLEventReader {
return nextEvent(); return nextEvent();
} }
catch (XMLStreamException ex) { catch (XMLStreamException ex) {
throw new NoSuchElementException(); throw new NoSuchElementException(ex.getMessage());
} }
} }
@ -53,64 +50,9 @@ abstract class AbstractXMLEventReader implements XMLEventReader {
"remove not supported on " + ClassUtils.getShortName(getClass())); "remove not supported on " + ClassUtils.getShortName(getClass()));
} }
@Override
public String getElementText() throws XMLStreamException {
checkIfClosed();
if (!peek().isStartElement()) {
throw new XMLStreamException("Not at START_ELEMENT");
}
StringBuilder builder = new StringBuilder();
while (true) {
XMLEvent event = nextEvent();
if (event.isEndElement()) {
break;
}
else if (!event.isCharacters()) {
throw new XMLStreamException(
"Unexpected event [" + event + "] in getElementText()");
}
Characters characters = event.asCharacters();
if (!characters.isIgnorableWhiteSpace()) {
builder.append(event.asCharacters().getData());
}
}
return builder.toString();
}
@Override
@Nullable
public XMLEvent nextTag() throws XMLStreamException {
checkIfClosed();
while (true) {
XMLEvent event = nextEvent();
switch (event.getEventType()) {
case XMLStreamConstants.START_ELEMENT:
case XMLStreamConstants.END_ELEMENT:
return event;
case XMLStreamConstants.END_DOCUMENT:
return null;
case XMLStreamConstants.SPACE:
case XMLStreamConstants.COMMENT:
case XMLStreamConstants.PROCESSING_INSTRUCTION:
continue;
case XMLStreamConstants.CDATA:
case XMLStreamConstants.CHARACTERS:
if (!event.asCharacters().isWhiteSpace()) {
throw new XMLStreamException(
"Non-ignorable whitespace CDATA or CHARACTERS event in nextTag()");
}
break;
default:
throw new XMLStreamException("Received event [" + event +
"], instead of START_ELEMENT or END_ELEMENT.");
}
}
}
/** /**
* Throws an {@code IllegalArgumentException} when called. * This implementation throws an {@code IllegalArgumentException} for any property.
* @throws IllegalArgumentException when called. * @throws IllegalArgumentException when called
*/ */
@Override @Override
public Object getProperty(String name) throws IllegalArgumentException { public Object getProperty(String name) throws IllegalArgumentException {
@ -123,21 +65,12 @@ abstract class AbstractXMLEventReader implements XMLEventReader {
} }
/** /**
* Returns {@code true} if closed; {@code false} otherwise. * Check if the reader is closed, and throws a {@code XMLStreamException} if so.
* @see #close()
*/
protected boolean isClosed() {
return this.closed;
}
/**
* Checks if the reader is closed, and throws a {@code XMLStreamException} if so.
* @throws XMLStreamException if the reader is closed * @throws XMLStreamException if the reader is closed
* @see #close() * @see #close()
* @see #isClosed()
*/ */
protected void checkIfClosed() throws XMLStreamException { protected void checkIfClosed() throws XMLStreamException {
if (isClosed()) { if (this.closed) {
throw new XMLStreamException("XMLEventReader has been closed"); throw new XMLStreamException("XMLEventReader has been closed");
} }
} }

View File

@ -19,6 +19,9 @@ package org.springframework.util.xml;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent; import javax.xml.stream.events.XMLEvent;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -29,12 +32,16 @@ import org.springframework.util.Assert;
* of {@link XMLEvent} elements. * of {@link XMLEvent} elements.
* *
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Juergen Hoeller
* @since 5.0 * @since 5.0
*/ */
class ListBasedXMLEventReader extends AbstractXMLEventReader { class ListBasedXMLEventReader extends AbstractXMLEventReader {
private final List<XMLEvent> events; private final List<XMLEvent> events;
@Nullable
private XMLEvent currentEvent;
private int cursor = 0; private int cursor = 0;
@ -46,13 +53,15 @@ class ListBasedXMLEventReader extends AbstractXMLEventReader {
@Override @Override
public boolean hasNext() { public boolean hasNext() {
return (this.cursor != this.events.size()); return (this.cursor < this.events.size());
} }
@Override @Override
public XMLEvent nextEvent() { public XMLEvent nextEvent() {
if (this.cursor < this.events.size()) { if (hasNext()) {
return this.events.get(this.cursor++); this.currentEvent = this.events.get(this.cursor);
this.cursor++;
return this.currentEvent;
} }
else { else {
throw new NoSuchElementException(); throw new NoSuchElementException();
@ -62,7 +71,7 @@ class ListBasedXMLEventReader extends AbstractXMLEventReader {
@Override @Override
@Nullable @Nullable
public XMLEvent peek() { public XMLEvent peek() {
if (this.cursor < this.events.size()) { if (hasNext()) {
return this.events.get(this.cursor); return this.events.get(this.cursor);
} }
else { else {
@ -70,6 +79,60 @@ class ListBasedXMLEventReader extends AbstractXMLEventReader {
} }
} }
@Override
public String getElementText() throws XMLStreamException {
checkIfClosed();
if (this.currentEvent == null || !this.currentEvent.isStartElement()) {
throw new XMLStreamException("Not at START_ELEMENT: " + this.currentEvent);
}
StringBuilder builder = new StringBuilder();
while (true) {
XMLEvent event = nextEvent();
if (event.isEndElement()) {
break;
}
else if (!event.isCharacters()) {
throw new XMLStreamException("Unexpected non-text event: " + event);
}
Characters characters = event.asCharacters();
if (!characters.isIgnorableWhiteSpace()) {
builder.append(event.asCharacters().getData());
}
}
return builder.toString();
}
@Override
@Nullable
public XMLEvent nextTag() throws XMLStreamException {
checkIfClosed();
while (true) {
XMLEvent event = nextEvent();
switch (event.getEventType()) {
case XMLStreamConstants.START_ELEMENT:
case XMLStreamConstants.END_ELEMENT:
return event;
case XMLStreamConstants.END_DOCUMENT:
return null;
case XMLStreamConstants.SPACE:
case XMLStreamConstants.COMMENT:
case XMLStreamConstants.PROCESSING_INSTRUCTION:
continue;
case XMLStreamConstants.CDATA:
case XMLStreamConstants.CHARACTERS:
if (!event.asCharacters().isWhiteSpace()) {
throw new XMLStreamException(
"Non-ignorable whitespace CDATA or CHARACTERS event: " + event);
}
break;
default:
throw new XMLStreamException("Expected START_ELEMENT or END_ELEMENT: " + event);
}
}
}
@Override @Override
public void close() { public void close() {
super.close(); super.close();

View File

@ -16,9 +16,6 @@
package org.springframework.util.xml; package org.springframework.util.xml;
import static org.junit.Assert.assertThat;
import static org.xmlunit.matchers.CompareMatcher.isSimilarTo;
import java.io.StringReader; import java.io.StringReader;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.ArrayList; import java.util.ArrayList;
@ -32,8 +29,13 @@ import javax.xml.stream.events.XMLEvent;
import org.junit.Test; import org.junit.Test;
import static javax.xml.stream.XMLStreamConstants.*;
import static org.junit.Assert.*;
import static org.xmlunit.matchers.CompareMatcher.*;
/** /**
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Andrzej Hołowko
*/ */
public class ListBasedXMLEventReaderTests { public class ListBasedXMLEventReaderTests {
@ -41,6 +43,7 @@ public class ListBasedXMLEventReaderTests {
private final XMLOutputFactory outputFactory = XMLOutputFactory.newFactory(); private final XMLOutputFactory outputFactory = XMLOutputFactory.newFactory();
@Test @Test
public void standard() throws Exception { public void standard() throws Exception {
String xml = "<foo><bar>baz</bar></foo>"; String xml = "<foo><bar>baz</bar></foo>";
@ -55,9 +58,42 @@ public class ListBasedXMLEventReaderTests {
assertThat(resultWriter.toString(), isSimilarTo(xml)); assertThat(resultWriter.toString(), isSimilarTo(xml));
} }
@Test
public void testGetElementText() throws Exception {
String xml = "<foo><bar>baz</bar></foo>";
List<XMLEvent> events = readEvents(xml);
ListBasedXMLEventReader reader = new ListBasedXMLEventReader(events);
assertEquals(START_DOCUMENT, reader.nextEvent().getEventType());
assertEquals(START_ELEMENT, reader.nextEvent().getEventType());
assertEquals(START_ELEMENT, reader.nextEvent().getEventType());
assertEquals("baz", reader.getElementText());
assertEquals(END_ELEMENT, reader.nextEvent().getEventType());
assertEquals(END_DOCUMENT, reader.nextEvent().getEventType());
}
@Test
public void testGetElementTextThrowsExceptionAtWrongPosition() throws Exception {
String xml = "<foo><bar>baz</bar></foo>";
List<XMLEvent> events = readEvents(xml);
ListBasedXMLEventReader reader = new ListBasedXMLEventReader(events);
assertEquals(START_DOCUMENT, reader.nextEvent().getEventType());
try {
reader.getElementText();
fail("Should have thrown XMLStreamException");
}
catch (XMLStreamException ex) {
// expected
assertTrue(ex.getMessage().startsWith("Not at START_ELEMENT"));
}
}
private List<XMLEvent> readEvents(String xml) throws XMLStreamException { private List<XMLEvent> readEvents(String xml) throws XMLStreamException {
XMLEventReader reader = XMLEventReader reader = this.inputFactory.createXMLEventReader(new StringReader(xml));
this.inputFactory.createXMLEventReader(new StringReader(xml));
List<XMLEvent> events = new ArrayList<>(); List<XMLEvent> events = new ArrayList<>();
while (reader.hasNext()) { while (reader.hasNext()) {
events.add(reader.nextEvent()); events.add(reader.nextEvent());