001: /*
002: * $Id: XmlTag.java 460102 2006-04-01 23:34:49Z jcompagner $ $Revision: 460102 $
003: * $Date: 2006-04-02 01:34:49 +0200 (Sun, 02 Apr 2006) $
004: *
005: * ==============================================================================
006: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
007: * use this file except in compliance with the License. You may obtain a copy of
008: * 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, WITHOUT
014: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
015: * License for the specific language governing permissions and limitations under
016: * the License.
017: */
018: package wicket.markup.parser;
019:
020: import java.util.Iterator;
021: import java.util.Map;
022:
023: import wicket.markup.MarkupElement;
024: import wicket.util.lang.EnumeratedType;
025: import wicket.util.string.AppendingStringBuffer;
026: import wicket.util.string.StringValue;
027: import wicket.util.string.Strings;
028: import wicket.util.value.AttributeMap;
029:
030: /**
031: * A subclass of MarkupElement which represents a tag including namespace and
032: * its optional attributes. XmlTags are returned by the XML parser.
033: *
034: * @author Jonathan Locke
035: */
036: public class XmlTag extends MarkupElement {
037: /** A close tag, like </TAG>. */
038: public static final Type CLOSE = new Type("CLOSE");
039:
040: /** An open tag, like <TAG componentId = "xyz">. */
041: public static final Type OPEN = new Type("OPEN");
042:
043: /** An open/close tag, like <TAG componentId = "xyz"/>. */
044: public static final Type OPEN_CLOSE = new Type("OPEN_CLOSE");
045:
046: /** Attribute map. */
047: private AttributeMap attributes;
048:
049: /** Column number. */
050: int columnNumber;
051:
052: /** Length of this tag in characters. */
053: int length;
054:
055: /** Line number. */
056: int lineNumber;
057:
058: /** Name of tag, such as "img" or "input". */
059: String name;
060:
061: /** Namespace of the tag, if available, such as <wicket:link ...> */
062: String namespace;
063:
064: /** Position of this tag in the input that was parsed. */
065: int pos;
066:
067: /** Full text of tag. */
068: String text;
069:
070: /** The tag type (OPEN, CLOSE or OPEN_CLOSE). */
071: Type type;
072:
073: /** Any component tag that this tag closes. */
074: private XmlTag closes;
075:
076: /** If mutable, the immutable tag that this tag is a mutable copy of. */
077: private XmlTag copyOf = this ;
078:
079: /** True if this tag is mutable, false otherwise. */
080: private boolean isMutable = true;
081:
082: /** True if the name of this tag was changed. */
083: private boolean nameChanged = false;
084:
085: /**
086: * Enumerated type for different kinds of component tags.
087: */
088: public static final class Type extends EnumeratedType {
089: private static final long serialVersionUID = 1L;
090:
091: /**
092: * Construct.
093: *
094: * @param name
095: * name of type
096: */
097: Type(final String name) {
098: super (name);
099: }
100: }
101:
102: /**
103: * Construct.
104: */
105: public XmlTag() {
106: super ();
107: }
108:
109: /**
110: * Gets whether this tag closes the provided open tag.
111: *
112: * @param open
113: * The open tag
114: * @return True if this tag closes the given open tag
115: */
116: public final boolean closes(final XmlTag open) {
117: return (closes == open) || (closes == open.copyOf);
118: }
119:
120: /**
121: * Gets a hashmap of this tag's attributes.
122: *
123: * @return The tag's attributes
124: */
125: public AttributeMap getAttributes() {
126: if (attributes == null) {
127: if (copyOf == this ) {
128: attributes = new AttributeMap();
129: } else {
130: attributes = new AttributeMap(copyOf.attributes);
131: }
132: }
133: return attributes;
134: }
135:
136: /**
137: * Get the column number.
138: *
139: * @return Returns the columnNumber.
140: */
141: public int getColumnNumber() {
142: return columnNumber;
143: }
144:
145: /**
146: * Gets the length of the tag in characters.
147: *
148: * @return The tag's length
149: */
150: public int getLength() {
151: return length;
152: }
153:
154: /**
155: * Get the line number.
156: *
157: * @return Returns the lineNumber.
158: */
159: public int getLineNumber() {
160: return lineNumber;
161: }
162:
163: /**
164: * Gets the name of the tag, for example the tag <code><b></code>'s name would be 'b'.
165: *
166: * @return The tag's name
167: */
168: public String getName() {
169: return name;
170: }
171:
172: /**
173: * Get whether the name of this component tag was changed.
174: *
175: * @return Returns true if the name of this component tag was changed
176: */
177: public boolean getNameChanged() {
178: return nameChanged;
179: }
180:
181: /**
182: * Namespace of the tag, if available. For example, <wicket:link>.
183: *
184: * @return The tag's namespace
185: */
186: public String getNamespace() {
187: return namespace;
188: }
189:
190: /**
191: * Assuming this is a close tag, return the corresponding open tag
192: *
193: * @return The open tag. Null, if no open tag available
194: */
195: public final XmlTag getOpenTag() {
196: return closes;
197: }
198:
199: /**
200: * Gets the location of the tag in the input string.
201: *
202: * @return Tag location (index in input string)
203: */
204: public int getPos() {
205: return pos;
206: }
207:
208: /**
209: * Get a string attribute.
210: *
211: * @param key
212: * The key
213: * @return The string value
214: */
215: public CharSequence getString(final String key) {
216: return getAttributes().getCharSequence(key);
217: }
218:
219: /**
220: * Get the tag type.
221: *
222: * @return the tag type (OPEN, CLOSE or OPEN_CLOSE).
223: */
224: public Type getType() {
225: return type;
226: }
227:
228: /**
229: * Gets whether this is a close tag.
230: *
231: * @return True if this tag is a close tag
232: */
233: public boolean isClose() {
234: return type == CLOSE;
235: }
236:
237: /**
238: *
239: * @return True, if tag is mutable
240: */
241: public final boolean isMutable() {
242: return isMutable;
243: }
244:
245: /**
246: * Gets whether this is an open tag.
247: *
248: * @return True if this tag is an open tag
249: */
250: public boolean isOpen() {
251: return type == OPEN;
252: }
253:
254: /**
255: * Gets whether this tag is an open/ close tag.
256: *
257: * @return True if this tag is an open and a close tag
258: */
259: public boolean isOpenClose() {
260: return type == OPEN_CLOSE;
261: }
262:
263: /**
264: * Compare tag name including namespace
265: *
266: * @param tag
267: * @return true if name and namespace are equal
268: */
269: public boolean hasEqualTagName(final XmlTag tag) {
270: if (!getName().equalsIgnoreCase(tag.getName())) {
271: return false;
272: }
273:
274: if ((getNamespace() == null) && (tag.getNamespace() == null)) {
275: return true;
276: }
277:
278: if ((getNamespace() != null) && (tag.getNamespace() != null)) {
279: return getNamespace().equalsIgnoreCase(tag.getNamespace());
280: }
281:
282: return false;
283: }
284:
285: /**
286: * Makes this tag object immutable by making the attribute map unmodifiable.
287: * Immutable tags cannot be made mutable again. They can only be copied into
288: * new mutable tag objects.
289: */
290: public void makeImmutable() {
291: if (isMutable) {
292: isMutable = false;
293: if (attributes != null) {
294: attributes.makeImmutable();
295: }
296: }
297: }
298:
299: /**
300: * Gets this tag if it is already mutable, or a mutable copy of this tag if
301: * it is immutable.
302: *
303: * @return This tag if it is already mutable, or a mutable copy of this tag
304: * if it is immutable.
305: */
306: public XmlTag mutable() {
307: if (isMutable) {
308: return this ;
309: } else {
310: final XmlTag tag = new XmlTag();
311:
312: tag.namespace = namespace;
313: tag.name = name;
314: tag.pos = pos;
315: tag.length = length;
316: tag.text = text;
317: tag.type = type;
318: tag.isMutable = true;
319: tag.closes = closes;
320: tag.copyOf = copyOf;
321:
322: return tag;
323: }
324: }
325:
326: /**
327: * Puts a boolean attribute.
328: *
329: * @param key
330: * The key
331: * @param value
332: * The value
333: * @return previous value associated with specified key, or null if there
334: * was no mapping for key. A null return can also indicate that the
335: * map previously associated null with the specified key, if the
336: * implementation supports null values.
337: */
338: public Object put(final String key, final boolean value) {
339: return put(key, Boolean.toString(value));
340: }
341:
342: /**
343: * Puts an int attribute.
344: *
345: * @param key
346: * The key
347: * @param value
348: * The value
349: * @return previous value associated with specified key, or null if there
350: * was no mapping for key. A null return can also indicate that the
351: * map previously associated null with the specified key, if the
352: * implementation supports null values.
353: */
354: public Object put(final String key, final int value) {
355: return put(key, Integer.toString(value));
356: }
357:
358: /**
359: * Puts a string attribute.
360: *
361: * @param key
362: * The key
363: * @param value
364: * The value
365: * @return previous value associated with specified key, or null if there
366: * was no mapping for key. A null return can also indicate that the
367: * map previously associated null with the specified key, if the
368: * implementation supports null values.
369: */
370: public Object put(final String key, final CharSequence value) {
371: return getAttributes().put(key, value);
372: }
373:
374: /**
375: * Puts a {@link StringValue}attribute.
376: *
377: * @param key
378: * The key
379: * @param value
380: * The value
381: * @return previous value associated with specified key, or null if there
382: * was no mapping for key. A null return can also indicate that the
383: * map previously associated null with the specified key, if the
384: * implementation supports null values.
385: */
386: public Object put(final String key, final StringValue value) {
387: return getAttributes().put(key,
388: (value != null) ? value.toString() : null);
389: }
390:
391: /**
392: * Puts all attributes in map
393: *
394: * @param map
395: * A key/value map
396: */
397: public void putAll(final Map map) {
398: for (final Iterator iterator = map.keySet().iterator(); iterator
399: .hasNext();) {
400: final String key = (String) iterator.next();
401: Object value = map.get(key);
402: put(key, (value != null) ? value.toString() : null);
403: }
404: }
405:
406: /**
407: * Removes an attribute.
408: *
409: * @param key
410: * The key to remove
411: */
412: public void remove(final String key) {
413: getAttributes().remove(key);
414: }
415:
416: /**
417: * Sets the tag name.
418: *
419: * @param name
420: * New tag name
421: */
422: public void setName(final String name) {
423: if (isMutable) {
424: this .name = name;
425: this .nameChanged = true;
426: } else {
427: throw new UnsupportedOperationException(
428: "Attempt to set name of immutable tag");
429: }
430: }
431:
432: /**
433: * Sets the tag namespace.
434: *
435: * @param namespace
436: * New tag name
437: */
438: public void setNamespace(final String namespace) {
439: if (isMutable) {
440: this .namespace = namespace;
441: this .nameChanged = true;
442: } else {
443: throw new UnsupportedOperationException(
444: "Attempt to set namespace of immutable tag");
445: }
446: }
447:
448: /**
449: * Assuming this is a close tag, assign it's corresponding open tag.
450: *
451: * @param tag
452: * the open-tag
453: * @throws RuntimeException
454: * if 'this' is not a close tag
455: */
456: public void setOpenTag(final XmlTag tag) {
457: this .closes = tag;
458: }
459:
460: /**
461: * Sets type of this tag if it is not immutable.
462: *
463: * @param type
464: * The new type
465: */
466: public void setType(final Type type) {
467: if (isMutable) {
468: this .type = type;
469: } else {
470: throw new UnsupportedOperationException(
471: "Attempt to set type of immutable tag");
472: }
473: }
474:
475: /**
476: * Converts this object to a string representation.
477: *
478: * @return String version of this object
479: */
480: public String toDebugString() {
481: return "[Tag name = " + name + ", pos = " + pos + ", line = "
482: + lineNumber + ", length = " + length
483: + ", attributes = [" + getAttributes() + "], type = "
484: + type + "]";
485: }
486:
487: /**
488: * Converts this object to a string representation.
489: *
490: * @return String version of this object
491: */
492: public String toString() {
493: return toCharSequence().toString();
494: }
495:
496: /**
497: * @see wicket.markup.MarkupElement#toCharSequence()
498: */
499: public CharSequence toCharSequence() {
500: if (!isMutable && (text != null)) {
501: return text;
502: }
503:
504: return toXmlString(null);
505: }
506:
507: /**
508: * Converts this object to a string representation.
509: *
510: * @return String version of this object
511: */
512: public String toUserDebugString() {
513: return "'" + toString() + "' (line " + lineNumber + ", column "
514: + columnNumber + ")";
515: }
516:
517: /**
518: * Assuming some attributes have been changed, toXmlString() rebuilds the
519: * String on based on the tags informations.
520: *
521: * @param attributeToBeIgnored
522: * @return A xml string matching the tag
523: */
524: public CharSequence toXmlString(final String attributeToBeIgnored) {
525: final AppendingStringBuffer buffer = new AppendingStringBuffer();
526:
527: buffer.append('<');
528:
529: if (type == CLOSE) {
530: buffer.append('/');
531: }
532:
533: if (namespace != null) {
534: buffer.append(namespace);
535: buffer.append(':');
536: }
537:
538: buffer.append(name);
539:
540: final AttributeMap attributes = getAttributes();
541: if (attributes.size() > 0) {
542: final Iterator iterator = attributes.keySet().iterator();
543: for (; iterator.hasNext();) {
544: final String key = (String) iterator.next();
545: if ((key != null)
546: && ((attributeToBeIgnored == null) || !key
547: .equalsIgnoreCase(attributeToBeIgnored))) {
548: buffer.append(" ");
549: buffer.append(key);
550: CharSequence value = getString(key);
551:
552: // Attributes without values are possible, e.g. 'disabled'
553: if (value != null) {
554: buffer.append("=\"");
555: value = Strings.replaceAll(value, "\"", "\\\"");
556: buffer.append(value);
557: buffer.append("\"");
558: }
559: }
560: }
561: }
562:
563: if (type == OPEN_CLOSE) {
564: buffer.append('/');
565: }
566:
567: buffer.append('>');
568:
569: return buffer;
570: }
571: }
|