001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 /*
019 * $Id: QName.java 468655 2006-10-28 07:12:06Z minchau $
020 */
021 package org.apache.xml.utils;
022
023 import java.util.Stack;
024 import java.util.StringTokenizer;
025
026 import org.apache.xml.res.XMLErrorResources;
027 import org.apache.xml.res.XMLMessages;
028
029 import org.w3c.dom.Element;
030
031 /**
032 * Class to represent a qualified name: "The name of an internal XSLT object,
033 * specifically a named template (see [7 Named Templates]), a mode (see [6.7 Modes]),
034 * an attribute set (see [8.1.4 Named Attribute Sets]), a key (see [14.2 Keys]),
035 * a locale (see [14.3 Number Formatting]), a variable or a parameter (see
036 * [12 Variables and Parameters]) is specified as a QName. If it has a prefix,
037 * then the prefix is expanded into a URI reference using the namespace declarations
038 * in effect on the attribute in which the name occurs. The expanded name
039 * consisting of the local part of the name and the possibly null URI reference
040 * is used as the name of the object. The default namespace is not used for
041 * unprefixed names."
042 * @xsl.usage general
043 */
044 public class QName implements java.io.Serializable
045 {
046 static final long serialVersionUID = 467434581652829920L;
047
048 /**
049 * The local name.
050 * @serial
051 */
052 protected String _localName;
053
054 /**
055 * The namespace URI.
056 * @serial
057 */
058 protected String _namespaceURI;
059
060 /**
061 * The namespace prefix.
062 * @serial
063 */
064 protected String _prefix;
065
066 /**
067 * The XML namespace.
068 */
069 public static final String S_XMLNAMESPACEURI =
070 "http://www.w3.org/XML/1998/namespace";
071
072 /**
073 * The cached hashcode, which is calculated at construction time.
074 * @serial
075 */
076 private int m_hashCode;
077
078 /**
079 * Constructs an empty QName.
080 * 20001019: Try making this public, to support Serializable? -- JKESS
081 */
082 public QName(){}
083
084 /**
085 * Constructs a new QName with the specified namespace URI and
086 * local name.
087 *
088 * @param namespaceURI The namespace URI if known, or null
089 * @param localName The local name
090 */
091 public QName(String namespaceURI, String localName)
092 {
093 this(namespaceURI, localName, false);
094 }
095
096 /**
097 * Constructs a new QName with the specified namespace URI and
098 * local name.
099 *
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 }