org.thymeleaf.templateparser.xmlsax.AbstractNonValidatingSAXTemplateParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.everit.osgi.bundles.org.thymeleaf.thymeleaf Show documentation
Show all versions of org.everit.osgi.bundles.org.thymeleaf.thymeleaf Show documentation
XML/XHTML/HTML5 template engine for Java
The newest version!
/*
* =============================================================================
*
* Copyright (c) 2011-2013, The THYMELEAF team (http://www.thymeleaf.org)
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.thymeleaf.templateparser.xmlsax;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thymeleaf.Configuration;
import org.thymeleaf.dom.CDATASection;
import org.thymeleaf.dom.Comment;
import org.thymeleaf.dom.DocType;
import org.thymeleaf.dom.Document;
import org.thymeleaf.dom.Element;
import org.thymeleaf.dom.NestableNode;
import org.thymeleaf.dom.Node;
import org.thymeleaf.dom.Text;
import org.thymeleaf.exceptions.ParserInitializationException;
import org.thymeleaf.exceptions.TemplateInputException;
import org.thymeleaf.exceptions.TemplateProcessingException;
import org.thymeleaf.templateparser.EntityResolver;
import org.thymeleaf.templateparser.ErrorHandler;
import org.thymeleaf.templateparser.ITemplateParser;
import org.thymeleaf.templateparser.TemplatePreprocessingReader;
import org.thymeleaf.util.ArrayUtils;
import org.thymeleaf.util.ResourcePool;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.DefaultHandler2;
import org.xml.sax.ext.Locator2;
/**
*
* Parses XML documents, using a standard SAX parser.
*
*
*
* This implementation populates tree nodes with detailed location
* information (document name and line number).
*
*
* @since 2.0.0
*
* @author Guven Demir
* @author Daniel Fernández
* @author Tsuyoshi Yoshizawa
*
*/
public abstract class AbstractNonValidatingSAXTemplateParser implements ITemplateParser {
private static final int BUFFER_SIZE = 8192;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final ResourcePool pool;
private boolean canResetParsers = true;
protected AbstractNonValidatingSAXTemplateParser(final int poolSize) {
super();
this.pool = createSaxParsers(poolSize, false);
}
protected ResourcePool getPool() {
return this.pool;
}
protected final ResourcePool getNonValidatingPool() {
return this.pool;
}
protected final ResourcePool createSaxParsers(final int poolSize, final boolean validating) {
final SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setValidating(validating);
return new ResourcePool(new SAXTemplateParserFactory(factory), poolSize);
}
public final Document parseTemplate(final Configuration configuration, final String documentName, final Reader reader) {
return parseTemplateUsingPool(configuration, documentName, reader, getPool());
}
private Document parseTemplateUsingPool(final Configuration configuration, final String documentName,
final Reader reader, final ResourcePool poolToBeUsed) {
final SAXParser saxParser = poolToBeUsed.allocate();
final TemplatePreprocessingReader templateReader = getTemplatePreprocessingReader(reader);
try {
/*
* Parse the document
*/
final Document document =
doParse(configuration, documentName, templateReader, saxParser);
if (this.canResetParsers) {
try {
/*
* Reset the parser so that it can be used again.
*/
saxParser.reset();
} catch (final UnsupportedOperationException ignored) {
if (this.logger.isWarnEnabled()) {
this.logger.warn(
"[THYMELEAF] The SAX Parser implementation being used (\"{}\") does not implement " +
"the \"reset\" operation. This will force Thymeleaf to re-create parser instances " +
"each time they are needed for parsing templates, which is more costly. Enabling template " +
"cache is recommended, and also using a parser library which implements \"reset\" such as " +
"xerces version 2.9.1 or newer.",
saxParser.getClass().getName());
}
this.canResetParsers = false;
}
}
return document;
} catch (final IOException e) {
throw new TemplateInputException("Exception parsing document", e);
} catch (final TemplateProcessingException e) {
throw e;
} catch (final SAXParseException e) {
final String message =
String.format("Exception parsing document: template=\"%s\", line %d - column %d",
documentName, Integer.valueOf(e.getLineNumber()), Integer.valueOf(e.getColumnNumber()));
throw new TemplateInputException(message, e);
} catch (final SAXException e) {
throw new TemplateInputException("Exception parsing document", e);
} finally {
if (templateReader != null) {
try {
templateReader.close();
} catch (final Exception ignored) {
// ignored
}
}
if (this.canResetParsers) {
poolToBeUsed.release(saxParser);
} else {
poolToBeUsed.discardAndReplace(saxParser);
}
}
}
private static Document doParse(
final Configuration configuration, final String documentName,
final TemplatePreprocessingReader reader, final SAXParser saxParser)
throws IOException, SAXException {
final InputSource inputSource = new InputSource(reader);
final XmlSAXHandler handler =
new XmlSAXHandler(documentName, new EntityResolver(configuration), ErrorHandler.INSTANCE);
saxParser.setProperty(
"http://xml.org/sax/properties/lexical-handler", handler);
saxParser.setProperty(
"http://xml.org/sax/properties/declaration-handler", handler);
saxParser.parse(inputSource, handler);
final String docTypeClause = reader.getDocTypeClause();
final String docTypeRootElementName = handler.getDocTypeRootElementName();
final String docTypePublicId = handler.getDocTypePublicId();
final String docTypeSystemId = handler.getDocTypeSystemId();
// The DOCTYPE root element name could be null if we are parsing
// a non-complete document, a fragment, without a DOCTYPE declaration.
final DocType docType =
(docTypeRootElementName != null?
new DocType(docTypeRootElementName, docTypePublicId, docTypeSystemId, docTypeClause) :
null);
final List rootNodes = handler.getRootNodes();
final String xmlVersion = handler.getXmlVersion();
final String xmlEncoding = handler.getXmlEncoding();
final boolean xmlStandalone = handler.isXmlStandalone();
final Document document = new Document(documentName, docType);
if (xmlVersion != null) {
document.setNodeProperty(Node.NODE_PROPERTY_XML_VERSION, xmlVersion);
}
if (xmlEncoding != null) {
document.setNodeProperty(Node.NODE_PROPERTY_XML_ENCODING, xmlEncoding);
}
if (xmlStandalone) {
document.setNodeProperty(Node.NODE_PROPERTY_XML_STANDALONE, Boolean.TRUE);
}
document.setChildren(rootNodes);
return document;
}
public final List parseFragment(final Configuration configuration, final String fragment) {
final String wrappedFragment = wrapFragment(fragment);
final Document document =
parseTemplateUsingPool(
configuration,
null, // documentName
new StringReader(wrappedFragment),
getNonValidatingPool());
return unwrapFragment(document);
}
protected abstract String wrapFragment(final String fragment);
protected abstract List unwrapFragment(final Document document);
/**
* @since 2.0.11
*/
protected boolean shouldAddThymeleafRootToParser() {
return true;
}
/**
* @since 2.0.11
*/
protected TemplatePreprocessingReader getTemplatePreprocessingReader(final Reader reader) {
if (reader instanceof TemplatePreprocessingReader) {
final TemplatePreprocessingReader templatePreprocessingReader = (TemplatePreprocessingReader) reader;
return new TemplatePreprocessingReader(
templatePreprocessingReader.getInnerReader(), BUFFER_SIZE, shouldAddThymeleafRootToParser());
}
return new TemplatePreprocessingReader(reader, BUFFER_SIZE, shouldAddThymeleafRootToParser());
}
private static final class XmlSAXHandler extends DefaultHandler2 {
private static final int HANDLER_BUFFER_SIZE = 512;
private final String documentName;
private final Stack elementStack;
private char[] textBuffer;
private int textBufferLen;
private char[] cdataBuffer;
private int cdataBufferLen;
private final org.xml.sax.EntityResolver entityResolver;
private final org.xml.sax.ErrorHandler errorHandler;
private Locator locator = null;
private String docTypeRootElementName = null;
private String docTypePublicId = null;
private String docTypeSystemId = null;
private List rootNodes = null;
private boolean cdataMode = false;
private boolean dtdMode = false;
private String xmlEncoding = null;
private String xmlVersion = null;
private boolean xmlStandalone = false;
private boolean xmlDeclarationComputed = false;
XmlSAXHandler(final String documentName,
final org.xml.sax.EntityResolver entityResolver,
final org.xml.sax.ErrorHandler errorHandler) {
super();
this.documentName = documentName;
this.elementStack = new Stack();
this.rootNodes = new ArrayList(4);
this.textBuffer = new char[HANDLER_BUFFER_SIZE];
this.cdataBuffer = new char[HANDLER_BUFFER_SIZE];
this.entityResolver = entityResolver;
this.errorHandler = errorHandler;
}
public String getDocTypeRootElementName() {
return this.docTypeRootElementName;
}
public String getDocTypePublicId() {
return this.docTypePublicId;
}
public String getDocTypeSystemId() {
return this.docTypeSystemId;
}
public List getRootNodes() {
return this.rootNodes;
}
public String getXmlEncoding() {
return this.xmlEncoding;
}
public String getXmlVersion() {
return this.xmlVersion;
}
public boolean isXmlStandalone() {
return this.xmlStandalone;
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
flushBuffer();
}
/*
* ----------------------
* CDATA section handling
* ----------------------
*/
@Override
public void startCDATA() throws SAXException {
super.startCDATA();
flushBuffer();
this.cdataMode = true;
}
@Override
public void endCDATA() throws SAXException {
super.endCDATA();
this.cdataMode = false;
if(this.cdataBufferLen > 0) {
final Node cdata =
new CDATASection(new String(this.cdataBuffer, 0, this.cdataBufferLen), null, null, true);
if (this.elementStack.isEmpty()) {
this.rootNodes.add(cdata);
} else {
final NestableNode parent = this.elementStack.peek();
parent.addChild(cdata);
}
this.cdataBufferLen = 0;
}
}
/*
* -------------
* Text handling
* -------------
*/
@Override
public void characters(final char[] ch, final int start, final int length) {
TemplatePreprocessingReader.removeEntitySubstitutions(ch, start, length);
if (this.cdataMode) {
while (this.cdataBufferLen + length > this.cdataBuffer.length) {
this.cdataBuffer = ArrayUtils.copyOf(this.cdataBuffer, this.cdataBuffer.length * 2);
}
System.arraycopy(ch, start, this.cdataBuffer, this.cdataBufferLen, length);
this.cdataBufferLen += length;
} else {
while (this.textBufferLen + length > this.textBuffer.length) {
this.textBuffer = ArrayUtils.copyOf(this.textBuffer, this.textBuffer.length * 2);
}
System.arraycopy(ch, start, this.textBuffer, this.textBufferLen, length);
this.textBufferLen += length;
}
}
@Override
public void ignorableWhitespace(final char[] ch, final int start, final int length)
throws SAXException {
characters(ch, start, length);
}
/*
* ----------------
* Comment handling
* ----------------
*/
@Override
public void comment(final char[] ch, final int start, final int length)
throws SAXException {
if (!this.dtdMode) {
flushBuffer();
final Comment comment =
new Comment(new String(ch, start, start + length));
if (this.elementStack.isEmpty()) {
this.rootNodes.add(comment);
} else {
final NestableNode parent = this.elementStack.peek();
parent.addChild(comment);
}
}
}
/*
* ----------------
* Element handling
* ----------------
*/
@Override
public void startElement(final String uri, final String localName, final String qName, final Attributes attributes)
throws SAXException {
if (!this.xmlDeclarationComputed) {
// SAX specification says the "getEncoding()" method in Locator2 can only
// be called AFTER startDocument has returned and BEFORE endDocument is called.
if (this.locator != null && this.locator instanceof Locator2) {
final Locator2 loc = (Locator2) this.locator;
this.xmlVersion = loc.getXMLVersion();
this.xmlEncoding = loc.getEncoding();
// There seems to be no way of obtaining the "standalone" property
// from the XML declaration.
}
this.xmlDeclarationComputed = true;
}
flushBuffer();
Integer lineNumber = null;
if (this.locator != null) {
lineNumber = Integer.valueOf(this.locator.getLineNumber());
}
final Element element = new Element(qName, this.documentName, lineNumber);
for (int i = 0; i < attributes.getLength(); i++) {
element.setAttribute(
attributes.getQName(i),
false,
TemplatePreprocessingReader.removeEntitySubstitutions(attributes.getValue(i)),
true);
}
this.elementStack.push(element);
}
@Override
public void endElement(final String uri, final String localName, final String qName) {
flushBuffer();
final NestableNode node = this.elementStack.pop();
if (node instanceof Element) {
final Element element = (Element) node;
if (TemplatePreprocessingReader.SYNTHETIC_ROOT_ELEMENT_NAME.equals(element.getOriginalName())) {
// If it is the synthetic root element, then we skip the element itself and just add
// its children to the results.
final List syntheticRootChildren = element.getChildren();
if (this.elementStack.isEmpty()) {
this.rootNodes.addAll(syntheticRootChildren);
} else {
final NestableNode parent = this.elementStack.peek();
for (final Node syntheticRootChild : syntheticRootChildren) {
parent.addChild(syntheticRootChild);
}
}
return;
}
}
if (this.elementStack.isEmpty()) {
this.rootNodes.add(node);
} else {
final NestableNode parent = this.elementStack.peek();
parent.addChild(node);
}
}
/*
* ------------
* DTD handling
* ------------
*/
@Override
public void startDTD(final String name, final String publicId, final String systemId)
throws SAXException {
super.startDTD(name, publicId, systemId);
this.docTypeRootElementName = name;
this.docTypePublicId = publicId;
this.docTypeSystemId = systemId;
this.dtdMode = true;
}
@Override
public void endDTD() throws SAXException {
super.endDTD();
this.dtdMode = false;
}
/*
* Buffer is used for accumulating text that is read in between elements,
* and should be flushed before creating any non-text element.
*
* Note there also is a 'CDATA' buffer, similar in use to this, containing
* all the contents read for a CDATA section.
*/
private void flushBuffer() {
if (this.textBufferLen > 0) {
final Node textNode =
new Text(new String(this.textBuffer, 0, this.textBufferLen), null, null, true);
if (this.elementStack.isEmpty()) {
this.rootNodes.add(textNode);
} else {
final NestableNode parent = this.elementStack.peek();
parent.addChild(textNode);
}
this.textBufferLen = 0;
}
}
/*
* Document locator will help us determine the line number for each node
* so that we can add debug information to our DOM tree
*/
@Override
public void setDocumentLocator(final Locator locator) {
this.locator = locator;
}
/*
* ------------
* ErrorHandler
* ------------
*/
@Override
public void error(final SAXParseException exception) throws SAXException {
this.errorHandler.error(exception);
}
@Override
public void fatalError(final SAXParseException exception) throws SAXException {
this.errorHandler.fatalError(exception);
}
@Override
public void warning(final SAXParseException exception) throws SAXException {
this.errorHandler.warning(exception);
}
/*
* ---------------
* EntityResolver2
* ---------------
*/
@Override
public InputSource getExternalSubset(final String name, final String baseURI)
throws SAXException, IOException {
return null;
}
@Override
public InputSource resolveEntity(final String publicId, final String systemId)
throws IOException, SAXException {
return this.entityResolver.resolveEntity(publicId, systemId);
}
@Override
public InputSource resolveEntity(final String name, final String publicId,
final String baseURI, final String systemId)
throws SAXException, IOException {
return resolveEntity(publicId, systemId);
}
}
static class SAXTemplateParserFactory implements ResourcePool.IResourceFactory {
private final SAXParserFactory saxParserFactory;
SAXTemplateParserFactory(final SAXParserFactory saxParserFactory) {
super();
this.saxParserFactory = saxParserFactory;
}
public SAXParser createResource() {
try {
return this.saxParserFactory.newSAXParser();
} catch(final ParserConfigurationException e) {
throw new ParserInitializationException("Error creating SAX parser", e);
} catch(final SAXException e) {
throw new ParserInitializationException("Error creating SAX parser", e);
}
}
}
}