001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: QName.java 468655 2006-10-28 07:12:06Z minchau $
020     */
021    package org.apache.xml.utils;
022    
023    import java.util.Stack;
024    import java.util.StringTokenizer;
025    
026    import org.apache.xml.res.XMLErrorResources;
027    import org.apache.xml.res.XMLMessages;
028    
029    import org.w3c.dom.Element;
030    
031    /**
032     * Class to represent a qualified name: "The name of an internal XSLT object,
033     * specifically a named template (see [7 Named Templates]), a mode (see [6.7 Modes]),
034     * an attribute set (see [8.1.4 Named Attribute Sets]), a key (see [14.2 Keys]),
035     * a locale (see [14.3 Number Formatting]), a variable or a parameter (see
036     * [12 Variables and Parameters]) is specified as a QName. If it has a prefix,
037     * then the prefix is expanded into a URI reference using the namespace declarations
038     * in effect on the attribute in which the name occurs. The expanded name
039     * consisting of the local part of the name and the possibly null URI reference
040     * is used as the name of the object. The default namespace is not used for
041     * unprefixed names."
042     * @xsl.usage general
043     */
044    public class QName implements java.io.Serializable
045    {
046        static final long serialVersionUID = 467434581652829920L;
047    
048      /**
049       * The local name.
050       * @serial
051       */
052      protected String _localName;
053    
054      /**
055       * The namespace URI.
056       * @serial
057       */
058      protected String _namespaceURI;
059    
060      /**
061       * The namespace prefix.
062       * @serial
063       */
064      protected String _prefix;
065    
066      /**
067       * The XML namespace.
068       */
069      public static final String S_XMLNAMESPACEURI =
070        "http://www.w3.org/XML/1998/namespace";
071    
072      /**
073       * The cached hashcode, which is calculated at construction time.
074       * @serial
075       */
076      private int m_hashCode;
077    
078      /**
079       * Constructs an empty QName.
080       * 20001019: Try making this public, to support Serializable? -- JKESS
081       */
082      public QName(){}
083    
084      /**
085       * Constructs a new QName with the specified namespace URI and
086       * local name.
087       *
088       * @param namespaceURI The namespace URI if known, or null
089       * @param localName The local name
090       */
091      public QName(String namespaceURI, String localName)
092      {
093        this(namespaceURI, localName, false); 
094      }
095    
096      /**
097       * Constructs a new QName with the specified namespace URI and
098       * local name.
099       *
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    }