Home | History | Annotate | Download | only in utils
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one
      3  * or more contributor license agreements. See the NOTICE file
      4  * distributed with this work for additional information
      5  * regarding copyright ownership. The ASF licenses this file
      6  * to you under the Apache License, Version 2.0 (the  "License");
      7  * you may not use this file except in compliance with the License.
      8  * You may obtain a copy of the License at
      9  *
     10  *     http://www.apache.org/licenses/LICENSE-2.0
     11  *
     12  * Unless required by applicable law or agreed to in writing, software
     13  * distributed under the License is distributed on an "AS IS" BASIS,
     14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15  * See the License for the specific language governing permissions and
     16  * limitations under the License.
     17  */
     18 /*
     19  * $Id: QName.java 468655 2006-10-28 07:12:06Z minchau $
     20  */
     21 package org.apache.xml.utils;
     22 
     23 import java.util.Stack;
     24 import java.util.StringTokenizer;
     25 
     26 import org.apache.xml.res.XMLErrorResources;
     27 import org.apache.xml.res.XMLMessages;
     28 
     29 import org.w3c.dom.Element;
     30 
     31 /**
     32  * Class to represent a qualified name: "The name of an internal XSLT object,
     33  * specifically a named template (see [7 Named Templates]), a mode (see [6.7 Modes]),
     34  * an attribute set (see [8.1.4 Named Attribute Sets]), a key (see [14.2 Keys]),
     35  * a locale (see [14.3 Number Formatting]), a variable or a parameter (see
     36  * [12 Variables and Parameters]) is specified as a QName. If it has a prefix,
     37  * then the prefix is expanded into a URI reference using the namespace declarations
     38  * in effect on the attribute in which the name occurs. The expanded name
     39  * consisting of the local part of the name and the possibly null URI reference
     40  * is used as the name of the object. The default namespace is not used for
     41  * unprefixed names."
     42  * @xsl.usage general
     43  */
     44 public class QName implements java.io.Serializable
     45 {
     46     static final long serialVersionUID = 467434581652829920L;
     47 
     48   /**
     49    * The local name.
     50    * @serial
     51    */
     52   protected String _localName;
     53 
     54   /**
     55    * The namespace URI.
     56    * @serial
     57    */
     58   protected String _namespaceURI;
     59 
     60   /**
     61    * The namespace prefix.
     62    * @serial
     63    */
     64   protected String _prefix;
     65 
     66   /**
     67    * The XML namespace.
     68    */
     69   public static final String S_XMLNAMESPACEURI =
     70     "http://www.w3.org/XML/1998/namespace";
     71 
     72   /**
     73    * The cached hashcode, which is calculated at construction time.
     74    * @serial
     75    */
     76   private int m_hashCode;
     77 
     78   /**
     79    * Constructs an empty QName.
     80    * 20001019: Try making this public, to support Serializable? -- JKESS
     81    */
     82   public QName(){}
     83 
     84   /**
     85    * Constructs a new QName with the specified namespace URI and
     86    * local name.
     87    *
     88    * @param namespaceURI The namespace URI if known, or null
     89    * @param localName The local name
     90    */
     91   public QName(String namespaceURI, String localName)
     92   {
     93     this(namespaceURI, localName, false);
     94   }
     95 
     96   /**
     97    * Constructs a new QName with the specified namespace URI and
     98    * local name.
     99    *
    100    * @param namespaceURI The namespace URI if known, or null
    101    * @param localName The local name
    102    * @param validate If true the new QName will be validated and an IllegalArgumentException will
    103    *                 be thrown if it is invalid.
    104    */
    105   public QName(String namespaceURI, String localName, boolean validate)
    106   {
    107 
    108     // This check was already here.  So, for now, I will not add it to the validation
    109     // that is done when the validate parameter is true.
    110     if (localName == null)
    111       throw new IllegalArgumentException(XMLMessages.createXMLMessage(
    112             XMLErrorResources.ER_ARG_LOCALNAME_NULL, null)); //"Argument 'localName' is null");
    113 
    114     if (validate)
    115     {
    116         if (!XML11Char.isXML11ValidNCName(localName))
    117         {
    118             throw new IllegalArgumentException(XMLMessages.createXMLMessage(
    119             XMLErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
    120         }
    121     }
    122 
    123     _namespaceURI = namespaceURI;
    124     _localName = localName;
    125     m_hashCode = toString().hashCode();
    126   }
    127 
    128   /**
    129    * Constructs a new QName with the specified namespace URI, prefix
    130    * and local name.
    131    *
    132    * @param namespaceURI The namespace URI if known, or null
    133    * @param prefix The namespace prefix is known, or null
    134    * @param localName The local name
    135    *
    136    */
    137   public QName(String namespaceURI, String prefix, String localName)
    138   {
    139      this(namespaceURI, prefix, localName, false);
    140   }
    141 
    142  /**
    143    * Constructs a new QName with the specified namespace URI, prefix
    144    * and local name.
    145    *
    146    * @param namespaceURI The namespace URI if known, or null
    147    * @param prefix The namespace prefix is known, or null
    148    * @param localName The local name
    149    * @param validate If true the new QName will be validated and an IllegalArgumentException will
    150    *                 be thrown if it is invalid.
    151    */
    152   public QName(String namespaceURI, String prefix, String localName, boolean validate)
    153   {
    154 
    155     // This check was already here.  So, for now, I will not add it to the validation
    156     // that is done when the validate parameter is true.
    157     if (localName == null)
    158       throw new IllegalArgumentException(XMLMessages.createXMLMessage(
    159             XMLErrorResources.ER_ARG_LOCALNAME_NULL, null)); //"Argument 'localName' is null");
    160 
    161     if (validate)
    162     {
    163         if (!XML11Char.isXML11ValidNCName(localName))
    164         {
    165             throw new IllegalArgumentException(XMLMessages.createXMLMessage(
    166             XMLErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
    167         }
    168 
    169         if ((null != prefix) && (!XML11Char.isXML11ValidNCName(prefix)))
    170         {
    171             throw new IllegalArgumentException(XMLMessages.createXMLMessage(
    172             XMLErrorResources.ER_ARG_PREFIX_INVALID,null )); //"Argument 'prefix' not a valid NCName");
    173         }
    174 
    175     }
    176     _namespaceURI = namespaceURI;
    177     _prefix = prefix;
    178     _localName = localName;
    179     m_hashCode = toString().hashCode();
    180   }
    181 
    182   /**
    183    * Construct a QName from a string, without namespace resolution.  Good
    184    * for a few odd cases.
    185    *
    186    * @param localName Local part of qualified name
    187    *
    188    */
    189   public QName(String localName)
    190   {
    191     this(localName, false);
    192   }
    193 
    194   /**
    195    * Construct a QName from a string, without namespace resolution.  Good
    196    * for a few odd cases.
    197    *
    198    * @param localName Local part of qualified name
    199    * @param validate If true the new QName will be validated and an IllegalArgumentException will
    200    *                 be thrown if it is invalid.
    201    */
    202   public QName(String localName, boolean validate)
    203   {
    204 
    205     // This check was already here.  So, for now, I will not add it to the validation
    206     // that is done when the validate parameter is true.
    207     if (localName == null)
    208       throw new IllegalArgumentException(XMLMessages.createXMLMessage(
    209             XMLErrorResources.ER_ARG_LOCALNAME_NULL, null)); //"Argument 'localName' is null");
    210 
    211     if (validate)
    212     {
    213         if (!XML11Char.isXML11ValidNCName(localName))
    214         {
    215             throw new IllegalArgumentException(XMLMessages.createXMLMessage(
    216             XMLErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
    217         }
    218     }
    219     _namespaceURI = null;
    220     _localName = localName;
    221     m_hashCode = toString().hashCode();
    222   }
    223 
    224   /**
    225    * Construct a QName from a string, resolving the prefix
    226    * using the given namespace stack. The default namespace is
    227    * not resolved.
    228    *
    229    * @param qname Qualified name to resolve
    230    * @param namespaces Namespace stack to use to resolve namespace
    231    */
    232   public QName(String qname, Stack namespaces)
    233   {
    234     this(qname, namespaces, false);
    235   }
    236 
    237   /**
    238    * Construct a QName from a string, resolving the prefix
    239    * using the given namespace stack. The default namespace is
    240    * not resolved.
    241    *
    242    * @param qname Qualified name to resolve
    243    * @param namespaces Namespace stack to use to resolve namespace
    244    * @param validate If true the new QName will be validated and an IllegalArgumentException will
    245    *                 be thrown if it is invalid.
    246    */
    247   public QName(String qname, Stack namespaces, boolean validate)
    248   {
    249 
    250     String namespace = null;
    251     String prefix = null;
    252     int indexOfNSSep = qname.indexOf(':');
    253 
    254     if (indexOfNSSep > 0)
    255     {
    256       prefix = qname.substring(0, indexOfNSSep);
    257 
    258       if (prefix.equals("xml"))
    259       {
    260         namespace = S_XMLNAMESPACEURI;
    261       }
    262       // Do we want this?
    263       else if (prefix.equals("xmlns"))
    264       {
    265         return;
    266       }
    267       else
    268       {
    269         int depth = namespaces.size();
    270 
    271         for (int i = depth - 1; i >= 0; i--)
    272         {
    273           NameSpace ns = (NameSpace) namespaces.elementAt(i);
    274 
    275           while (null != ns)
    276           {
    277             if ((null != ns.m_prefix) && prefix.equals(ns.m_prefix))
    278             {
    279               namespace = ns.m_uri;
    280               i = -1;
    281 
    282               break;
    283             }
    284 
    285             ns = ns.m_next;
    286           }
    287         }
    288       }
    289 
    290       if (null == namespace)
    291       {
    292         throw new RuntimeException(
    293           XMLMessages.createXMLMessage(
    294             XMLErrorResources.ER_PREFIX_MUST_RESOLVE,
    295             new Object[]{ prefix }));  //"Prefix must resolve to a namespace: "+prefix);
    296       }
    297     }
    298 
    299     _localName = (indexOfNSSep < 0)
    300                  ? qname : qname.substring(indexOfNSSep + 1);
    301 
    302     if (validate)
    303     {
    304         if ((_localName == null) || (!XML11Char.isXML11ValidNCName(_localName)))
    305         {
    306            throw new IllegalArgumentException(XMLMessages.createXMLMessage(
    307             XMLErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
    308         }
    309     }
    310     _namespaceURI = namespace;
    311     _prefix = prefix;
    312     m_hashCode = toString().hashCode();
    313   }
    314 
    315   /**
    316    * Construct a QName from a string, resolving the prefix
    317    * using the given namespace context and prefix resolver.
    318    * The default namespace is not resolved.
    319    *
    320    * @param qname Qualified name to resolve
    321    * @param namespaceContext Namespace Context to use
    322    * @param resolver Prefix resolver for this context
    323    */
    324   public QName(String qname, Element namespaceContext,
    325                PrefixResolver resolver)
    326   {
    327       this(qname, namespaceContext, resolver, false);
    328   }
    329 
    330   /**
    331    * Construct a QName from a string, resolving the prefix
    332    * using the given namespace context and prefix resolver.
    333    * The default namespace is not resolved.
    334    *
    335    * @param qname Qualified name to resolve
    336    * @param namespaceContext Namespace Context to use
    337    * @param resolver Prefix resolver for this context
    338    * @param validate If true the new QName will be validated and an IllegalArgumentException will
    339    *                 be thrown if it is invalid.
    340    */
    341   public QName(String qname, Element namespaceContext,
    342                PrefixResolver resolver, boolean validate)
    343   {
    344 
    345     _namespaceURI = null;
    346 
    347     int indexOfNSSep = qname.indexOf(':');
    348 
    349     if (indexOfNSSep > 0)
    350     {
    351       if (null != namespaceContext)
    352       {
    353         String prefix = qname.substring(0, indexOfNSSep);
    354 
    355         _prefix = prefix;
    356 
    357         if (prefix.equals("xml"))
    358         {
    359           _namespaceURI = S_XMLNAMESPACEURI;
    360         }
    361 
    362         // Do we want this?
    363         else if (prefix.equals("xmlns"))
    364         {
    365           return;
    366         }
    367         else
    368         {
    369           _namespaceURI = resolver.getNamespaceForPrefix(prefix,
    370                   namespaceContext);
    371         }
    372 
    373         if (null == _namespaceURI)
    374         {
    375           throw new RuntimeException(
    376             XMLMessages.createXMLMessage(
    377               XMLErrorResources.ER_PREFIX_MUST_RESOLVE,
    378               new Object[]{ prefix }));  //"Prefix must resolve to a namespace: "+prefix);
    379         }
    380       }
    381       else
    382       {
    383 
    384         // TODO: error or warning...
    385       }
    386     }
    387 
    388     _localName = (indexOfNSSep < 0)
    389                  ? qname : qname.substring(indexOfNSSep + 1);
    390 
    391     if (validate)
    392     {
    393         if ((_localName == null) || (!XML11Char.isXML11ValidNCName(_localName)))
    394         {
    395            throw new IllegalArgumentException(XMLMessages.createXMLMessage(
    396             XMLErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
    397         }
    398     }
    399 
    400     m_hashCode = toString().hashCode();
    401   }
    402 
    403 
    404   /**
    405    * Construct a QName from a string, resolving the prefix
    406    * using the given namespace stack. The default namespace is
    407    * not resolved.
    408    *
    409    * @param qname Qualified name to resolve
    410    * @param resolver Prefix resolver for this context
    411    */
    412   public QName(String qname, PrefixResolver resolver)
    413   {
    414     this(qname, resolver, false);
    415   }
    416 
    417   /**
    418    * Construct a QName from a string, resolving the prefix
    419    * using the given namespace stack. The default namespace is
    420    * not resolved.
    421    *
    422    * @param qname Qualified name to resolve
    423    * @param resolver Prefix resolver for this context
    424    * @param validate If true the new QName will be validated and an IllegalArgumentException will
    425    *                 be thrown if it is invalid.
    426    */
    427   public QName(String qname, PrefixResolver resolver, boolean validate)
    428   {
    429 
    430 	String prefix = null;
    431     _namespaceURI = null;
    432 
    433     int indexOfNSSep = qname.indexOf(':');
    434 
    435     if (indexOfNSSep > 0)
    436     {
    437       prefix = qname.substring(0, indexOfNSSep);
    438 
    439       if (prefix.equals("xml"))
    440       {
    441         _namespaceURI = S_XMLNAMESPACEURI;
    442       }
    443       else
    444       {
    445         _namespaceURI = resolver.getNamespaceForPrefix(prefix);
    446       }
    447 
    448       if (null == _namespaceURI)
    449       {
    450         throw new RuntimeException(
    451           XMLMessages.createXMLMessage(
    452             XMLErrorResources.ER_PREFIX_MUST_RESOLVE,
    453             new Object[]{ prefix }));  //"Prefix must resolve to a namespace: "+prefix);
    454       }
    455       _localName = qname.substring(indexOfNSSep + 1);
    456     }
    457     else if (indexOfNSSep == 0)
    458     {
    459       throw new RuntimeException(
    460          XMLMessages.createXMLMessage(
    461            XMLErrorResources.ER_NAME_CANT_START_WITH_COLON,
    462            null));
    463     }
    464     else
    465     {
    466       _localName = qname;
    467     }
    468 
    469     if (validate)
    470     {
    471         if ((_localName == null) || (!XML11Char.isXML11ValidNCName(_localName)))
    472         {
    473            throw new IllegalArgumentException(XMLMessages.createXMLMessage(
    474             XMLErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
    475         }
    476     }
    477 
    478 
    479     m_hashCode = toString().hashCode();
    480     _prefix = prefix;
    481   }
    482 
    483   /**
    484    * Returns the namespace URI. Returns null if the namespace URI
    485    * is not known.
    486    *
    487    * @return The namespace URI, or null
    488    */
    489   public String getNamespaceURI()
    490   {
    491     return _namespaceURI;
    492   }
    493 
    494   /**
    495    * Returns the namespace prefix. Returns null if the namespace
    496    * prefix is not known.
    497    *
    498    * @return The namespace prefix, or null
    499    */
    500   public String getPrefix()
    501   {
    502     return _prefix;
    503   }
    504 
    505   /**
    506    * Returns the local part of the qualified name.
    507    *
    508    * @return The local part of the qualified name
    509    */
    510   public String getLocalName()
    511   {
    512     return _localName;
    513   }
    514 
    515   /**
    516    * Return the string representation of the qualified name, using the
    517    * prefix if available, or the '{ns}foo' notation if not. Performs
    518    * string concatenation, so beware of performance issues.
    519    *
    520    * @return the string representation of the namespace
    521    */
    522   public String toString()
    523   {
    524 
    525     return _prefix != null
    526            ? (_prefix + ":" + _localName)
    527            : (_namespaceURI != null
    528               ? ("{"+_namespaceURI + "}" + _localName) : _localName);
    529   }
    530 
    531   /**
    532    * Return the string representation of the qualified name using the
    533    * the '{ns}foo' notation. Performs
    534    * string concatenation, so beware of performance issues.
    535    *
    536    * @return the string representation of the namespace
    537    */
    538   public String toNamespacedString()
    539   {
    540 
    541     return (_namespaceURI != null
    542               ? ("{"+_namespaceURI + "}" + _localName) : _localName);
    543   }
    544 
    545 
    546   /**
    547    * Get the namespace of the qualified name.
    548    *
    549    * @return the namespace URI of the qualified name
    550    */
    551   public String getNamespace()
    552   {
    553     return getNamespaceURI();
    554   }
    555 
    556   /**
    557    * Get the local part of the qualified name.
    558    *
    559    * @return the local part of the qualified name
    560    */
    561   public String getLocalPart()
    562   {
    563     return getLocalName();
    564   }
    565 
    566   /**
    567    * Return the cached hashcode of the qualified name.
    568    *
    569    * @return the cached hashcode of the qualified name
    570    */
    571   public int hashCode()
    572   {
    573     return m_hashCode;
    574   }
    575 
    576   /**
    577    * Override equals and agree that we're equal if
    578    * the passed object is a string and it matches
    579    * the name of the arg.
    580    *
    581    * @param ns Namespace URI to compare to
    582    * @param localPart Local part of qualified name to compare to
    583    *
    584    * @return True if the local name and uri match
    585    */
    586   public boolean equals(String ns, String localPart)
    587   {
    588 
    589     String thisnamespace = getNamespaceURI();
    590 
    591     return getLocalName().equals(localPart)
    592            && (((null != thisnamespace) && (null != ns))
    593                ? thisnamespace.equals(ns)
    594                : ((null == thisnamespace) && (null == ns)));
    595   }
    596 
    597   /**
    598    * Override equals and agree that we're equal if
    599    * the passed object is a QName and it matches
    600    * the name of the arg.
    601    *
    602    * @return True if the qualified names are equal
    603    */
    604   public boolean equals(Object object)
    605   {
    606 
    607     if (object == this)
    608       return true;
    609 
    610     if (object instanceof QName) {
    611       QName qname = (QName) object;
    612       String thisnamespace = getNamespaceURI();
    613       String thatnamespace = qname.getNamespaceURI();
    614 
    615       return getLocalName().equals(qname.getLocalName())
    616              && (((null != thisnamespace) && (null != thatnamespace))
    617                  ? thisnamespace.equals(thatnamespace)
    618                  : ((null == thisnamespace) && (null == thatnamespace)));
    619     }
    620     else
    621       return false;
    622   }
    623 
    624   /**
    625    * Given a string, create and return a QName object
    626    *
    627    *
    628    * @param name String to use to create QName
    629    *
    630    * @return a QName object
    631    */
    632   public static QName getQNameFromString(String name)
    633   {
    634 
    635     StringTokenizer tokenizer = new StringTokenizer(name, "{}", false);
    636     QName qname;
    637     String s1 = tokenizer.nextToken();
    638     String s2 = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
    639 
    640     if (null == s2)
    641       qname = new QName(null, s1);
    642     else
    643       qname = new QName(s1, s2);
    644 
    645     return qname;
    646   }
    647 
    648   /**
    649    * This function tells if a raw attribute name is a
    650    * xmlns attribute.
    651    *
    652    * @param attRawName Raw name of attribute
    653    *
    654    * @return True if the attribute starts with or is equal to xmlns
    655    */
    656   public static boolean isXMLNSDecl(String attRawName)
    657   {
    658 
    659     return (attRawName.startsWith("xmlns")
    660             && (attRawName.equals("xmlns")
    661                 || attRawName.startsWith("xmlns:")));
    662   }
    663 
    664   /**
    665    * This function tells if a raw attribute name is a
    666    * xmlns attribute.
    667    *
    668    * @param attRawName Raw name of attribute
    669    *
    670    * @return Prefix of attribute
    671    */
    672   public static String getPrefixFromXMLNSDecl(String attRawName)
    673   {
    674 
    675     int index = attRawName.indexOf(':');
    676 
    677     return (index >= 0) ? attRawName.substring(index + 1) : "";
    678   }
    679 
    680   /**
    681    * Returns the local name of the given node.
    682    *
    683    * @param qname Input name
    684    *
    685    * @return Local part of the name if prefixed, or the given name if not
    686    */
    687   public static String getLocalPart(String qname)
    688   {
    689 
    690     int index = qname.indexOf(':');
    691 
    692     return (index < 0) ? qname : qname.substring(index + 1);
    693   }
    694 
    695   /**
    696    * Returns the local name of the given node.
    697    *
    698    * @param qname Input name
    699    *
    700    * @return Prefix of name or empty string if none there
    701    */
    702   public static String getPrefixPart(String qname)
    703   {
    704 
    705     int index = qname.indexOf(':');
    706 
    707     return (index >= 0) ? qname.substring(0, index) : "";
    708   }
    709 }
    710