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    */
  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    */
  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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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    */
 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 }