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 }