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    */
QName()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    */
QName(String namespaceURI, String localName)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    */
QName(String namespaceURI, String localName, boolean validate)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    */
QName(String namespaceURI, String prefix, String localName)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    */
QName(String namespaceURI, String prefix, String localName, boolean validate)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    */
QName(String localName)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    */
QName(String localName, boolean validate)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    */
QName(String qname, Stack namespaces)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    */
QName(String qname, Stack namespaces, boolean validate)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    */
QName(String qname, Element namespaceContext, PrefixResolver resolver)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    */
QName(String qname, Element namespaceContext, PrefixResolver resolver, boolean validate)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    */
QName(String qname, PrefixResolver resolver)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    */
QName(String qname, PrefixResolver resolver, boolean validate)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    */
getNamespaceURI()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    */
getPrefix()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    */
getLocalName()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    */
toString()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    */
toNamespacedString()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    */
getNamespace()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    */
getLocalPart()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    */
hashCode()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    */
equals(String ns, String localPart)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    */
equals(Object object)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    */
getQNameFromString(String name)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    */
isXMLNSDecl(String attRawName)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    */
getPrefixFromXMLNSDecl(String attRawName)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    */
getLocalPart(String qname)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    */
getPrefixPart(String qname)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