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