/**
* Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright ownership. Apereo
* 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 the
* following location:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>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 org.springframework.util.xml;
import javax.xml.stream.Location;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.Comment;
import javax.xml.stream.events.DTD;
import javax.xml.stream.events.EntityReference;
import javax.xml.stream.events.XMLEvent;
import javax.xml.stream.util.XMLEventConsumer;
import org.springframework.util.Assert;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
/**
*/
public class StaxEventLexicalContentHandler extends StaxEventContentHandler
implements LexicalHandler {
public static final String EMPTY_SYSTEM_IDENTIFIER = "EMPTY";
private final XMLEventFactory eventFactory;
private final XMLEventConsumer eventConsumer;
private StringBuilder cdata = null;
public StaxEventLexicalContentHandler(XMLEventConsumer consumer, XMLEventFactory factory) {
super(consumer, factory);
Assert.notNull(consumer, "'consumer' must not be null");
this.eventFactory = factory;
this.eventConsumer = consumer;
}
public StaxEventLexicalContentHandler(XMLEventConsumer consumer) {
this(consumer, XMLEventFactory.newInstance());
}
/**
* Essentially the same logic as the parent but uses a static Location impl to avoid this$0
* reference holding on to StaxEventLexicalContentHandler instances
*/
public void setDocumentLocator(final Locator locator) {
if (locator != null) {
eventFactory.setLocation(new LocatorLocation(locator));
}
}
/* (non-Javadoc)
* @see org.xml.sax.ext.LexicalHandler#startDTD(java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public void startDTD(String name, String publicId, String systemId) throws SAXException {
// System identifier must be specified to print DOCTYPE.
// This method is only called if the system identifier is specified.
// Since the HTML5 DOCTYPE declaration does not include a system
// identifier, this code allows the static string EMPTY to serve as
// a temporary system id for doctypes which should not have one set.
// If public identifier is specified print 'PUBLIC
// <public> <system>', or if a non-'EMPTY' system identifier is
// specified, print 'SYSTEM <system>'.
final StringBuilder dtdBuilder = new StringBuilder("<!DOCTYPE ");
dtdBuilder.append(name);
if (publicId != null) {
dtdBuilder.append(" PUBLIC \"").append(publicId).append("\" \"");
dtdBuilder.append(systemId).append("\"");
} else if (!EMPTY_SYSTEM_IDENTIFIER.equals(systemId)) {
dtdBuilder.append(" SYSTEM \"");
dtdBuilder.append(systemId).append("\"");
}
dtdBuilder.append(">");
final DTD event = eventFactory.createDTD(dtdBuilder.toString());
try {
this.consumeEvent(event);
} catch (XMLStreamException ex) {
throw new SAXException("Could not create DTD: " + ex.getMessage(), ex);
}
}
/* (non-Javadoc)
* @see org.xml.sax.ext.LexicalHandler#endDTD()
*/
@Override
public void endDTD() throws SAXException {
return;
//noop
}
/* (non-Javadoc)
* @see org.xml.sax.ext.LexicalHandler#startEntity(java.lang.String)
*/
@Override
public void startEntity(String name) throws SAXException {
final EntityReference event = eventFactory.createEntityReference(name, null);
try {
this.consumeEvent(event);
} catch (XMLStreamException ex) {
throw new SAXException("Could not create Entity: " + ex.getMessage(), ex);
}
}
/* (non-Javadoc)
* @see org.xml.sax.ext.LexicalHandler#endEntity(java.lang.String)
*/
@Override
public void endEntity(String name) throws SAXException {
return;
//noop
}
/* (non-Javadoc)
* @see org.xml.sax.ext.LexicalHandler#startCDATA()
*/
@Override
public void startCDATA() throws SAXException {
this.cdata = new StringBuilder();
}
/* (non-Javadoc)
* @see org.xml.sax.ext.LexicalHandler#endCDATA()
*/
@Override
public void endCDATA() throws SAXException {
final Characters event = eventFactory.createCData(cdata.toString());
cdata = null;
try {
this.consumeEvent(event);
} catch (XMLStreamException ex) {
throw new SAXException("Could not create CDATA: " + ex.getMessage(), ex);
}
}
/* (non-Javadoc)
* @see org.springframework.util.xml.StaxEventContentHandler#charactersInternal(char[], int, int)
*/
@Override
protected void charactersInternal(char[] ch, int start, int length) throws XMLStreamException {
if (this.cdata != null) {
cdata.append(ch, start, length);
} else {
super.charactersInternal(ch, start, length);
}
}
/* (non-Javadoc)
* @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
*/
@Override
public void comment(char[] ch, int start, int length) throws SAXException {
final Comment event = eventFactory.createComment(new String(ch, start, length));
try {
this.consumeEvent(event);
} catch (XMLStreamException ex) {
throw new SAXException("Could not create Comment: " + ex.getMessage(), ex);
}
}
private void consumeEvent(XMLEvent event) throws XMLStreamException {
eventConsumer.add(event);
}
private static final class LocatorLocation implements Location {
private final Locator locator;
private LocatorLocation(Locator locator) {
this.locator = locator;
}
public int getLineNumber() {
return locator.getLineNumber();
}
public int getColumnNumber() {
return locator.getColumnNumber();
}
public int getCharacterOffset() {
return -1;
}
public String getPublicId() {
return locator.getPublicId();
}
public String getSystemId() {
return locator.getSystemId();
}
}
}