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