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