/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.xml;
import gw.config.CommonServices;
import gw.internal.ext.org.apache.xerces.jaxp.SAXParserFactoryImpl;
import gw.internal.ext.org.apache.xerces.jaxp.SAXParserImpl;
import gw.internal.xml.IXMLWriter;
import gw.internal.xml.TeeOutputStream;
import gw.internal.xml.XMLWriterFactory;
import gw.internal.xml.XmlDeserializationContext;
import gw.internal.xml.XmlElementInternals;
import gw.internal.xml.XmlMixedContentList;
import gw.internal.xml.XmlParserCallback;
import gw.internal.xml.XmlSchemaAccessImpl;
import gw.internal.xml.XmlSchemaLocalResourceResolver;
import gw.internal.xml.XmlSerializationContext;
import gw.internal.xml.XmlSimpleValueBase;
import gw.internal.xml.XmlSimpleValueValidationContext;
import gw.internal.xml.XmlTypeInstanceInternals;
import gw.internal.xml.XmlTypeResolver;
import gw.internal.xml.XmlUtil;
import gw.internal.xml.xsd.typeprovider.IXmlSchemaElementTypeData;
import gw.internal.xml.xsd.typeprovider.IXmlSchemaTypeInstanceTypeData;
import gw.internal.xml.xsd.typeprovider.IXmlType;
import gw.internal.xml.xsd.typeprovider.NotFoundException;
import gw.internal.xml.xsd.typeprovider.XmlSchemaElementTypeData;
import gw.internal.xml.xsd.typeprovider.XmlSchemaIndex;
import gw.internal.xml.xsd.typeprovider.XmlSchemaPropertySpec;
import gw.internal.xml.xsd.typeprovider.XmlSchemaResourceTypeLoaderBase;
import gw.internal.xml.xsd.typeprovider.XmlSchemaTypeInstanceTypeData;
import gw.internal.xml.xsd.typeprovider.XmlSchemaTypeSchemaInfo;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaAnyAttribute;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaAttribute;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaCollection;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaComplexType;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaType;
import gw.internal.xml.xsd.typeprovider.simplevaluefactory.IDREFSimpleValueFactory;
import gw.internal.xml.xsd.typeprovider.simplevaluefactory.XmlSimpleValueFactory;
import gw.internal.xml.xsd.typeprovider.validator.XmlSimpleValueValidator;
import gw.internal.xml.xsd.typeprovider.xmlmatcher.MatchFoundException;
import gw.internal.xml.xsd.typeprovider.xmlmatcher.XmlMatchHandler;
import gw.internal.xml.xsd.typeprovider.xmlmatcher.XmlSchemaAnyMatchHandler;
import gw.internal.xml.xsd.typeprovider.xmlsorter.XmlSorter;
import gw.lang.reflect.IConstructorInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.ITypeInfo;
import gw.lang.reflect.java.JavaTypes;
import gw.util.GosuExceptionUtil;
import gw.util.Pair;
import gw.util.StreamUtil;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.stream.StreamFilter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.Validator;
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;
@SuppressWarnings( { "UnusedDeclaration" } )
class XmlElementInternalsImpl extends XmlElementInternals {
private static final Pair<IType, IType> EMPTY_PAIR = new Pair<IType, IType>( null, null );
private static final XmlTypeResolver DEFAULT_TYPE_RESOLVER = new XmlTypeResolver() {
@Override
public Pair<IType, IType> resolveTypes( List<QName> elementStack ) {
return EMPTY_PAIR;
}
@Override
public Schema getSchemaForValidation() {
return null;
}
@Override
public String getValidationSchemasDescription() {
return null;
}
};
private static final QName XSI_TYPE_ATTRIBUTE_QNAME = new QName( XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "type" );
private static final QName XSI_NIL_ATTRIBUTE_QNAME = new QName( XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "nil" );
private static final Method _isSpecifiedMethod;
private static final XmlParseOptions DEFAULT_PARSE_OPTIONS = new XmlParseOptions();
static {
try {
Class<?> clazz = Class.forName( "gw.internal.ext.org.apache.xerces.parsers.AbstractSAXParser$AttributesProxy" );
_isSpecifiedMethod = clazz.getMethod( "isSpecified", int.class );
}
catch ( Exception ex ) {
throw GosuExceptionUtil.forceThrow( ex );
}
}
@Override
public void writeTo( final XmlElement element, OutputStream out, XmlSerializationOptions options ) {
if ( options == null ) {
options = new XmlSerializationOptions();
}
boolean validate;
final IType type = element._type;
final XmlSchemaIndex<?> schemaIndex = type == null ? null : XmlSchemaIndex.getSchemaIndexByType( type );
if ( type == null || ! options.getValidate()) {
validate = false;
}
else {
XmlSchemaElementTypeData typeData = (XmlSchemaElementTypeData) schemaIndex.getTypeData( type.getName() );
validate = ! typeData.isAnonymous();
}
if ( ! validate ) {
try {
writeWithoutValidation( element, out, new XmlSerializationContext(), options );
}
catch ( IOException ex ) {
throw GosuExceptionUtil.convertToRuntimeException( ex );
}
}
else {
try {
final XmlSerializationContext context = new XmlSerializationContext();
TeeOutputStream teeOut;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
teeOut = new TeeOutputStream( out, baos );
writeWithoutValidation( element, teeOut, context, options );
validate( context.getRequiredSchemas(), type, new ByteArrayInputStream( baos.toByteArray() ), element.toString(), false, null );
}
catch ( IOException ex ) {
throw new XmlException( "Unable to generate XML", ex );
}
}
}
private static int _cacheHits = 0;
private static int _cacheMisses = 0;
@Override
public void doDeclareNamespace( XmlElement element, String nsuri, String suggestedPrefix, Map<String, URI> _uriCache ) {
if ( nsuri == null ) {
throw new IllegalArgumentException("The value for the namespace URI must not be null");
}
if ( suggestedPrefix == null ) {
throw new IllegalArgumentException("The value for the suggestedPrefix parameter must not be null");
}
URI uri;
if ( _uriCache == null ) {
try {
uri = new URI( nsuri );
}
catch ( URISyntaxException e) {
throw GosuExceptionUtil.convertToRuntimeException( e );
}
}
else {
uri = _uriCache.get( nsuri );
if ( uri == null ) {
try {
uri = new URI( nsuri );
}
catch ( URISyntaxException e) {
throw GosuExceptionUtil.convertToRuntimeException( e );
}
_uriCache.put( nsuri, uri );
}
}
element._declaredNamespaceBindings.add( new Pair<String, URI>( suggestedPrefix, uri ) );
}
private void writeWithoutValidation( XmlElement element, OutputStream out, XmlSerializationContext context, XmlSerializationOptions options ) throws IOException {
IXMLWriter writer = XMLWriterFactory.newDefaultXMLWriter( out, options );
writeTo( element, writer, context, false );
writer.finish();
}
void writeTo( XmlElement element, IXMLWriter writer, XmlSerializationContext context, boolean mixed ) throws IOException {
context.pushScope(); // at beginning of each element, push namespace scope
context.setCurrentElement( element ); // so other parts of serialization can reference the current element
if ( element._comment != null ) {
writer.writeComment( " " + element._comment + " " );
}
writeAttributes( element, writer, context );
context.addRequiredSchema( element._type );
XmlTypeInstance typeInstance = element.getTypeInstance();
context.addRequiredSchema( XmlTypeInstanceInternals.instance().getType( typeInstance ) );
if ( typeInstance._schemaInfo != null ) {
mixed = typeInstance._schemaInfo.isMixed();
}
if ( mixed ) {
boolean pretty = writer.getWriterOptions().getPretty();
writer.getWriterOptions().setPretty( false );
writeContents( element, writer, context, mixed );
writer.endElement();
writer.getWriterOptions().setPretty( pretty );
}
else {
writeContents( element, writer, context, mixed );
writer.endElement();
}
context.popScope();
}
private void writeContents( XmlElement element, IXMLWriter writer, XmlSerializationContext context, boolean mixed ) throws IOException {
for ( XmlElement child : element.getChildren() ) {
context.addRequiredSchema( getType( child ) );
context.addRequiredSchema( XmlTypeInstanceInternals.instance().getType( child.getTypeInstance() ) );
}
// now, write out the element's ( possibly mixed ) contents
List<? extends IXmlMixedContent> childList = element.getTypeInstance()._children;
if ( ! mixed && XmlTypeInstanceInternals.instance().getType( element.getTypeInstance() ) != null && writer.getWriterOptions().getSort() ) {
// this element is based on an XSD - possibly sort it's contents to match the XSD
final XmlSchemaType xsdType = XmlTypeInstanceInternals.instance().getSchemaInfo( element.getTypeInstance() ).getXsdType();
// only sort contents for complex types
if ( xsdType instanceof XmlSchemaComplexType ) {
try {
LinkedList<XmlElement> remainingChildren = new LinkedList<XmlElement>( element.getChildren() );
childList = XmlSorter.sort( element.isNil() ? null : xsdType, remainingChildren, context.getRequiredSchemas() );
}
catch ( XmlSortException ex ) {
wrapAndThrowSortException( element, ex );
}
}
}
if ( element.getSimpleValue() == null ) {
for ( IXmlMixedContent child : childList ) {
if ( child instanceof XmlElement ) {
writeTo( (XmlElement) child, writer, context, mixed );
}
else if ( mixed ) {
writer.addText( ( (XmlMixedContentText) child ).getText() );
}
}
}
else {
assert element.getChildren().isEmpty();
( (XmlSimpleValueBase) element.getSimpleValue() ).writeTo( writer, context );
}
}
private void wrapAndThrowSortException( XmlElement element, XmlSortException ex ) {
final StringBuilder errorMessage = new StringBuilder( "Unable to process children of element " + element.getQName() + "." );
List<QName> missingChildren = ex.getQNames();
if ( ! missingChildren.isEmpty() ) {
errorMessage.append( " Expected one of " );
Map<String,Set<String>> sortedMissingChildren = new TreeMap<String, Set<String>>();
for ( QName qname : missingChildren ) {
Set<String> localParts = sortedMissingChildren.get( qname.getNamespaceURI() );
if ( localParts == null ) {
localParts = new TreeSet<String>();
sortedMissingChildren.put( qname.getNamespaceURI(), localParts );
}
localParts.add( qname.getLocalPart() );
}
boolean firstNsuri = true;
for ( Map.Entry<String,Set<String>> entry : sortedMissingChildren.entrySet() ) {
String nsuri = entry.getKey();
Set<String> localParts = entry.getValue();
if ( ! firstNsuri ) {
errorMessage.append( ", " );
}
firstNsuri = false;
errorMessage.append( "{" );
if ( ! nsuri.equals( XMLConstants.NULL_NS_URI ) ) {
errorMessage.append( "\"" );
}
errorMessage.append( nsuri );
if ( ! nsuri.equals( XMLConstants.NULL_NS_URI ) ) {
errorMessage.append( "\":" );
}
boolean firstLocalPart = true;
for ( String localPart : localParts ) {
if ( ! firstLocalPart ) {
errorMessage.append( ", " );
}
firstLocalPart = false;
errorMessage.append( localPart );
}
errorMessage.append( "}" );
}
throw new XmlSortException( errorMessage.toString() );
}
else {
//noinspection ThrowableResultOfMethodCallIgnored
throw new XmlSortException( errorMessage.toString(), (XmlSortException) GosuExceptionUtil.findExceptionCause( ex ) );
}
}
private void writeAttributes( XmlElement element, IXMLWriter writer, XmlSerializationContext context ) throws IOException {
// This is the list of namespaces to declare at this level. Some namespaces will already have been declared
// on ancestor elements, and need not be redeclared under most circumstances.
TreeMap<String,String> namespacesToDeclare = new TreeMap<String, String>();
// If null namespace is used anywhere in this element, the default namespace must be undeclared
declareNullNamespaceIfRequired( element, context, namespacesToDeclare );
// add any additional user-declared namespaces at this level
for ( Pair<String, URI> pair : element._declaredNamespaceBindings) {
addNamespace(context, pair.getFirst(), pair.getSecond().toString(), namespacesToDeclare, false, true );
}
IXmlType xmlType = (IXmlType) XmlTypeInstanceInternals.instance().getType( element.getTypeInstance() );
IXmlSchemaTypeInstanceTypeData typeData = xmlType == null ? null : (IXmlSchemaTypeInstanceTypeData) xmlType.getTypeData();
LinkedHashMap<QName, XmlSimpleValue> attributes = element.getTypeInstance()._attributes;
Map<QName, XmlSimpleValue> localAttributes = attributes == null ? null : new LinkedHashMap<QName, XmlSimpleValue>( attributes );
XmlSchemaTypeSchemaInfo schemaInfo = typeData == null ? null : typeData.getSchemaInfo();
// Attributes are special in that if they don't have a prefix, they don't inherit the default namespace...
// Therefore, check them first... If we're going to declare a namespace, declare it based on the attribute
// prefix naming rules... Then that namespace could be reused elsewhere in the element if necessary... Without
// doing this, we can end up with multiple namespace declarations on a single element for the same namespace.
//
// For example, we could end up with <root ns0:attr="value" xmlns="foo" xmlns:ns0="foo"/>
// This would happen because we checked the namespace of the element first, decided it needed to be declared,
// declared it as mapped to the empty prefix, then when we got to the attribute, we can't use the empty prefix
// for an attribute obviously, and must declare it again.
if ( localAttributes != null ) {
for ( QName attributeQName : localAttributes.keySet() ) {
// No point in declaring the null namespace for an attribute since it gets it by default anyway when no prefix is specified
if ( ! XMLConstants.NULL_NS_URI.equals( attributeQName.getNamespaceURI() ) ) {
addNamespace( context, attributeQName.getPrefix(), attributeQName.getNamespaceURI(), namespacesToDeclare, true, false );
}
}
}
// add element namespace to namespaces ( may or may not cause declaration )
String elementQNamePrefix = addNamespace( context, element.getQName().getPrefix(), element.getQName().getNamespaceURI(), namespacesToDeclare, false, false );
writer.startElement( makeQNameString( elementQNamePrefix, element.getQName().getLocalPart() ) );
// write out attributes of this element
// first write attributes declared in schema, in the order they are declared in schema
if ( localAttributes != null ) {
if ( schemaInfo != null ) {
for ( QName attributeQName : schemaInfo.getAttributeNames() ) {
XmlSimpleValue attributeSimpleValue = localAttributes.remove( attributeQName );
if ( attributeSimpleValue != null ) {
writeAttribute( writer, context, namespacesToDeclare, attributeQName, attributeSimpleValue );
}
}
}
for ( Map.Entry<QName, XmlSimpleValue> entry : localAttributes.entrySet() ) {
QName attributeQName = entry.getKey();
XmlSimpleValue attributeSimpleValue = entry.getValue();
writeAttribute( writer, context, namespacesToDeclare, attributeQName, attributeSimpleValue );
}
}
// add namespaces for any qnames of any of the element's simple value contents
if ( element.getSimpleValue() != null ) {
for ( QName qname : element.getSimpleValue().getQNames() ) {
addNamespace( context, qname.getPrefix(), qname.getNamespaceURI(), namespacesToDeclare, false, false );
}
}
// add xsi:type if required
QName typeInstanceQName = getExplicitTypeInstanceQName( element );
if ( typeInstanceQName != null ) {
String xsiPrefix = addNamespace( context, "xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, namespacesToDeclare, true, false );
String typeInstancePrefix = addNamespace( context, "", typeInstanceQName.getNamespaceURI(), namespacesToDeclare, false, false );
writer.addAttribute( makeQNameString( xsiPrefix, "type" ), makeQNameString( typeInstancePrefix, typeInstanceQName.getLocalPart() ) );
}
// add xsi:nil if required
if ( element.isNil() ) {
String xsiPrefix = addNamespace( context, "xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, namespacesToDeclare, true, false );
writer.addAttribute( makeQNameString( xsiPrefix, "nil" ), "true" );
}
// finally, actually declare any namespaces that must be declared
for ( Map.Entry<String,String> entry : namespacesToDeclare.entrySet() ) {
writer.addAttribute( makeXmlnsString( entry.getKey() ), entry.getValue() );
}
}
private QName getExplicitTypeInstanceQName( XmlElement element ) {
IType typeInstanceTypeRef = XmlTypeInstanceInternals.instance().getType( element.getTypeInstance() );
boolean includeXsiType = ( element._schemaDefinedTypeInstanceType == null && ! isAnyType( typeInstanceTypeRef ) )
|| ( element._schemaDefinedTypeInstanceType != null && ! element._schemaDefinedTypeInstanceType.equals( typeInstanceTypeRef ) );
if ( includeXsiType ) {
String typeInstanceTypeName = typeInstanceTypeRef.getName();
XmlSchemaIndex<?> typeInstanceSchemaIndex = XmlSchemaIndex.getSchemaIndexByType( typeInstanceTypeRef );
XmlSchemaTypeInstanceTypeData typeInstanceTypeData = (XmlSchemaTypeInstanceTypeData) typeInstanceSchemaIndex.getActualTypeByName( typeInstanceTypeName );
if ( ! typeInstanceTypeData.isAnonymous() ) {
return typeInstanceTypeData.getSchemaInfo().getXsdType().getQName();
}
}
return null;
}
private void writeAttribute( IXMLWriter writer, XmlSerializationContext context, TreeMap<String, String> namespacesToDeclare, QName attributeQName, XmlSimpleValue attributeSimpleValue ) throws IOException {
final String attributeQNamePrefix;
if ( attributeQName.getNamespaceURI().equals( XMLConstants.NULL_NS_URI ) ) {
attributeQNamePrefix = XMLConstants.DEFAULT_NS_PREFIX; // unqualified attributes are always in the null namespace
}
else {
attributeQNamePrefix = addNamespace( context, attributeQName.getPrefix(), attributeQName.getNamespaceURI(), namespacesToDeclare, true, false );
}
// add namespaces for any qnames of the attribute's simple value ( xsd:QName derived by xsd:list could contain multiple )
for ( QName qname : attributeSimpleValue.getQNames() ) {
addNamespace( context, qname.getPrefix(), qname.getNamespaceURI(), namespacesToDeclare, false, false );
}
// finally, write the attribute itself
writer.addAttribute( makeQNameString( attributeQNamePrefix, attributeQName.getLocalPart() ), ( (XmlSimpleValueBase) attributeSimpleValue ).serialize( context ) );
}
private void declareNullNamespaceIfRequired( XmlElement element, XmlSerializationContext context, TreeMap<String, String> namespacesToDeclare ) throws IOException {
// The null ns uri can only be assigned to default prefix. The reason is that, according to the spec, a
// declaration such as xmlns="" is actually *undeclaring* the namespace (restoring the "lack of a namespace" as
// the default namespace). A declaration like xmlns:foo="", however, undefines the "foo" prefix, making it
// unusable. I will refer to this "lack of a namespace" as "being in the null namespace" since it's an easier
// concept to understand (at least for me). I read that XML 2.0 is planning on actually removing the concept
// of a "lack of a namespace" altogether, and replacing it with the concept of an "empty-string" namespace,
// which could be assigned to any prefix, which is essentially XMLConstants.NULL_NS_URI
// See http://www.w3.org/TR/xml-names11/ for more information on undeclaring namespaces
// So therefore, if this element is in the null namespace, and since the null namespace can only be
// used as a default namespace (not prefixed), then any existing default namespace MUST be undeclared.
// Unfortunately, this concept applies to all attribute values that are xsd:QName simple values...
// AND to the element content itself if it contains any xsd:QName simple values. (We allow multiple)
// It also applies to any simple values derived from QName, including those derived by xsd:list, which
// means a single list entry of a single attribute value of type xsd:QName-derivedby-xsd:List could possibly
// contain a single QName entry in the null namespace, requiring us to undeclare the default namespace...
// All of these places must be checked, and all of the following logic is simply to determine whether we should
// add an xmlns="" declaration (undeclaration) to this element, and all of this has to be done before
// writing out the first non-namespace-declaration attribute.
// We need not check attribute names, however, since an attribute name is always in the null
// namespace unless explicitly prefixed
boolean nullNsUriUsed = element.getQName().getNamespaceURI().equals( XMLConstants.NULL_NS_URI ); // check element
if ( ! nullNsUriUsed ) {
// check simple value contents
if ( element.getSimpleValue() != null ) {
for ( QName qname : element.getSimpleValue().getQNames() ) {
if ( qname.getNamespaceURI().equals( XMLConstants.NULL_NS_URI ) ) {
nullNsUriUsed = true;
break;
}
}
}
}
if ( ! nullNsUriUsed ) {
// check attribute simple value contents
LinkedHashMap<QName, XmlSimpleValue> attributes = element.getTypeInstance()._attributes;
if ( attributes != null ) {
OUTER:
for ( XmlSimpleValue content : attributes.values() ) {
for ( QName qname : content.getQNames() ) {
if ( qname.getNamespaceURI().equals( XMLConstants.NULL_NS_URI ) ) {
nullNsUriUsed = true;
break OUTER;
}
}
}
}
}
if ( ! nullNsUriUsed ) {
// check declared namespace bindings
for ( Pair<String, URI> pair : element._declaredNamespaceBindings ) {
if ( pair.getSecond().toString().equals( XMLConstants.NULL_NS_URI ) ) {
nullNsUriUsed = true;
break;
}
}
}
// if we are to include an xsi:type, the namespace of the explicit type might be in the null namespace
if ( ! nullNsUriUsed ) {
final QName typeInstanceQName = getExplicitTypeInstanceQName( element );
if ( typeInstanceQName != null && typeInstanceQName.getNamespaceURI().equals( XMLConstants.NULL_NS_URI ) ) {
nullNsUriUsed = true;
}
}
if ( nullNsUriUsed ) {
// undeclare the default namespace
addNamespace( context, XMLConstants.DEFAULT_NS_PREFIX, XMLConstants.NULL_NS_URI, namespacesToDeclare, false, false );
}
}
private String makeQNameString(String prefix, String localPart) {
String qnameString;
if ( prefix.equals( XMLConstants.DEFAULT_NS_PREFIX ) ) {
qnameString = localPart;
}
else {
qnameString = prefix + ':' + localPart;
}
return qnameString;
}
private String makeXmlnsString( String prefix ) {
String xmlnsString;
if ( prefix.equals( XMLConstants.DEFAULT_NS_PREFIX ) ) {
xmlnsString = "xmlns";
}
else {
xmlnsString = "xmlns:" + prefix;
}
return xmlnsString;
}
private String addNamespace( XmlSerializationContext context, String suggestedPrefix, String nsuri, TreeMap<String, String> namespacesToDeclare, boolean isAttribute, boolean redefineIfAlreadyDeclared ) throws IOException {
if ( XMLConstants.NULL_NS_URI.equals( nsuri ) ) {
suggestedPrefix = XMLConstants.DEFAULT_NS_PREFIX; //Non-default namespace can not map to empty URI (as per Namespace 1.0 # 2) in XML 1.0 documents
}
else if ( XMLConstants.XML_NS_URI.equals( nsuri ) ) {
return XMLConstants.XML_NS_PREFIX; // See http://www.w3.org/XML/1998/namespace - no need to declare specifically
}
else if ( suggestedPrefix.toLowerCase().startsWith( "xml" ) ) {
suggestedPrefix = ""; // prefixes starting with "[Xx][Mm][Ll]" are all reserved
}
// use suggested prefix if already defined as this nsuri
boolean declareNamespaceAtThisLevel = false;
String newPrefix = suggestedPrefix;
String existingNSURI = ( isAttribute && suggestedPrefix.equals( XMLConstants.DEFAULT_NS_PREFIX ) ) ? XMLConstants.NULL_NS_URI : context.getPrefixToNamespaceUriMap().get( suggestedPrefix );
if ( ! nsuri.equals( existingNSURI ) ) {
// use an existing prefix defined as this nsuri if possible
TreeSet<String> availablePrefixes = redefineIfAlreadyDeclared ? null : context.getNamespaceUriToPrefixMap().get( nsuri ); // use existing prefix if possible
boolean mustCreateNewPrefix = true;
if ( availablePrefixes != null && ! availablePrefixes.isEmpty() ) {
for ( String prefix : availablePrefixes ) {
if ( isAttribute && prefix.equals( XMLConstants.DEFAULT_NS_PREFIX ) && ! nsuri.equals( XMLConstants.NULL_NS_URI ) ) {
continue; // we can't use the default ns prefix as an attribute prefix unless that attribute exists in the null namespace
}
newPrefix = prefix;
mustCreateNewPrefix = false;
break;
}
}
if ( mustCreateNewPrefix ) {
// if we got here, then we must create a new prefix... first use suggested prefix if not already defined at this level
newPrefix = suggestedPrefix;
if ( context.getPrefixToNamespaceUriMap().containsKeyDirect( newPrefix ) || ( isAttribute && newPrefix.equals( XMLConstants.DEFAULT_NS_PREFIX ) && ! nsuri.equals( XMLConstants.NULL_NS_URI ) ) ) {
// there's no point in declaring a minted prefix if the namespace is already declared directly on this element using some other prefix
boolean mustReallyCreateNewPrefix = true;
TreeSet<String> existingPrefixes = context.getNamespaceUriToPrefixMap().getDirect( nsuri );
if ( existingPrefixes != null && ! existingPrefixes.isEmpty() ) {
for ( String prefix : existingPrefixes ) {
if ( isAttribute && prefix.equals( XMLConstants.DEFAULT_NS_PREFIX ) && ! nsuri.equals( XMLConstants.NULL_NS_URI ) ) {
continue; // we can't use the default ns prefix as an attribute prefix unless that attribute exists in the null namespace
}
newPrefix = prefix;
mustReallyCreateNewPrefix = false;
break;
}
}
if ( mustReallyCreateNewPrefix ) {
newPrefix = makeNewNamespacePrefix( context, newPrefix );
declareNamespaceAtThisLevel = true;
}
}
else {
declareNamespaceAtThisLevel = true;
}
}
}
if ( declareNamespaceAtThisLevel ) {
String old = namespacesToDeclare.put( newPrefix, nsuri );
if ( old != null ) {
throw new IllegalStateException( "Expected old namespace to be null, but was " + old );
}
}
// redefine at this level even if already defined, to ensure we don't try to redefine prefix again at this level
String oldNsUri = context.getPrefixToNamespaceUriMap().get( newPrefix );
if ( oldNsUri != null ) {
// the old nsuri to prefix mapping is no longer valid
context.getNamespaceUriToPrefixMap().put( oldNsUri, null );
}
TreeSet<String> prefixes = context.getNamespaceUriToPrefixMap().get( nsuri );
if ( prefixes == null ) {
prefixes = new TreeSet<String>();
context.getNamespaceUriToPrefixMap().put( nsuri, prefixes );
}
else {
prefixes = new TreeSet<String>( prefixes ); // make copy at this level so that we don't affect the exiting set
}
prefixes.add( newPrefix );
context.getPrefixToNamespaceUriMap().put( newPrefix, nsuri );
return newPrefix;
}
private String makeNewNamespacePrefix(XmlSerializationContext context, String newPrefix) {
// last resort, invent a new prefix and use it
String prefixBase;
int suffix;
if ( newPrefix.length() == 0 ) {
prefixBase = "ns";
suffix = 0;
}
else {
prefixBase = newPrefix;
suffix = 2;
}
do {
// prefix already in use
newPrefix = prefixBase + suffix++;
} while ( context.getPrefixToNamespaceUriMap().containsKey( newPrefix ) );
return newPrefix;
}
public XmlElement parse( final IType type, InputSource source, final String description, List<XmlSchemaIndex> schemaIndexes, XmlTypeResolver typeResolver, XmlParseOptions options, final XmlParserCallback callback, final URL schemaEF ) {
if ( options == null ) {
options = DEFAULT_PARSE_OPTIONS;
}
final XmlParseOptions foptions = options;
SAXParserImpl parser;
try {
SAXParserFactoryImpl factory = new SAXParserFactoryImpl();
XmlSchemaIndex index = null;
if ( type != null ) {
IXmlSchemaElementTypeData<?> typeData = (IXmlSchemaElementTypeData<?>) type;
if ( typeData.isAnonymous() ) {
throw new XmlException( "parse() cannot be called on an anonymous element type" );
}
index = XmlSchemaIndex.getSchemaIndexByType( type );
}
schemaIndexes = addSchemaIndexesFromList( index, schemaIndexes );
//noinspection deprecation
if ( options.getValidate() ) {
if ( typeResolver != null && typeResolver.getSchemaForValidation() != null ) {
factory.setSchema( typeResolver.getSchemaForValidation() );
}
else if ( schemaIndexes.size() > 0 ) {
factory.setSchema( XmlSchemaIndex.parseSchemas( schemaIndexes ) );
}
}
factory.setNamespaceAware( true );
factory.setFeature( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false );
// validation in the xerces sense refers to DTD validation, and has nothing to do with schemas
factory.setFeature( "http://xml.org/sax/features/validation", false );
parser = (SAXParserImpl) factory.newSAXParser();
}
catch ( Throwable t ) {
throw new XmlException( "Unable to create parser for " + description + ( type == null ? "" : " using schema root " + type.getName() ), t );
}
final Locator[] documentLocator = { null };
try {
final XmlTypeResolver[] ftypeResolver = { typeResolver == null ? DEFAULT_TYPE_RESOLVER : typeResolver };
final List<XmlSchemaIndex> fschemaIndexes = schemaIndexes;
final XmlElement[] rootElement = { null };
final IType xmlElementType = JavaTypes.getSystemType(XmlElement.class);
final XmlSchemaCollection collection = new XmlSchemaCollection();
for ( XmlSchemaIndex schemaIndex : fschemaIndexes ) {
addSchemaToCollection( collection, schemaIndex );
}
//parser.setProperty( "http://apache.org/xml/properties/internal/entity-resolver", resolver );
parser.parse( source, new DefaultHandler2() {
private LinkedList<QName> _elementStack = new LinkedList<QName>();
private XmlDeserializationContext _context = new XmlDeserializationContext( new XmlDeserializationContext( null ) );
private LinkedHashMap<String, String> _locallyDeclaredNamespaces = new LinkedHashMap<String, String>();
private Map<String,URI> _uriCache = new HashMap<String, URI>();
{
_context.setMatchHandler( new XmlSchemaAnyMatchHandler( null ) ); // for root element
}
@Override
public void setDocumentLocator( Locator locator ) {
documentLocator[0] = locator;
}
@Override
public void endDocument() throws SAXException {
if ( ! _elementStack.isEmpty() ) {
throw new IllegalStateException( "Expected element stack to be empty" );
}
// do an assignable check, since this might be a member of this element's substitution group
// schema does not make a distinction, since any top level element is a valid root, but we can
IType expectedRootElementType = type;
if ( expectedRootElementType == null ) {
expectedRootElementType = xmlElementType;
}
IType actualType = rootElement[0].getIntrinsicType();
if ( ! expectedRootElementType.isAssignableFrom( actualType ) ) {
throw new XmlException( "Invalid root element. Expected " + expectedRootElementType + ", but was " + actualType + ", QName " + rootElement[0].getQName() );
}
linkIdrefs( _context );
}
@Override
public void endElement( String uri, String localName, String qName ) throws SAXException {
_elementStack.removeLast();
XmlElement parent = _context.getCurrentElement();
XmlTypeInstance typeInstance = parent.getTypeInstance();
// the only time we don't add a simple value for an element with no child elements is when
// this element is based on a schema, and its type is defined as a complex type
XmlSchemaTypeSchemaInfo schemaInfo = _context.getSchemaInfo();
if ( ( schemaInfo == null || schemaInfo.hasSimpleContent() ) && typeInstance._children.isEmpty() ) {
// simple type
XmlSimpleValue simpleValue;
if ( schemaInfo != null ) {
String text = schemaInfo.getValidator().collapseWhitespace( _context.getAllText().toString(), new XmlSimpleValueValidationContext() );
// if text length is zero, and this element has a default value, the default value is used
if ( ! parent.isNil() ) {
simpleValue = schemaInfo.getSimpleValueFactory().deserialize( _context, text, false );
typeInstance.setSimpleValue( simpleValue );
}
}
else {
simpleValue = XmlSimpleValue.makeStringInstance( _context.getAllText().toString() );
typeInstance.setSimpleValue( simpleValue );
}
}
_context = _context.getParent();
}
@Override
public void startElement( String uri, String localName, String qName, Attributes attributes ) throws SAXException {
try {
XmlElement element;
String[] parts = XmlUtil.splitQName( qName );
QName elementName = new QName( uri, parts[1], parts[0] );
_elementStack.add( elementName );
Pair<IType, IType> types = ftypeResolver[0].resolveTypes( _elementStack );
XmlMatchHandler matchHandler = _context.getMatchHandler();
IType actualType;
if ( matchHandler == null ) {
actualType = null;
}
else {
try {
matchHandler.match( elementName, collection );
//noinspection deprecation
if ( foptions.getValidate() ) {
// if we managed to validate the xml against the schema, and we still can't match
// this element to the schema, we did something wrong internally
throw new IllegalStateException( "Unable to determine IType for element " + elementName );
}
else {
actualType = null;
}
}
catch ( MatchFoundException ex ) {
actualType = ex.getType();
}
}
if ( actualType == null ) {
actualType = types.getFirst();
if ( actualType == null ) {
actualType = xmlElementType;
}
}
if ( actualType.equals( xmlElementType ) ) {
element = new XmlElement( elementName );
}
else {
ITypeInfo typeInfo = actualType.getTypeInfo();
if( typeInfo == null )
{
throw new IllegalStateException( "No type information found for " + actualType.getName() );
}
IConstructorInfo ci = typeInfo.getConstructor();
if( ci == null )
{
throw new IllegalStateException( actualType.getName() + " has no default constructor" );
}
element = (XmlElement) ci.getConstructor().newInstance();
}
if ( rootElement[0] == null ) {
rootElement[0] = element;
}
else {
_context.getCurrentElement().addChild( element );
}
_context = new XmlDeserializationContext( _context );
_context.setCurrentElement( element );
for ( Map.Entry<String, String> entry : _locallyDeclaredNamespaces.entrySet() ) {
_context.addNamespace( entry.getKey(), entry.getValue() );
}
XmlElementInternals.instance().addNamespacesToElementFromParse( element, _context.getNamespaces(), _uriCache );
resolveXsiTypeAndNil( element, fschemaIndexes, types.getSecond(), attributes, _context );
XmlTypeInstance typeInstance = element.getTypeInstance();
XmlSchemaTypeSchemaInfo schemaInfo = XmlTypeInstanceInternalsImpl.instance().getSchemaInfo( typeInstance );
_context.setSchemaInfo( schemaInfo );
if ( schemaInfo != null ) {
_context.setMixed( schemaInfo.isMixed() );
_context.setMatchHandler( XmlMatchHandler.getMatchHandler( _context.getSchemaInfo().getXsdType() ) );
}
for ( int i = 0; i < attributes.getLength(); i++ ) {
if ( ! (Boolean) _isSpecifiedMethod.invoke( attributes, i ) ) {
continue;
}
String attrQName = attributes.getQName( i );
parts = XmlUtil.splitQName( attrQName );
QName attributeName = new QName( attributes.getURI( i ), parts[1], parts[0] );
if ( typeInstance._type != null && attributeName.equals( XSI_TYPE_ATTRIBUTE_QNAME ) ) {
continue; // we handle xsi:type, not the user
}
else if ( attributeName.equals( XSI_NIL_ATTRIBUTE_QNAME ) ) {
continue; // we handle xsi:nil, not the user
}
String attributeValue = attributes.getValue( i );
boolean wroteValue = false;
if ( schemaInfo != null ) {
XmlSchemaPropertySpec property = schemaInfo.getPropertyByAttributeName( attributeName );
if ( property != null ) {
attributeValue = property.getValidator().collapseWhitespace( attributeValue, new XmlSimpleValueValidationContext() );
XmlSimpleValue storageValue = property.getSimpleValueFactory().deserialize( _context, attributeValue, false );
typeInstance.setAttributeSimpleValue( attributeName, storageValue );
wroteValue = true;
}
else if ( schemaInfo.getXsdType().getAnyAttributeRecursiveIncludingSupertypes() != null ) {
XmlSchemaAnyAttribute anyAttribute = schemaInfo.getXsdType().getAnyAttributeRecursiveIncludingSupertypes();
if ( anyAttribute.getProcessContents() != XmlSchemaAnyAttribute.ProcessContents.skip ) {
XmlSchemaAttribute attribute = null;
for ( XmlSchemaIndex schemaIndex : fschemaIndexes ) {
try {
attribute = schemaIndex.getXmlSchemaAttributeByQName( attributeName );
break;
}
catch ( NotFoundException ex ) {
// continue
}
}
if ( attribute != null ) {
XmlSchemaType schemaType = XmlSchemaIndex.getSchemaTypeForAttribute( attribute );
XmlSimpleValueValidator simpleValueValidator = XmlSchemaIndex.getSimpleValueValidatorForSchemaType( schemaType );
XmlSimpleValueFactory simpleValueFactory = XmlSchemaIndex.getSimpleValueFactoryForSchemaType( schemaType );
attributeValue = simpleValueValidator.collapseWhitespace( attributeValue, new XmlSimpleValueValidationContext() );
XmlSimpleValue storageValue = simpleValueFactory.deserialize( _context, attributeValue, false );
typeInstance.setAttributeSimpleValue( attributeName, storageValue );
wroteValue = true;
}
else if ( anyAttribute.getProcessContents() == XmlSchemaAnyAttribute.ProcessContents.strict ) {
throw new XmlException( "The attribute " + attributeName + " matching xs:anyAttribute could not be resolved to an attribute definition." );
}
}
}
}
if ( ! wroteValue ) {
// fall back to non-typesafe write
typeInstance.setAttributeValue( attributeName, attributeValue );
}
}
if( callback != null ) {
callback.onStartElement( documentLocator[0], element, schemaEF );
}
}
catch ( Throwable t ) {
throw new XmlException( "Unable to parse XML from " + description + ( type == null ? "" : " using schema root " + type.getName() ), t );
}
}
@Override
public void characters( char[] ch, int start, int length ) throws SAXException {
if ( _context.isMixed() ) {
// Unfortunately, Xerces doesn't do what we're doing here, it can send us a big block of text in multiple chunks.
// This isn't the most efficient way to do this, but hopefully it doesn't happen that often.
String text = String.valueOf( ch, start, length );
XmlMixedContentList children = _context.getCurrentElement().getTypeInstance()._children;
if ( ! children.isEmpty() ) {
IXmlMixedContent previousMixedContent = children.get( children.size() - 1 );
if ( previousMixedContent instanceof XmlMixedContentText ) {
text = ( (XmlMixedContentText) previousMixedContent ).getText() + text;
children.set( children.size() - 1, new XmlMixedContentText( text ) ); // replace existing mixed content text with new version that has additional text appended to it
return;
}
}
children.add( new XmlMixedContentText( text ) );
}
else {
_context.getAllText().append( ch, start, length );
}
}
@Override
public void startPrefixMapping( String prefix, String uri ) throws SAXException {
_locallyDeclaredNamespaces.put( prefix, uri );
}
@Override
public void endPrefixMapping( String prefix ) throws SAXException {
_locallyDeclaredNamespaces.remove( prefix );
}
@Override
public void error( SAXParseException e ) throws SAXException {
throw e;
}
@Override
public void fatalError( SAXParseException e ) throws SAXException {
throw e;
}
@Override
public void warning( SAXParseException e ) throws SAXException {
/* ignore */
}
} );
return rootElement[0];
}
catch ( Exception ex ) {
StringBuilder sb = new StringBuilder();
sb.append( "Unable to parse XML from " );
sb.append( description );
boolean firstUsing = true;
if ( typeResolver != null && typeResolver.getValidationSchemasDescription() != null ) {
if ( firstUsing ) {
sb.append( " using " );
}
else {
sb.append( " and " );
}
firstUsing = false;
sb.append( typeResolver.getValidationSchemasDescription() );
}
else if ( ! schemaIndexes.isEmpty() ) {
TreeSet<String> sortedSet = new TreeSet<String>();
for ( XmlSchemaIndex schemaIndex : schemaIndexes ) {
sortedSet.add( schemaIndex.getPackageName() );
}
if ( firstUsing ) {
sb.append( " using" );
}
else {
sb.append( " and" );
}
firstUsing = false;
sb.append( " schemas [" );
boolean first = true;
for ( String schemaPackageName : sortedSet ) {
if ( ! first ) {
sb.append( ", " );
}
first = false;
sb.append( schemaPackageName );
}
sb.append( "]" );
}
if ( type != null ) {
if ( firstUsing ) {
sb.append( " using" );
}
else {
sb.append( " and" );
}
//noinspection UnusedAssignment
firstUsing = false;
sb.append( " schema root " );
sb.append( type.getName() );
}
if ( documentLocator[0] != null ) {
sb.append( " at line " );
sb.append( documentLocator[ 0 ].getLineNumber() );
sb.append( " column " );
sb.append( documentLocator[ 0 ].getColumnNumber() );
}
throw new XmlException( sb.toString(), ex );
}
}
private void addSchemaToCollection( XmlSchemaCollection collection, XmlSchemaIndex<?> schemaIndex ) {
addSchemaToCollection( collection, schemaIndex, new HashSet<String>() );
}
private void addSchemaToCollection( XmlSchemaCollection collection, XmlSchemaIndex<?> schemaIndex, Set<String> addedGosuNamespaces ) {
if ( addedGosuNamespaces.add( schemaIndex.getPackageName() ) ) {
collection.addSchemas( schemaIndex.getXmlSchemaCollection().getXmlSchemas() );
for ( Set<String> gosuNamespaces : schemaIndex.getGosuNamespacesByXmlNamespace().values() ) {
for ( String gosuNamespace : gosuNamespaces ) {
addSchemaToCollection( collection, XmlSchemaResourceTypeLoaderBase.findSchemaForNamespace(schemaIndex.getTypeLoader().getModule(), gosuNamespace), addedGosuNamespaces );
}
}
}
}
@Override
public void addNamespacesToElementFromParse( XmlElement element, Map<String, String> allNamespaces, Map<String, URI> _uriCache ) {
for ( Map.Entry<String, String> entry : allNamespaces.entrySet() ) {
String prefix = entry.getKey();
String nsUri = entry.getValue();
if ( prefix.equals( "xmlns" ) ) {
// Xerces gives us this prefix, but we shouldn't declare it explicitly
continue;
}
if ( prefix.equals( "xml" ) ) {
// Xerces gives us this prefix, but we shouldn't declare it explicitly
continue;
}
element.declareNamespace( new XmlNamespace( nsUri, prefix ) );
}
}
public void resolveXsiTypeAndNil( XmlElement element, List<XmlSchemaIndex> schemaIndexes, IType typeInstanceType, Attributes attributes, XmlDeserializationContext context ) {
String xsiType = attributes.getValue( XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "type" );
IType gosuType;
if ( xsiType != null ) {
XmlSchemaType xsdType = null;
String[] parts = XmlUtil.splitQName( xsiType );
String nsURI = context.getNamespaces().get( parts[0] );
QName qname = new QName( nsURI, parts[1] );
for ( XmlSchemaIndex schemaIndex : schemaIndexes ) {
try {
xsdType = schemaIndex.getXmlSchemaTypeByQName( qname );
break;
}
catch ( NotFoundException ex ) {
// not a match
}
}
if ( xsdType == null ) {
// throw new XmlException( "Unable to resolve xsi:type " + qname );
return; // same as finding an element that matches an xsd:any with processContents=lax which is not found in the schema?
}
gosuType = XmlSchemaIndex.getGosuTypeBySchemaObject( xsdType );
}
else {
gosuType = typeInstanceType;
}
if ( gosuType != null ) {
XmlTypeInstance typeInstance = (XmlTypeInstance) gosuType.getTypeInfo().getConstructor().getConstructor().newInstance();
element.setTypeInstance( typeInstance );
}
String nil = attributes.getValue( XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "nil" );
if ( nil != null ) {
nil = nil.trim();
if ( nil.equals( "true" ) || nil.equals( "1" ) ) {
element.setNil( true );
}
}
}
private void linkIdrefs( XmlDeserializationContext context ) {
final Map<String, XmlElement> ids = context.getIds();
for ( Pair<String, IDREFSimpleValueFactory.Value> pair : context.getIdrefs() ) {
final String id = pair.getFirst();
XmlElement element = ids.get( id );
if ( element == null ) {
throw new XmlException( "Unable to find ID for IDREF " + id );
}
final IDREFSimpleValueFactory.Value idref = pair.getSecond();
idref.setElement( element );
}
}
public XmlElement parse( IType type, InputStream stream, String description, boolean validating, HashMap<String, BinaryData> attachments, XmlParseOptions options, String systemId ) {
return parseInternal( type, stream, description, options );
}
public XmlElement parse( IType type, Reader stream, String description, boolean validating, HashMap<String, BinaryData> attachments, XmlParseOptions options ) {
return parseInternal( type, stream, description, options );
}
public XmlElement parseInternal( IType type, Object stream, String description, XmlParseOptions options ) {
final XmlSchemaIndex<?> schemaIndex = type == null ? null : XmlSchemaIndex.getSchemaIndexByType( type );
List<XmlSchemaIndex> schemaIndexes = addSchemaIndexesFromParseOptions( schemaIndex, options );
InputSource source;
if ( stream instanceof InputStream ) {
source = new InputSource( (InputStream) stream );
}
else {
source = new InputSource( (Reader) stream );
}
return parse( type, source, description, schemaIndexes, null, options, null, null );
}
StreamSource validate( List<XmlSchemaIndex> schemaIndexes, IType type, InputStream stream, String description, boolean isXOP, String systemId ) {
try {
byte[] content = StreamUtil.getContent(stream);
XMLInputFactory factory = XMLInputFactory.newInstance();
StreamSource streamSource = new StreamSource( new ByteArrayInputStream( content ), systemId );
if ( isXOP ) {
XMLStreamReader parser = factory.createXMLStreamReader( streamSource );
validate( schemaIndexes, type, new StAXSource( wrapInXOPFilter( factory, parser ) ), description );
}
else {
validate( schemaIndexes, type, streamSource, description );
}
return new StreamSource( new ByteArrayInputStream( content ), systemId );
}
catch ( Exception ex ) {
throw new XmlException( "Could not parse " + description, ex );
}
}
StreamSource validate( List<XmlSchemaIndex> schemaIndexes, IType type, Reader stream, String description, boolean isXOP ) {
try {
String content = StreamUtil.getContent(stream);
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader parser = factory.createXMLStreamReader( new StringReader( content ) );
if ( isXOP ) {
validate( schemaIndexes, type, new StAXSource( wrapInXOPFilter( factory, parser ) ), description );
}
else {
validate( schemaIndexes, type, new StreamSource( new StringReader( content ) ), description );
}
return new StreamSource( new StringReader( content ) );
}
catch ( Exception ex ) {
throw new XmlException( "Could not parse " + description, ex );
}
}
private XMLStreamReader wrapInXOPFilter( XMLInputFactory factory, XMLStreamReader parser ) throws XMLStreamException {
parser = factory.createFilteredReader( parser, new StreamFilter() {
private int _ignoreLevel = 0;
@Override
public boolean accept( XMLStreamReader reader ) {
switch ( reader.getEventType() ) {
case XMLStreamConstants.START_ELEMENT: {
if ( _ignoreLevel > 0 || reader.getName().equals( XmlElement.XOP_INCLUDE_QNAME ) ) {
_ignoreLevel++;
}
break;
}
case XMLStreamConstants.END_ELEMENT: {
if ( _ignoreLevel == 1 ) {
_ignoreLevel--;
return false;
}
if ( --_ignoreLevel < 0 ) {
_ignoreLevel = 0;
}
break;
}
}
return _ignoreLevel == 0;
}
} );
return parser;
}
private List<XmlSchemaIndex> addSchemaIndexesFromList( XmlSchemaIndex schemaIndex, List<XmlSchemaIndex> list ) {
if ( list == null || list.isEmpty() ) {
if ( schemaIndex == null ) {
return Collections.emptyList();
}
else {
return Collections.singletonList( schemaIndex );
}
}
LinkedHashSet<XmlSchemaIndex> schemaIndexes = new LinkedHashSet<XmlSchemaIndex>();
if ( schemaIndex != null ) {
schemaIndexes.add( schemaIndex );
}
schemaIndexes.addAll( list );
return new ArrayList<XmlSchemaIndex>( schemaIndexes );
}
private List<XmlSchemaIndex> addSchemaIndexesFromParseOptions( XmlSchemaIndex<?> schemaIndex, XmlParseOptions options ) {
List<XmlSchemaIndex> schemaIndexes;
if ( options != null && ! options.getAdditionalSchemas().isEmpty() ) {
schemaIndexes = new ArrayList<XmlSchemaIndex>();
if ( schemaIndex != null ) {
schemaIndexes.add( schemaIndex );
}
for ( XmlSchemaAccess schemaAccess : options.getAdditionalSchemas() ) {
schemaIndexes.add( ( (XmlSchemaAccessImpl) schemaAccess ).getSchemaIndex() );
}
}
else if ( schemaIndex == null ) {
schemaIndexes = Collections.emptyList();
}
else {
schemaIndexes = Collections.<XmlSchemaIndex>singletonList( schemaIndex );
}
return schemaIndexes;
}
public void validate( List<XmlSchemaIndex> schemaIndexes, IType type, Source source, String description ) {
try {
Schema schema = XmlSchemaIndex.parseSchemas( schemaIndexes );
Validator validator = schema.newValidator();
validator.setResourceResolver( new XmlSchemaLocalResourceResolver() );
validator.validate( source );
}
catch ( SAXParseException ex ) {
throw new XmlException( "Failed to validate XML from " + description + " using schema root " + type.getName() + " at line " + ex.getLineNumber() + " column " + ex.getColumnNumber(), ex );
}
catch ( Exception ex ) {
throw new XmlException( "Failed to validate XML from " + description + " using schema root " + type.getName(), ex );
}
}
public IType getType( XmlElement element ) {
return element._type;
}
@Override
public void validateQName( QName qname ) {
XmlUtil.validateQName( qname );
}
public XmlElement parse( InputStream inputStream, URL schemaEF, XmlSchemaLocalResourceResolver resolver, XmlParserCallback callback ) {
return parse( null, new InputSource( inputStream ), schemaEF == null ? null : schemaEF.toExternalForm(), null, null, null, callback, schemaEF );
}
@Override
public boolean isAnyType( IType type ) {
return type == null || type.getName().equals( "gw.xsd.w3c.xmlschema.types.complex.AnyType" );
}
public void setTypeInstance( XmlElement element, XmlTypeInstance xmlTypeInstance ) {
// supertype will perform null check, no need to repeat it here
if ( xmlTypeInstance != null ) {
if ( element._schemaDefinedTypeInstanceType != null && ! element._schemaDefinedTypeInstanceType.equals( XmlTypeInstanceInternals.instance().getType( xmlTypeInstance ) ) ) {
IType typeInstanceType = XmlTypeInstanceInternals.instance().getType( xmlTypeInstance );
final XmlSchemaIndex<?> schemaIndex = XmlSchemaIndex.getSchemaIndexByType( typeInstanceType );
XmlSchemaTypeInstanceTypeData typeData = (XmlSchemaTypeInstanceTypeData) schemaIndex.getTypeData( typeInstanceType.getName() );
if ( typeData.isAnonymous() ) {
throw new XmlException( "Anonymous type instances can only be assigned to their schema-defined containing elements, or to elements of type xs:anyType." );
}
// static typing doesn't always help us here ( cast to XmlElement then assign ), so we have to perform a runtime assignable check
if ( ! element._schemaDefinedTypeInstanceType.isAssignableFrom( typeInstanceType ) ) {
throw new ClassCastException( xmlTypeInstance + " cannot be converted to " + element._schemaDefinedTypeInstanceType );
}
}
}
}
private List<XmlSchemaIndex> getSchemaIndexes( XmlParseOptions options ) {
List<XmlSchemaIndex> indexes;
if ( options == null ) {
indexes = null;
}
else {
indexes = new ArrayList<XmlSchemaIndex>( options.getAdditionalSchemas().size() );
for ( XmlSchemaAccess xmlSchemaAccess : options.getAdditionalSchemas() ) {
indexes.add( ( (XmlSchemaAccessImpl) xmlSchemaAccess ).getSchemaIndex() );
}
}
return indexes;
}
@Override
public XmlElement parse( InputStream stream ) {
return parse( stream, null );
}
@Override
public XmlElement parse( InputStream stream, XmlParseOptions options ) {
return parse( null, new InputSource( stream ), "input stream", getSchemaIndexes( options ), null, options, null, null );
}
@Override
public XmlElement parse( InputStream stream, XmlParseOptions options, XmlTypeResolver typeResolver ) {
return parse( null, new InputSource( stream ), "input stream", getSchemaIndexes( options ), typeResolver, options, null, null );
}
@Override
public XmlElement parse( Reader reader, XmlParseOptions options, XmlTypeResolver typeResolver ) {
return parse( null, new InputSource( reader ), "input stream", getSchemaIndexes( options ), typeResolver, options, null, null );
}
@Override
public XmlElement parse( byte[] bytes ) {
return parse( bytes, null );
}
@Override
public XmlElement parse( byte[] bytes, XmlParseOptions options ) {
return parse( null, new InputSource( new ByteArrayInputStream( bytes ) ), "byte array", getSchemaIndexes( options ), null, options, null, null );
}
@Override
public XmlElement parse( File file ) {
return parse( file, null );
}
@Override
public XmlElement parse( File file, XmlParseOptions options ) {
FileInputStream inputStream;
try {
inputStream = new FileInputStream(file);
return parse( null, new InputSource( inputStream ), file.getCanonicalPath(), getSchemaIndexes( options ), null, options, null, null );
}
catch ( IOException ex ) {
throw new XmlException( "Unable to parse file " + file, ex );
}
}
@Override
public XmlElement parse( URL url ) {
return parse( url, null );
}
@Override
public XmlElement parse( URL url, XmlParseOptions options ) {
InputStream inputStream;
String urlEF = url.toExternalForm();
try {
// try {
// return CommonServices.getFileSystem().getIFile(new File(url.toURI()));
// } catch (URISyntaxException e) {
// throw new RuntimeException(e);
// }
inputStream = CommonServices.getFileSystem().getIFile(url).openInputStream();
return parse( null, new InputSource( inputStream ), urlEF, getSchemaIndexes( options ), null, options, null, null );
}
catch ( IOException ex ) {
throw new XmlException( "Unable to parse file " + urlEF, ex );
}
}
@Override
public XmlElement parse( String string ) {
return parse( string, null );
}
@Override
public XmlElement parse( String string, XmlParseOptions options ) {
if ( string.length() > 0 && string.indexOf( '<' ) < 0 ) {
throw new XmlException( "Please use XmlElement.parse( java.io.File ) to parse a file" );
}
return parse( null, new InputSource( new StringReader( string ) ), "string", getSchemaIndexes( options ), null, options, null, null );
}
@Override
public XmlElement parse( Reader reader ) {
return parse( reader, null );
}
@Override
public XmlElement parse( Reader reader, XmlParseOptions options ) {
try {
return parse( null, new InputSource( reader ), "input reader", getSchemaIndexes( options ), null, options, null, null );
}
finally {
try {
reader.close();
}
catch (IOException e) {
// ignore
}
}
}
public XmlElement create( QName qName, IType type, IType xmlTypeInstanceType, XmlTypeInstance xmlTypeInstance ) {
return new XmlElement( qName, type, xmlTypeInstanceType, xmlTypeInstance );
}
}