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