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