/* * Copyright 2013 Guidewire Software, Inc. */ package gw.xml; import gw.internal.schema.gw.xsd.w3c.xmlschema.types.complex.AnyType; import gw.internal.xml.XmlDeserializationContext; import gw.internal.xml.XmlElementInternals; import gw.lang.PublishInGosu; import gw.lang.PublishedType; import gw.lang.PublishedTypes; import gw.lang.reflect.IType; import gw.lang.reflect.java.JavaTypes; import gw.util.Pair; import gw.util.StreamUtil; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.net.URI; import java.net.URL; import java.util.*; import javax.xml.namespace.QName; /** * Represents an XML element. Unlike DOM, it does not directly contain any children. It has a backing XML type * instance (XmlTypeInstance), however, which contains it's children. Call getTypeInstance() to access the backing * type instance. All of the methods and properties of the backing type instance have been re-exposed on the * XmlElement, however, so accessing the backing type instance directly should rarely be necessary. */ @PublishInGosu @PublishedTypes({ @PublishedType(fromType = "gw.xml.XmlTypeInstance", toType = "gw.xsd.w3c.xmlschema.types.complex.AnyType") }) public class XmlElement extends XmlBase implements IXmlMixedContent { static final QName XOP_INCLUDE_QNAME = new QName( "http://www.w3.org/2004/08/xop/include", "Include" ); final QName _qname; final IType _type; final IType _schemaDefinedTypeInstanceType; // This must be a list rather than a map, since it can have duplicate keys that are resolved for conflicts at serialization time List<Pair<String, URI>> _declaredNamespaceBindings = new ArrayList<Pair<String, URI>>( 0 ); String _comment; private boolean _nil; protected XmlElement( QName qname, IType type, IType schemaDefinedTypeInstanceType, XmlTypeInstance typeInstance ) { _type = type; XmlElementInternals.instance().validateQName( qname ); _schemaDefinedTypeInstanceType = XmlElementInternals.instance().isAnyType( schemaDefinedTypeInstanceType ) ? null : schemaDefinedTypeInstanceType; if ( typeInstance == null ) { if ( _schemaDefinedTypeInstanceType == null ) { typeInstance = new AnyType(); } else { typeInstance = (XmlTypeInstance) _schemaDefinedTypeInstanceType.getTypeInfo().getConstructor().getConstructor().newInstance(); } } _qname = qname; setTypeInstance( typeInstance ); } /** * Creates a new element with the specified QName, and the specified backing type instance. * @param qname The QName for the element * @param typeInstance The backing type instance */ public XmlElement( QName qname, XmlTypeInstance typeInstance ) { this( qname, null, null, typeInstance ); } /** * Creates a new element with the specified name in the null namespace, and the specified backing type instance. * @param localPart the local name for the element in the null namespace * @param typeInstance The backing type instance */ public XmlElement( String localPart, XmlTypeInstance typeInstance ) { this( new QName( localPart ), null, null, typeInstance ); } /** * Creates a new element with the specified QName, * and an empty gw.xsd.w3c.xmlschema.types.complex.AnyType backing type instance. * @param qname The QName for the element */ public XmlElement( QName qname ) { this( qname, null ); } /** * Creates a new element with the specified local name in the null namespace, * and an empty gw.xsd.w3c.xmlschema.types.complex.AnyType backing type instance. * @param name the name of the element in the null namespace */ public XmlElement( String name ) { this( name, null ); } /** * Returns the QName of this element. * @return the QName of this element */ public QName getQName() { return _qname; } /** * Returns the namespace of this element. * @return the namespace of this element */ public XmlNamespace getNamespace() { return new XmlNamespace( _qname.getNamespaceURI(), _qname.getPrefix() ); } /** * Serializes this element to the console. Equivalent to System.out.println( asUTFString() ) */ public void print() { try { writeTo( System.out ); } finally { System.out.println(); } } /** * Serializes this element to the console. Equivalent to System.out.println( asUTFString() ) * @param options the options to control serialization */ public void print( XmlSerializationOptions options) { writeTo( System.out, options ); System.out.println(); } /** * Serializes this element to a string. The returned string is in a format suitable for byte conversion * to either UTF-8 or UTF-16 (with a leading byte order mark to indicate endianness). The use of * getBytes() is highly recommended over the use of this method. * @return a string containing the serialized XML */ public String asUTFString() { return StreamUtil.toString( bytes(new XmlSerializationOptions()) ); } /** * Serializes this element to a string. The returned string is in a format suitable for byte conversion * to either UTF-8 or UTF-16 (with a leading byte order mark to indicate endianness). The use of * getBytes() is highly recommended over the use of this method. * @param options the options to control serialization * @return a string containing the serialized XML */ public String asUTFString( XmlSerializationOptions options) { return StreamUtil.toString( bytes(options) ); } /** * Serializes this element to a byte array using UTF-8 encoding. * @return a byte array containing the serialized XML */ public byte[] bytes() { return bytes(new XmlSerializationOptions()); } /** * Serializes this element to a byte array using UTF-8 encoding. * @param options the options to control serialization * @return a byte array containing the serialized XML */ public byte[] bytes( XmlSerializationOptions options) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeTo( baos, options ); return baos.toByteArray(); } /** * Serializes this element to the specified output stream using UTF-8 encoding. The stream will not be closed. * @param out the output stream where the data should be written */ public void writeTo( OutputStream out ) { writeTo( out, new XmlSerializationOptions() ); } /** * Serializes this element to the specified output stream using UTF-8 encoding. The stream will not be closed. * @param out the output stream where the data should be written * @param options the options to control serialization */ public void writeTo( OutputStream out, XmlSerializationOptions options ) { XmlElementInternals.instance().writeTo( this, out, options ); } /** * Explicitly declares an XML namespace and a suggested prefix at this level. * The prefix that actually ends up being bound to the namespace may not be the same as the suggested prefix. * @param nsuri the namespace URI to which the prefix should be bound * @param suggestedPrefix the suggested prefix to use */ public void declareNamespace(URI nsuri, String suggestedPrefix) { if (nsuri == null) { throw new IllegalArgumentException("The value for the nsuri parameter must not be null"); } XmlElementInternals.instance().doDeclareNamespace( this, nsuri.toString(), suggestedPrefix, null ); } /** * Explicitly declares an XML namespace and a suggested prefix at this level. * The prefix that actually ends up being bound to the namespace may not be the same as the suggested prefix. * @param qname the qname that contains the namespace URI to be declared and the suggested prefix to which the namespace should be bound */ public void declareNamespace(QName qname) { if (qname == null) { throw new IllegalArgumentException("The value for the qname parameter must not be null"); } XmlElementInternals.instance().doDeclareNamespace( this, qname.getNamespaceURI(), qname.getPrefix(), null ); } /** * Explicitly declares an XML namespace and a suggested prefix at this level. * The prefix that actually ends up being bound to the namespace may not be the same as the suggested prefix. * @param ns the XmlNamespace that contains the namespace URI to be declared and the suggested prefix to which the namespace should be bound */ public void declareNamespace(XmlNamespace ns) { if (ns == null) { throw new IllegalArgumentException("The value for the ns parameter must not be null"); } XmlElementInternals.instance().doDeclareNamespace( this, ns.getNamespaceURI(), ns.getPrefix(), null ); } /** * Explicitly declares an XML namespace at this level with the specified namespace URI. * @param nsuri the namespace URI to be declared */ public void declareNamespace(URI nsuri) { if (nsuri == null) { throw new IllegalArgumentException("The value for the nsuri parameter must not be null"); } XmlElementInternals.instance().doDeclareNamespace( this, nsuri.toString(), "", null ); } /** * Parses XML from the specified input stream. The input stream will be closed by this method. * @param stream The stream containing the XML * @return An XmlElement representing the XML content */ public static XmlElement parse( InputStream stream ) { return XmlElementInternals.instance().parse( stream ); } /** * Parses XML from the specified input stream. The input stream will be closed by this method. * @param stream The stream containing the XML * @param options parsing options * @return An XmlElement representing the XML content */ public static XmlElement parse( InputStream stream, XmlParseOptions options ) { return XmlElementInternals.instance().parse( stream, options ); } /** * Parses XML from the specified byte array. * @param bytes The byte array containing the XML * @return An XmlElement representing the XML content */ public static XmlElement parse( byte[] bytes ) { return XmlElementInternals.instance().parse( bytes ); } /** * Parses XML from the specified byte array. * @param bytes The byte array containing the XML * @param options parsing options * @return An XmlElement representing the XML content */ public static XmlElement parse( byte[] bytes, XmlParseOptions options ) { return XmlElementInternals.instance().parse( bytes, options ); } /** * Parses XML from the specified file. * @param file The file containing the XML * @return An XmlElement representing the XML content */ public static XmlElement parse( File file ) { return XmlElementInternals.instance().parse( file ); } /** * Parses XML from the specified file. * @param file The file containing the XML * @param options parsing options * @return An XmlElement representing the XML content */ public static XmlElement parse( File file, XmlParseOptions options ) { return XmlElementInternals.instance().parse( file, options ); } /** * Parses XML from the specified string. * @param string The string containing the XML * @return An XmlElement representing the XML content */ public static XmlElement parse( String string ) { return XmlElementInternals.instance().parse( string ); } /** * Parses XML from the specified string. * @param string The string containing the XML * @param options parsing options * @return An XmlElement representing the XML content */ public static XmlElement parse( String string, XmlParseOptions options ) { return XmlElementInternals.instance().parse( string, options ); } /** * Parses XML from the specified reader. The reader will be closed by this method. * @param reader The reader containing the XML * @return An XmlElement representing the XML content */ public static XmlElement parse( Reader reader ) { return XmlElementInternals.instance().parse( reader ); } /** * Parses XML from the specified reader. The reader will be closed by this method. * @param reader The reader containing the XML * @param options parsing options * @return An XmlElement representing the XML content */ public static XmlElement parse( Reader reader, XmlParseOptions options ) { return XmlElementInternals.instance().parse( reader, options ); } /** * Parses XML from the specified URL. * @param url The URL containing the XML * @return An XmlElement representing the XML content */ public static XmlElement parse( URL url ) { return XmlElementInternals.instance().parse( url ); } /** * Parses XML from the specified URL. * @param url The URL containing the XML * @param options parsing options * @return An XmlElement representing the XML content */ public static XmlElement parse( URL url, XmlParseOptions options ) { return XmlElementInternals.instance().parse( url, options ); } /** * Returns the backing type instance of this element. * @return the backing type instance of this element */ public XmlTypeInstance getTypeInstance() { return super.getTypeInstance(); } /** * Sets the backing type instance of this element. * @param xmlTypeInstance the new backing type instance for this element */ public void setTypeInstance( XmlTypeInstance xmlTypeInstance ) { XmlElementInternals.instance().setTypeInstance( this, xmlTypeInstance ); super.setTypeInstance( xmlTypeInstance ); } @Override public IType getIntrinsicType() { return _type == null ? JavaTypes.getSystemType( getClass() ) : _type; } /** * Returns a human-readable description of this element. * @return a human-readable description of this element */ public String toString() { return _type == null ? ( "Element " + getQName() ) : super.toString(); } /** * Returns the namespace context of this element. * @return the namespace context of this element */ public Map<String,String> getNamespaceContext() { Map<String,String> ret = new HashMap<String, String>(); ret.putAll( new XmlDeserializationContext( null ).getNamespaces() ); for ( Pair<String, URI> declaredNamespaceBinding : _declaredNamespaceBindings ) { ret.put( declaredNamespaceBinding.getFirst(), declaredNamespaceBinding.getSecond().toString() ); } return ret; } /** * Returns a list of explicitly declared namespaces at this element's level. * @return a list of explicitly declared namespaces at this element's level */ public List<Pair<String,URI>> getDeclaredNamespaces() { return Collections.unmodifiableList( _declaredNamespaceBindings ); } /** * Attaches a comment to this element. * @param comment the comment to be set */ public void setComment( String comment ) { _comment = comment; } /** * Returns the comment previously set on this element, if applicable. * @return the comment previously set on this element, if applicable */ public String getComment() { return _comment; } /** * Returns true if this element is nil. * @return true if this element is nil */ public boolean isNil() { return _nil; } /** * Sets the nillness of this element. * @param nil the nillness of this element */ public void setNil( boolean nil ) { _nil = nil; } }