1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. *
  5. * Copyright (c) 1999 The Apache Software Foundation. All rights
  6. * reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * 1. Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * 2. Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * 3. The end-user documentation included with the redistribution,
  21. * if any, must include the following acknowledgment:
  22. * "This product includes software developed by the
  23. * Apache Software Foundation (http://www.apache.org/)."
  24. * Alternately, this acknowledgment may appear in the software itself,
  25. * if and wherever such third-party acknowledgments normally appear.
  26. *
  27. * 4. The names "Xalan" and "Apache Software Foundation" must
  28. * not be used to endorse or promote products derived from this
  29. * software without prior written permission. For written
  30. * permission, please contact apache@apache.org.
  31. *
  32. * 5. Products derived from this software may not be called "Apache",
  33. * nor may "Apache" appear in their name, without prior written
  34. * permission of the Apache Software Foundation.
  35. *
  36. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  37. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  38. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  39. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  40. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  41. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  42. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  43. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  44. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  45. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  46. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  47. * SUCH DAMAGE.
  48. * ====================================================================
  49. *
  50. * This software consists of voluntary contributions made by many
  51. * individuals on behalf of the Apache Software Foundation and was
  52. * originally based on software copyright (c) 1999, Lotus
  53. * Development Corporation., http://www.lotus.com. For more
  54. * information on the Apache Software Foundation, please see
  55. * <http://www.apache.org/>.
  56. */
  57. package org.apache.xml.utils;
  58. import java.util.Stack;
  59. import java.util.StringTokenizer;
  60. import org.w3c.dom.Element;
  61. import org.apache.xpath.res.XPATHErrorResources;
  62. import org.apache.xalan.res.XSLMessages;
  63. /**
  64. * <meta name="usage" content="general"/>
  65. * Class to represent a qualified name: "The name of an internal XSLT object,
  66. * specifically a named template (see [7 Named Templates]), a mode (see [6.7 Modes]),
  67. * an attribute set (see [8.1.4 Named Attribute Sets]), a key (see [14.2 Keys]),
  68. * a locale (see [14.3 Number Formatting]), a variable or a parameter (see
  69. * [12 Variables and Parameters]) is specified as a QName. If it has a prefix,
  70. * then the prefix is expanded into a URI reference using the namespace declarations
  71. * in effect on the attribute in which the name occurs. The expanded name
  72. * consisting of the local part of the name and the possibly null URI reference
  73. * is used as the name of the object. The default namespace is not used for
  74. * unprefixed names."
  75. */
  76. public class QName implements java.io.Serializable
  77. {
  78. /**
  79. * The local name.
  80. * @serial
  81. */
  82. protected String _localName;
  83. /**
  84. * The namespace URI.
  85. * @serial
  86. */
  87. protected String _namespaceURI;
  88. /**
  89. * The namespace prefix.
  90. * @serial
  91. */
  92. protected String _prefix;
  93. /**
  94. * The XML namespace.
  95. */
  96. public static final String S_XMLNAMESPACEURI =
  97. "http://www.w3.org/XML/1998/namespace";
  98. /**
  99. * The cached hashcode, which is calculated at construction time.
  100. * @serial
  101. */
  102. private int m_hashCode;
  103. /**
  104. * Constructs an empty QName.
  105. * 20001019: Try making this public, to support Serializable? -- JKESS
  106. */
  107. public QName(){}
  108. /**
  109. * Constructs a new QName with the specified namespace URI and
  110. * local name.
  111. *
  112. * @param namespaceURI The namespace URI if known, or null
  113. * @param localName The local name
  114. */
  115. public QName(String namespaceURI, String localName)
  116. {
  117. this(namespaceURI, localName, false);
  118. }
  119. /**
  120. * Constructs a new QName with the specified namespace URI and
  121. * local name.
  122. *
  123. * @param namespaceURI The namespace URI if known, or null
  124. * @param localName The local name
  125. * @param validate If true the new QName will be validated and an IllegalArgumentException will
  126. * be thrown if it is invalid.
  127. */
  128. public QName(String namespaceURI, String localName, boolean validate)
  129. {
  130. // This check was already here. So, for now, I will not add it to the validation
  131. // that is done when the validate parameter is true.
  132. if (localName == null)
  133. throw new IllegalArgumentException(XSLMessages.createXPATHMessage(
  134. XPATHErrorResources.ER_ARG_LOCALNAME_NULL, null)); //"Argument 'localName' is null");
  135. if (validate)
  136. {
  137. if (!XMLChar.isValidNCName(localName))
  138. {
  139. throw new IllegalArgumentException(XSLMessages.createXPATHMessage(
  140. XPATHErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
  141. }
  142. }
  143. _namespaceURI = namespaceURI;
  144. _localName = localName;
  145. m_hashCode = toString().hashCode();
  146. }
  147. /**
  148. * Constructs a new QName with the specified namespace URI, prefix
  149. * and local name.
  150. *
  151. * @param namespaceURI The namespace URI if known, or null
  152. * @param prefix The namespace prefix is known, or null
  153. * @param localName The local name
  154. *
  155. */
  156. public QName(String namespaceURI, String prefix, String localName)
  157. {
  158. this(namespaceURI, prefix, localName, false);
  159. }
  160. /**
  161. * Constructs a new QName with the specified namespace URI, prefix
  162. * and local name.
  163. *
  164. * @param namespaceURI The namespace URI if known, or null
  165. * @param prefix The namespace prefix is known, or null
  166. * @param localName The local name
  167. * @param validate If true the new QName will be validated and an IllegalArgumentException will
  168. * be thrown if it is invalid.
  169. */
  170. public QName(String namespaceURI, String prefix, String localName, boolean validate)
  171. {
  172. // This check was already here. So, for now, I will not add it to the validation
  173. // that is done when the validate parameter is true.
  174. if (localName == null)
  175. throw new IllegalArgumentException(XSLMessages.createXPATHMessage(
  176. XPATHErrorResources.ER_ARG_LOCALNAME_NULL, null)); //"Argument 'localName' is null");
  177. if (validate)
  178. {
  179. if (!XMLChar.isValidNCName(localName))
  180. {
  181. throw new IllegalArgumentException(XSLMessages.createXPATHMessage(
  182. XPATHErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
  183. }
  184. if ((null != prefix) && (!XMLChar.isValidNCName(prefix)))
  185. {
  186. throw new IllegalArgumentException(XSLMessages.createXPATHMessage(
  187. XPATHErrorResources.ER_ARG_PREFIX_INVALID,null )); //"Argument 'prefix' not a valid NCName");
  188. }
  189. }
  190. _namespaceURI = namespaceURI;
  191. _prefix = prefix;
  192. _localName = localName;
  193. m_hashCode = toString().hashCode();
  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. *
  201. */
  202. public QName(String localName)
  203. {
  204. this(localName, false);
  205. }
  206. /**
  207. * Construct a QName from a string, without namespace resolution. Good
  208. * for a few odd cases.
  209. *
  210. * @param localName Local part of qualified name
  211. * @param validate If true the new QName will be validated and an IllegalArgumentException will
  212. * be thrown if it is invalid.
  213. */
  214. public QName(String localName, boolean validate)
  215. {
  216. // This check was already here. So, for now, I will not add it to the validation
  217. // that is done when the validate parameter is true.
  218. if (localName == null)
  219. throw new IllegalArgumentException(XSLMessages.createXPATHMessage(
  220. XPATHErrorResources.ER_ARG_LOCALNAME_NULL, null)); //"Argument 'localName' is null");
  221. if (validate)
  222. {
  223. if (!XMLChar.isValidNCName(localName))
  224. {
  225. throw new IllegalArgumentException(XSLMessages.createXPATHMessage(
  226. XPATHErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
  227. }
  228. }
  229. _namespaceURI = null;
  230. _localName = localName;
  231. m_hashCode = toString().hashCode();
  232. }
  233. /**
  234. * Construct a QName from a string, resolving the prefix
  235. * using the given namespace stack. The default namespace is
  236. * not resolved.
  237. *
  238. * @param qname Qualified name to resolve
  239. * @param namespaces Namespace stack to use to resolve namespace
  240. */
  241. public QName(String qname, Stack namespaces)
  242. {
  243. this(qname, namespaces, false);
  244. }
  245. /**
  246. * Construct a QName from a string, resolving the prefix
  247. * using the given namespace stack. The default namespace is
  248. * not resolved.
  249. *
  250. * @param qname Qualified name to resolve
  251. * @param namespaces Namespace stack to use to resolve namespace
  252. * @param validate If true the new QName will be validated and an IllegalArgumentException will
  253. * be thrown if it is invalid.
  254. */
  255. public QName(String qname, Stack namespaces, boolean validate)
  256. {
  257. String namespace = null;
  258. String prefix = null;
  259. int indexOfNSSep = qname.indexOf(':');
  260. if (indexOfNSSep > 0)
  261. {
  262. prefix = qname.substring(0, indexOfNSSep);
  263. if (prefix.equals("xml"))
  264. {
  265. namespace = S_XMLNAMESPACEURI;
  266. }
  267. // Do we want this?
  268. else if (prefix.equals("xmlns"))
  269. {
  270. return;
  271. }
  272. else
  273. {
  274. int depth = namespaces.size();
  275. for (int i = depth - 1; i >= 0; i--)
  276. {
  277. NameSpace ns = (NameSpace) namespaces.elementAt(i);
  278. while (null != ns)
  279. {
  280. if ((null != ns.m_prefix) && prefix.equals(ns.m_prefix))
  281. {
  282. namespace = ns.m_uri;
  283. i = -1;
  284. break;
  285. }
  286. ns = ns.m_next;
  287. }
  288. }
  289. }
  290. if (null == namespace)
  291. {
  292. throw new RuntimeException(
  293. XSLMessages.createXPATHMessage(
  294. XPATHErrorResources.ER_PREFIX_MUST_RESOLVE,
  295. new Object[]{ prefix })); //"Prefix must resolve to a namespace: "+prefix);
  296. }
  297. }
  298. _localName = (indexOfNSSep < 0)
  299. ? qname : qname.substring(indexOfNSSep + 1);
  300. if (validate)
  301. {
  302. if ((_localName == null) || (!XMLChar.isValidNCName(_localName)))
  303. {
  304. throw new IllegalArgumentException(XSLMessages.createXPATHMessage(
  305. XPATHErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
  306. }
  307. }
  308. _namespaceURI = namespace;
  309. _prefix = prefix;
  310. m_hashCode = toString().hashCode();
  311. }
  312. /**
  313. * Construct a QName from a string, resolving the prefix
  314. * using the given namespace context and prefix resolver.
  315. * The default namespace is not resolved.
  316. *
  317. * @param qname Qualified name to resolve
  318. * @param namespaceContext Namespace Context to use
  319. * @param resolver Prefix resolver for this context
  320. */
  321. public QName(String qname, Element namespaceContext,
  322. PrefixResolver resolver)
  323. {
  324. this(qname, namespaceContext, resolver, false);
  325. }
  326. /**
  327. * Construct a QName from a string, resolving the prefix
  328. * using the given namespace context and prefix resolver.
  329. * The default namespace is not resolved.
  330. *
  331. * @param qname Qualified name to resolve
  332. * @param namespaceContext Namespace Context to use
  333. * @param resolver Prefix resolver for this context
  334. * @param validate If true the new QName will be validated and an IllegalArgumentException will
  335. * be thrown if it is invalid.
  336. */
  337. public QName(String qname, Element namespaceContext,
  338. PrefixResolver resolver, boolean validate)
  339. {
  340. _namespaceURI = null;
  341. int indexOfNSSep = qname.indexOf(':');
  342. if (indexOfNSSep > 0)
  343. {
  344. if (null != namespaceContext)
  345. {
  346. String prefix = qname.substring(0, indexOfNSSep);
  347. _prefix = prefix;
  348. if (prefix.equals("xml"))
  349. {
  350. _namespaceURI = S_XMLNAMESPACEURI;
  351. }
  352. // Do we want this?
  353. else if (prefix.equals("xmlns"))
  354. {
  355. return;
  356. }
  357. else
  358. {
  359. _namespaceURI = resolver.getNamespaceForPrefix(prefix,
  360. namespaceContext);
  361. }
  362. if (null == _namespaceURI)
  363. {
  364. throw new RuntimeException(
  365. XSLMessages.createXPATHMessage(
  366. XPATHErrorResources.ER_PREFIX_MUST_RESOLVE,
  367. new Object[]{ prefix })); //"Prefix must resolve to a namespace: "+prefix);
  368. }
  369. }
  370. else
  371. {
  372. // TODO: error or warning...
  373. }
  374. }
  375. _localName = (indexOfNSSep < 0)
  376. ? qname : qname.substring(indexOfNSSep + 1);
  377. if (validate)
  378. {
  379. if ((_localName == null) || (!XMLChar.isValidNCName(_localName)))
  380. {
  381. throw new IllegalArgumentException(XSLMessages.createXPATHMessage(
  382. XPATHErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
  383. }
  384. }
  385. m_hashCode = toString().hashCode();
  386. }
  387. /**
  388. * Construct a QName from a string, resolving the prefix
  389. * using the given namespace stack. The default namespace is
  390. * not resolved.
  391. *
  392. * @param qname Qualified name to resolve
  393. * @param resolver Prefix resolver for this context
  394. */
  395. public QName(String qname, PrefixResolver resolver)
  396. {
  397. this(qname, resolver, false);
  398. }
  399. /**
  400. * Construct a QName from a string, resolving the prefix
  401. * using the given namespace stack. The default namespace is
  402. * not resolved.
  403. *
  404. * @param qname Qualified name to resolve
  405. * @param resolver Prefix resolver for this context
  406. * @param validate If true the new QName will be validated and an IllegalArgumentException will
  407. * be thrown if it is invalid.
  408. */
  409. public QName(String qname, PrefixResolver resolver, boolean validate)
  410. {
  411. String prefix = null;
  412. _namespaceURI = null;
  413. int indexOfNSSep = qname.indexOf(':');
  414. if (indexOfNSSep > 0)
  415. {
  416. prefix = qname.substring(0, indexOfNSSep);
  417. if (prefix.equals("xml"))
  418. {
  419. _namespaceURI = S_XMLNAMESPACEURI;
  420. }
  421. else
  422. {
  423. _namespaceURI = resolver.getNamespaceForPrefix(prefix);
  424. }
  425. if (null == _namespaceURI)
  426. {
  427. throw new RuntimeException(
  428. XSLMessages.createXPATHMessage(
  429. XPATHErrorResources.ER_PREFIX_MUST_RESOLVE,
  430. new Object[]{ prefix })); //"Prefix must resolve to a namespace: "+prefix);
  431. }
  432. }
  433. _localName = (indexOfNSSep < 0)
  434. ? qname : qname.substring(indexOfNSSep + 1);
  435. if (validate)
  436. {
  437. if ((_localName == null) || (!XMLChar.isValidNCName(_localName)))
  438. {
  439. throw new IllegalArgumentException(XSLMessages.createXPATHMessage(
  440. XPATHErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
  441. }
  442. }
  443. m_hashCode = toString().hashCode();
  444. _prefix = prefix;
  445. }
  446. /**
  447. * Returns the namespace URI. Returns null if the namespace URI
  448. * is not known.
  449. *
  450. * @return The namespace URI, or null
  451. */
  452. public String getNamespaceURI()
  453. {
  454. return _namespaceURI;
  455. }
  456. /**
  457. * Returns the namespace prefix. Returns null if the namespace
  458. * prefix is not known.
  459. *
  460. * @return The namespace prefix, or null
  461. */
  462. public String getPrefix()
  463. {
  464. return _prefix;
  465. }
  466. /**
  467. * Returns the local part of the qualified name.
  468. *
  469. * @return The local part of the qualified name
  470. */
  471. public String getLocalName()
  472. {
  473. return _localName;
  474. }
  475. /**
  476. * Return the string representation of the qualified name, using the
  477. * prefix if available, or the '{ns}foo' notation if not. Performs
  478. * string concatenation, so beware of performance issues.
  479. *
  480. * @return the string representation of the namespace
  481. */
  482. public String toString()
  483. {
  484. return _prefix != null
  485. ? (_prefix + ":" + _localName)
  486. : (_namespaceURI != null
  487. ? ("{"+_namespaceURI + "}" + _localName) : _localName);
  488. }
  489. /**
  490. * Return the string representation of the qualified name using the
  491. * the '{ns}foo' notation. Performs
  492. * string concatenation, so beware of performance issues.
  493. *
  494. * @return the string representation of the namespace
  495. */
  496. public String toNamespacedString()
  497. {
  498. return (_namespaceURI != null
  499. ? ("{"+_namespaceURI + "}" + _localName) : _localName);
  500. }
  501. /**
  502. * Get the namespace of the qualified name.
  503. *
  504. * @return the namespace URI of the qualified name
  505. */
  506. public String getNamespace()
  507. {
  508. return getNamespaceURI();
  509. }
  510. /**
  511. * Get the local part of the qualified name.
  512. *
  513. * @return the local part of the qualified name
  514. */
  515. public String getLocalPart()
  516. {
  517. return getLocalName();
  518. }
  519. /**
  520. * Return the cached hashcode of the qualified name.
  521. *
  522. * @return the cached hashcode of the qualified name
  523. */
  524. public int hashCode()
  525. {
  526. return m_hashCode;
  527. }
  528. /**
  529. * Override equals and agree that we're equal if
  530. * the passed object is a string and it matches
  531. * the name of the arg.
  532. *
  533. * @param ns Namespace URI to compare to
  534. * @param localPart Local part of qualified name to compare to
  535. *
  536. * @return True if the local name and uri match
  537. */
  538. public boolean equals(String ns, String localPart)
  539. {
  540. String thisnamespace = getNamespaceURI();
  541. return getLocalName().equals(localPart)
  542. && (((null != thisnamespace) && (null != ns))
  543. ? thisnamespace.equals(ns)
  544. : ((null == thisnamespace) && (null == ns)));
  545. }
  546. /**
  547. * Override equals and agree that we're equal if
  548. * the passed object is a QName and it matches
  549. * the name of the arg.
  550. *
  551. * @param qname Qualified name to compare to
  552. *
  553. * @return True if the qualified names are equal
  554. */
  555. public boolean equals(Object object)
  556. {
  557. if (object == this)
  558. return true;
  559. if (object instanceof QName) {
  560. QName qname = (QName) object;
  561. String thisnamespace = getNamespaceURI();
  562. String thatnamespace = qname.getNamespaceURI();
  563. return getLocalName().equals(qname.getLocalName())
  564. && (((null != thisnamespace) && (null != thatnamespace))
  565. ? thisnamespace.equals(thatnamespace)
  566. : ((null == thisnamespace) && (null == thatnamespace)));
  567. }
  568. else
  569. return false;
  570. }
  571. /**
  572. * Given a string, create and return a QName object
  573. *
  574. *
  575. * @param name String to use to create QName
  576. *
  577. * @return a QName object
  578. */
  579. public static QName getQNameFromString(String name)
  580. {
  581. StringTokenizer tokenizer = new StringTokenizer(name, "{}", false);
  582. QName qname;
  583. String s1 = tokenizer.nextToken();
  584. String s2 = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
  585. if (null == s2)
  586. qname = new QName(null, s1);
  587. else
  588. qname = new QName(s1, s2);
  589. return qname;
  590. }
  591. /**
  592. * This function tells if a raw attribute name is a
  593. * xmlns attribute.
  594. *
  595. * @param attRawName Raw name of attribute
  596. *
  597. * @return True if the attribute starts with or is equal to xmlns
  598. */
  599. public static boolean isXMLNSDecl(String attRawName)
  600. {
  601. return (attRawName.startsWith("xmlns")
  602. && (attRawName.equals("xmlns")
  603. || attRawName.startsWith("xmlns:")));
  604. }
  605. /**
  606. * This function tells if a raw attribute name is a
  607. * xmlns attribute.
  608. *
  609. * @param attRawName Raw name of attribute
  610. *
  611. * @return Prefix of attribute
  612. */
  613. public static String getPrefixFromXMLNSDecl(String attRawName)
  614. {
  615. int index = attRawName.indexOf(':');
  616. return (index >= 0) ? attRawName.substring(index + 1) : "";
  617. }
  618. /**
  619. * Returns the local name of the given node.
  620. *
  621. * @param qname Input name
  622. *
  623. * @return Local part of the name if prefixed, or the given name if not
  624. */
  625. public static String getLocalPart(String qname)
  626. {
  627. int index = qname.indexOf(':');
  628. return (index < 0) ? qname : qname.substring(index + 1);
  629. }
  630. /**
  631. * Returns the local name of the given node.
  632. *
  633. * @param qname Input name
  634. *
  635. * @return Prefix of name or empty string if none there
  636. */
  637. public static String getPrefixPart(String qname)
  638. {
  639. int index = qname.indexOf(':');
  640. return (index >= 0) ? qname.substring(0, index) : "";
  641. }
  642. }