001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.wicket.markup;
018:
019: import org.apache.wicket.util.resource.IResourceStream;
020: import org.apache.wicket.util.string.Strings;
021:
022: /**
023: * A stream of {@link org.apache.wicket.markup.MarkupElement}s, subclases of
024: * which are {@link org.apache.wicket.markup.ComponentTag} and
025: * {@link org.apache.wicket.markup.RawMarkup}. A markup stream has a current
026: * index in the list of markup elements. The next markup element can be
027: * retrieved and the index advanced by calling next(). If the index hits the
028: * end, hasMore() will return false.
029: * <p>
030: * The current markup element can be accessed with get() and as a ComponentTag
031: * with getTag().
032: * <p>
033: * The stream can be seeked to a particular location with setCurrentIndex().
034: * <p>
035: * Convenience methods also exist to skip component tags (and any potentially
036: * nested markup) or raw markup.
037: * <p>
038: * Several boolean methods of the form at*() return true if the markup stream is
039: * positioned at a tag with a given set of characteristics.
040: * <p>
041: * The resource from which the markup was loaded can be retrieved with
042: * getResource().
043: *
044: * @author Jonathan Locke
045: */
046: public final class MarkupStream {
047: /** Element at currentIndex */
048: private MarkupElement current;
049:
050: /** Current index in markup stream */
051: private int currentIndex = 0;
052:
053: /** The markup element list */
054: private final Markup markup;
055:
056: /**
057: * Constructor
058: *
059: * @param markup
060: * List of markup elements
061: */
062: public MarkupStream(final Markup markup) {
063: this .markup = markup;
064:
065: if (markup.size() > 0) {
066: current = get(currentIndex);
067: }
068: }
069:
070: /**
071: * @return True if current markup element is a close tag
072: */
073: public boolean atCloseTag() {
074: return atTag() && getTag().isClose();
075: }
076:
077: /**
078: * @return True if current markup element is an openclose tag
079: */
080: public boolean atOpenCloseTag() {
081: return atTag() && getTag().isOpenClose();
082: }
083:
084: /**
085: * @param componentId
086: * Required component name attribute
087: * @return True if the current markup element is an openclose tag with the
088: * given component name
089: */
090: public boolean atOpenCloseTag(final String componentId) {
091: return atOpenCloseTag() && componentId.equals(getTag().getId());
092: }
093:
094: /**
095: * @return True if current markup element is an open tag
096: */
097: public boolean atOpenTag() {
098: return atTag() && getTag().isOpen();
099: }
100:
101: /**
102: * @param id
103: * Required component id attribute
104: * @return True if the current markup element is an open tag with the given
105: * component name
106: */
107: public boolean atOpenTag(final String id) {
108: return atOpenTag() && id.equals(getTag().getId());
109: }
110:
111: /**
112: * @return True if current markup element is a tag
113: */
114: public boolean atTag() {
115: return current instanceof ComponentTag;
116: }
117:
118: /**
119: * Compare this markup stream with another one
120: *
121: * @param that
122: * The other markup stream
123: * @return True if each MarkupElement in this matches each element in that
124: */
125: public boolean equalTo(final MarkupStream that) {
126: // While a has more markup elements
127: while (hasMore()) {
128: // Get an element from each
129: final MarkupElement this Element = this .get();
130: final MarkupElement thatElement = that.get();
131:
132: // and if the elements are not equal
133: if (this Element != null && thatElement != null) {
134: if (!this Element.equalTo(thatElement)) {
135: // fail the comparison
136: return false;
137: }
138: } else {
139: // If one element is null,
140: if (!(this Element == null && thatElement == null)) {
141: // fail the comparison
142: return false;
143: }
144: }
145: next();
146: that.next();
147: }
148:
149: // If we've run out of markup elements in b
150: if (!that.hasMore()) {
151: // then the two streams match perfectly
152: return true;
153: }
154:
155: // Stream b had extra elements
156: return false;
157: }
158:
159: /**
160: * True, if associate markup is the same. It will change e.g. if the markup
161: * file has been re-loaded or the locale has been changed.
162: *
163: * @param markupStream
164: * The markup stream to compare with.
165: * @return true, if markup has not changed
166: */
167: public final boolean equalMarkup(final MarkupStream markupStream) {
168: if (markupStream == null) {
169: return false;
170: }
171: return markup == markupStream.markup;
172: }
173:
174: /**
175: * Find the markup element index of the component with 'path'
176: *
177: * @param path
178: * The component path expression
179: * @param id
180: * The component's id to search for
181: * @return -1, if not found
182: */
183: public final int findComponentIndex(final String path,
184: final String id) {
185: return markup.findComponentIndex(path, id);
186: }
187:
188: /**
189: * @return The current markup element
190: */
191: public MarkupElement get() {
192: return current;
193: }
194:
195: /**
196: * @param index
197: * The index of a markup element
198: * @return The MarkupElement element
199: */
200: public MarkupElement get(final int index) {
201: return markup.get(index);
202: }
203:
204: /**
205: * Get the component/container's Class which is directly associated with the
206: * stream.
207: *
208: * @return The component's class
209: */
210: public final Class getContainerClass() {
211: return markup.getMarkupResourceData().getResource()
212: .getMarkupClass();
213: }
214:
215: /**
216: * @return Current index in markup stream
217: */
218: public int getCurrentIndex() {
219: return currentIndex;
220: }
221:
222: /**
223: * Gets the markup encoding. A markup encoding may be specified in a markup
224: * file with an XML encoding specifier of the form <?xml ...
225: * encoding="..." ?>.
226: *
227: * @return The encoding, or null if not found
228: */
229: public final String getEncoding() {
230: return markup.getMarkupResourceData().getEncoding();
231: }
232:
233: /**
234: * @return The resource where this markup stream came from
235: */
236: public IResourceStream getResource() {
237: return markup.getMarkupResourceData().getResource();
238: }
239:
240: /**
241: * @return The current markup element as a markup tag
242: */
243: public ComponentTag getTag() {
244: if (current instanceof ComponentTag) {
245: return (ComponentTag) current;
246: }
247:
248: throwMarkupException("Tag expected");
249:
250: return null;
251: }
252:
253: /**
254: * Get the wicket namespace valid for this specific markup
255: *
256: * @return wicket namespace
257: */
258: public final String getWicketNamespace() {
259: return markup.getMarkupResourceData().getWicketNamespace();
260: }
261:
262: /**
263: * Return the XML declaration string, in case if found in the markup.
264: *
265: * @return Null, if not found.
266: */
267: public String getXmlDeclaration() {
268: return markup.getMarkupResourceData().getXmlDeclaration();
269: }
270:
271: /**
272: * @return True if this markup stream has more MarkupElement elements
273: */
274: public boolean hasMore() {
275: return currentIndex < markup.size();
276: }
277:
278: /**
279: *
280: * @return true, if underlying markup has been merged (inheritance)
281: */
282: public final boolean isMergedMarkup() {
283: return markup instanceof MergedMarkup;
284: }
285:
286: /**
287: * Note:
288: *
289: * @return The next markup element in the stream
290: */
291: public MarkupElement next() {
292: if (++currentIndex < markup.size()) {
293: return current = get(currentIndex);
294: }
295:
296: return null;
297: }
298:
299: /**
300: * @param currentIndex
301: * New current index in the stream
302: */
303: public void setCurrentIndex(final int currentIndex) {
304: current = get(currentIndex);
305: this .currentIndex = currentIndex;
306: }
307:
308: /**
309: * Skips this component and all nested components
310: */
311: public final void skipComponent() {
312: // Get start tag
313: final ComponentTag startTag = getTag();
314:
315: if (startTag.isOpen()) {
316: // With HTML not all tags require a close tag which
317: // must have been detected by the HtmlHandler earlier on.
318: if (startTag.hasNoCloseTag() == false) {
319: // Skip <tag>
320: next();
321:
322: // Skip nested components
323: skipToMatchingCloseTag(startTag);
324: }
325:
326: // Skip </tag>
327: next();
328: } else if (startTag.isOpenClose()) {
329: // Skip <tag/>
330: next();
331: } else {
332: // We were something other than <tag> or <tag/>
333: throwMarkupException("Skip component called on bad markup element "
334: + startTag);
335: }
336: }
337:
338: /**
339: * Skips any raw markup at the current position
340: */
341: public void skipRawMarkup() {
342: while (true) {
343: if (current instanceof RawMarkup) {
344: if (next() != null) {
345: continue;
346: }
347: } else if ((current instanceof ComponentTag)
348: && !(current instanceof WicketTag)) {
349: ComponentTag tag = (ComponentTag) current;
350: if (tag.isAutoComponentTag()) {
351: if (next() != null) {
352: continue;
353: }
354: } else if (tag.isClose()
355: && tag.getOpenTag().isAutoComponentTag()) {
356: if (next() != null) {
357: continue;
358: }
359: }
360: }
361: break;
362: }
363: }
364:
365: /**
366: * Skips any markup at the current position until the wicket tag name is
367: * found.
368: *
369: * @param wicketTagName
370: * wicket tag name to seek
371: */
372: public void skipUntil(final String wicketTagName) {
373: while (true) {
374: if ((current instanceof WicketTag)
375: && ((WicketTag) current).getName().equals(
376: wicketTagName)) {
377: return;
378: }
379:
380: // go on until we reach the end
381: if (next() == null) {
382: return;
383: }
384: }
385: }
386:
387: /**
388: * Renders markup until a closing tag for openTag is reached.
389: *
390: * @param openTag
391: * The open tag
392: */
393: public void skipToMatchingCloseTag(final ComponentTag openTag) {
394: // Loop through the markup in this container
395: while (hasMore()) {
396: // If the current markup tag closes the openTag
397: if (get().closes(openTag)) {
398: // Done!
399: return;
400: }
401:
402: // Skip element
403: next();
404: }
405: throwMarkupException("Expected close tag for " + openTag);
406: }
407:
408: /**
409: * Throws a new markup exception
410: *
411: * @param message
412: * The exception message
413: * @throws MarkupException
414: */
415: public void throwMarkupException(final String message) {
416: throw new MarkupException(this , message);
417: }
418:
419: /**
420: * @return An HTML string highlighting the current position in the markup
421: * stream
422: */
423: public String toHtmlDebugString() {
424: final StringBuffer buffer = new StringBuffer();
425:
426: for (int i = 0; i < markup.size(); i++) {
427: if (i == currentIndex) {
428: buffer.append("<font color = \"red\">");
429: }
430:
431: final MarkupElement element = markup.get(i);
432:
433: buffer.append(Strings
434: .escapeMarkup(element.toString(), true).toString());
435:
436: if (i == currentIndex) {
437: buffer.append("</font>");
438: }
439: }
440:
441: return buffer.toString();
442: }
443:
444: /**
445: * @return String representation of markup stream
446: */
447: public String toString() {
448: return "[markup = "
449: + String.valueOf(markup)
450: + ", index = "
451: + currentIndex
452: + ", current = "
453: + ((current == null) ? "null" : current
454: .toUserDebugString()) + "]";
455: }
456: }
|