blob: a17fc3f446b5998a63585644471f269fc76c5894 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2008-2010 See4sys and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* See4sys - Initial API and implementation
*
* </copyright>
*/
package org.eclipse.sphinx.platform.util;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.xerces.impl.Constants;
import org.eclipse.core.runtime.OperationCanceledException;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.DefaultHandler;
/**
* An XML event handler for detecting the root element's namespace, target namespace, and schema location.
*/
/**
*
*/
public class XMLRootElementHandler extends DefaultHandler implements LexicalHandler {
/**
* An exception indicating that the parsing should stop.
*/
private class StopParsingException extends SAXException {
/**
* All serializable objects should have a stable serialVersionUID
*/
private static final long serialVersionUID = 1L;
/**
* Constructs an instance of <code>StopParsingException</code> with a <code>null</code> detail message.
*/
public StopParsingException() {
super((String) null);
}
}
private static final String ATTRIBUTE_NAME_TARGET_NAMESPACE = "targetNamespace"; //$NON-NLS-1$
private static final String ATTRIBUTE_NAME_SCHEMA_LOCATION = "schemaLocation"; //$NON-NLS-1$
private static final String ATTRIBUTE_PREFIX_XMLNS = XMLConstants.XMLNS_ATTRIBUTE;
private static final String ATTRIBUTE_NAME_XMI = "xmi"; //$NON-NLS-1$
private static final String ATTRIBUTE_NAME_XSI = "xsi"; //$NON-NLS-1$
private final String FIELD_NAME_ATTRIBUTES = "fAttributes"; //$NON-NLS-1$
private final String FIELD_NAME_NAME = "name"; //$NON-NLS-1$
private final String FIELD_NAME_VALUE = "value"; //$NON-NLS-1$
private SAXParserFactory parserFactory;
private String rootElementNamespace = null;
private String targetNamespace = null;
private String xsiSchemaLocation = null;
private ArrayList<String> rootElementComments = new ArrayList<String>();
private String[] targetNamespaceExcludePatterns;
protected SAXParser getParser(boolean useLexicalHandler) throws ParserConfigurationException, SAXException {
if (parserFactory == null) {
parserFactory = SAXParserFactory.newInstance();
parserFactory.setNamespaceAware(true);
parserFactory.setValidating(false);
parserFactory.setXIncludeAware(false);
try {
parserFactory.setFeature(Constants.XERCES_FEATURE_PREFIX + Constants.DISALLOW_DOCTYPE_DECL_FEATURE, true);
parserFactory.setFeature(Constants.XERCES_FEATURE_PREFIX + Constants.LOAD_EXTERNAL_DTD_FEATURE, false);
} catch (SAXNotRecognizedException | SAXNotSupportedException e) {
// These exceptions are expected if Xerces is not used as the underlying parser.
}
parserFactory.setFeature(Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_GENERAL_ENTITIES_FEATURE, false);
parserFactory.setFeature(Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_PARAMETER_ENTITIES_FEATURE, false);
}
SAXParser parser = createParser(parserFactory);
if (useLexicalHandler) {
parser.setProperty(Constants.SAX_PROPERTY_PREFIX + Constants.LEXICAL_HANDLER_PROPERTY, this);
}
return parser;
}
protected SAXParser createParser(SAXParserFactory parserFactory)
throws ParserConfigurationException, SAXException, SAXNotRecognizedException, SAXNotSupportedException {
return parserFactory.newSAXParser();
}
/*
* @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String,
* org.xml.sax.Attributes)
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException, OperationCanceledException {
// Retrieve root element namespace
rootElementNamespace = uri.length() > 0 ? uri : null;
// Retrieve XSI schema location
xsiSchemaLocation = attributes.getValue(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, ATTRIBUTE_NAME_SCHEMA_LOCATION);
// Retrieve target namespace
targetNamespace = attributes.getValue("", ATTRIBUTE_NAME_TARGET_NAMESPACE); //$NON-NLS-1$
if (targetNamespace == null) {
targetNamespace = getTargetNamespaceFromDeclaredXMLNamespaces(qName, attributes);
}
// For the sake of performance stop parsing here!
throw new StopParsingException();
}
// TODO Avoid reflective accesses to internal fields by leveraging SAX parser feature
// http://xml.org/sax/features/namespace-prefixes; see http://xerces.apache.org/xerces-j/features.html for details
private String getTargetNamespaceFromDeclaredXMLNamespaces(String qName, Attributes attributes) {
try {
int separatorIndex = qName.indexOf(":");//$NON-NLS-1$
String prefix = separatorIndex != -1 ? qName.substring(0, separatorIndex) : ""; //$NON-NLS-1$
Object xmlAttributesObject = ReflectUtil.getInvisibleFieldValue(attributes, FIELD_NAME_ATTRIBUTES);
Object[] xmlAttributesArray = (Object[]) ReflectUtil.getInvisibleFieldValue(xmlAttributesObject, FIELD_NAME_ATTRIBUTES);
for (Object xmlAttribute : xmlAttributesArray) {
org.apache.xerces.xni.QName xQName = (org.apache.xerces.xni.QName) ReflectUtil.getInvisibleFieldValue(xmlAttribute, FIELD_NAME_NAME);
if ((ATTRIBUTE_PREFIX_XMLNS.equals(xQName.prefix) || ATTRIBUTE_PREFIX_XMLNS.equals(xQName.localpart))
&& !prefix.equals(xQName.localpart)) {
if (!ATTRIBUTE_NAME_XMI.equals(xQName.localpart) && !ATTRIBUTE_NAME_XSI.equals(xQName.localpart)) {
if (xQName.localpart != null) {
String value = (String) ReflectUtil.getInvisibleFieldValue(xmlAttribute, FIELD_NAME_VALUE);
if (!isExcludedTargetNamespace(value)) {
return value;
}
}
}
}
}
} catch (Exception ex) {
// Ignore exception, just return null
}
return null;
}
protected boolean isExcludedTargetNamespace(String value) {
if (value != null && value.trim().length() > 0 && targetNamespaceExcludePatterns != null) {
for (String excludePattern : targetNamespaceExcludePatterns) {
if (value.matches(excludePattern)) {
return true;
}
}
}
return false;
}
/*
* @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
*/
@Override
public void comment(char[] ch, int start, int length) throws SAXException {
// we store comments inside document
if (ch != null && length > 0 && start <= ch.length) {
String comment = new String(ch, start, length);
rootElementComments.add(comment);
}
}
/*
* @see org.xml.sax.ext.LexicalHandler#endCDATA()
*/
@Override
public void endCDATA() throws SAXException {
}
/*
* @see org.xml.sax.ext.LexicalHandler#endDTD()
*/
@Override
public void endDTD() throws SAXException {
}
/*
* @see org.xml.sax.ext.LexicalHandler#endEntity(java.lang.String)
*/
@Override
public void endEntity(String name) throws SAXException {
}
/*
* @see org.xml.sax.ext.LexicalHandler#startCDATA()
*/
@Override
public void startCDATA() throws SAXException {
}
/*
* @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 {
}
/*
* @see org.xml.sax.ext.LexicalHandler#startEntity(java.lang.String)
*/
@Override
public void startEntity(String name) throws SAXException {
}
public void parseContents(InputStream inputStream) throws IOException, ParserConfigurationException, SAXException {
parseContents(inputStream, false);
}
public void parseContents(InputStream inputStream, boolean useLexicalHandler) throws IOException, ParserConfigurationException, SAXException {
try {
if (inputStream != null) {
SAXParser parser = getParser(useLexicalHandler);
parser.parse(inputStream, this);
}
} catch (StopParsingException ex) {
// This exception is thrown when namespaces have been detected and parsing doesn't need to be
// proceeded
}
}
public void parseContents(InputSource inputSource) throws IOException, ParserConfigurationException, SAXException {
parseContents(inputSource, false);
}
public void parseContents(InputSource inputSource, boolean useLexicalHandler) throws IOException, ParserConfigurationException, SAXException {
try {
if (inputSource != null && inputSource.getByteStream() != null) {
SAXParser parser = getParser(useLexicalHandler);
parser.parse(inputSource.getByteStream(), this);
}
} catch (StopParsingException ex) {
// This exception is thrown when namespaces have been detected and parsing doesn't need to be
// proceeded
}
}
public String getRootElementNamespace() {
return rootElementNamespace;
}
public String getTargetNamespace() {
return targetNamespace;
}
public String getSchemaLocation() {
return xsiSchemaLocation;
}
/**
* Retrieves all comments located before the root element of the document.
*
* @return Collection of strings representing the retrieved comments or empty collection if no such could be found.
*/
public Collection<String> getRootElementComments() {
return rootElementComments;
}
public void setTargetNamespaceExcludePatterns(String... targetNamespaceExcludePatterns) {
this.targetNamespaceExcludePatterns = targetNamespaceExcludePatterns;
}
}