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