001: /*
002: * $Id: MergedMarkup.java 460559 2006-05-08 16:02:10Z jdonnerstag $ $Revision:
003: * 4913 $ $Date: 2006-05-08 18:02:10 +0200 (Mon, 08 May 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;
019:
020: import org.apache.commons.logging.Log;
021: import org.apache.commons.logging.LogFactory;
022:
023: import wicket.Page;
024: import wicket.WicketRuntimeException;
025: import wicket.markup.parser.XmlTag;
026: import wicket.markup.parser.filter.HtmlHeaderSectionHandler;
027: import wicket.util.string.Strings;
028:
029: /**
030: * A Markup class which represents merged markup, as it is required for markup
031: * inheritance.
032: * <p>
033: * The Markups are merged at load time. Deep markup hierarchies are supported.
034: * Multiple inheritance is not.
035: * <p>
036: * The markup resource file, which is associated with the markup, will be the
037: * resource of the requested markup file. The base markup resources are not.
038: * <p>
039: * Base Markup must have a <wicket:hild/> tag which the position where the
040: * derived markup is inserted. From the derived markup all tags in between
041: * <wicket:extend> and </wicket:extend> will be inserted.
042: * <p>
043: * In addition, all <wicket:head> regions are copied as well as the body
044: * onLoad attribute. This allows to develop completely self-contained plug &
045: * play components including javascript etc.
046: *
047: * @author Juergen Donnerstag
048: */
049: public class MergedMarkup extends Markup {
050: private final static Log log = LogFactory
051: .getLog(MergedMarkup.class);
052:
053: /**
054: * Merge inherited and base markup.
055: *
056: * @param markup
057: * The inherited markup
058: * @param baseMarkup
059: * The base markup
060: * @param extendIndex
061: * Index where <wicket:extend> has been found
062: */
063: MergedMarkup(final Markup markup, final Markup baseMarkup,
064: int extendIndex) {
065: // Copy settings from derived markup
066: setResource(markup.getResource());
067: setXmlDeclaration(markup.getXmlDeclaration());
068: setEncoding(markup.getEncoding());
069: setWicketNamespace(markup.getWicketNamespace());
070:
071: if (log.isDebugEnabled()) {
072: String derivedResource = Strings.afterLast(markup
073: .getResource().toString(), '/');
074: String baseResource = Strings.afterLast(baseMarkup
075: .getResource().toString(), '/');
076: log.debug("Merge markup: derived markup: "
077: + derivedResource + "; base markup: "
078: + baseResource);
079: }
080:
081: // Merge derived and base markup
082: merge(markup, baseMarkup, extendIndex);
083:
084: // Initialize internals based on new markup
085: initialize();
086:
087: if (log.isDebugEnabled()) {
088: log.debug("Merge markup: " + toDebugString());
089: }
090: }
091:
092: /**
093: * Return the body onLoad attribute of the markup
094: *
095: * @param markup
096: * @return onLoad attribute
097: */
098: private String getBodyOnLoadString(final Markup markup) {
099: int i = 0;
100:
101: // The markup must have a <wicket:head> region, else copying the
102: // body onLoad attributes doesn't make sense
103: for (; i < markup.size(); i++) {
104: MarkupElement elem = markup.get(i);
105: if (elem instanceof WicketTag) {
106: WicketTag tag = (WicketTag) elem;
107: if (tag.isClose() && tag.isHeadTag()) {
108: // Ok, we found <wicket:head>
109: break;
110: } else if (tag.isMajorWicketComponentTag()) {
111: // Short cut: We found <wicket:panel> or <wicket:border>.
112: // There certainly will be no <wicket:head> later on.
113: return null;
114: }
115: } else if (elem instanceof ComponentTag) {
116: ComponentTag tag = (ComponentTag) elem;
117: if (TagUtils.isBodyTag(tag)) {
118: // Short cut: We found <body> but no <wicket:head>.
119: // There certainly will be no <wicket:head> later on.
120: return null;
121: }
122: }
123: }
124:
125: // Found </wicket:head> => get body onLoad
126: for (; i < markup.size(); i++) {
127: MarkupElement elem = markup.get(i);
128: if (elem instanceof ComponentTag) {
129: ComponentTag tag = (ComponentTag) elem;
130: if (tag.isOpen() && TagUtils.isBodyTag(tag)) {
131: String onLoad = tag.getAttributes().getString(
132: "onload");
133: return onLoad;
134: }
135: }
136: }
137:
138: return null;
139: }
140:
141: /**
142: * Merge inherited and base markup.
143: *
144: * @param markup
145: * The inherited markup
146: * @param baseMarkup
147: * The base markup
148: * @param extendIndex
149: * Index where <wicket:extend> has been found
150: */
151: private void merge(final Markup markup, final Markup baseMarkup,
152: int extendIndex) {
153: // True if either <wicket:head> or <head> has been processed
154: boolean wicketHeadProcessed = false;
155:
156: // Add all elements from the base markup to the new list
157: // until <wicket:child/> is found. Convert <wicket:child/>
158: // into <wicket:child> and add it as well.
159: WicketTag childTag = null;
160: int baseIndex = 0;
161: for (; baseIndex < baseMarkup.size(); baseIndex++) {
162: MarkupElement element = baseMarkup.get(baseIndex);
163: if (element instanceof RawMarkup) {
164: // Add the element to the merged list
165: addMarkupElement(element);
166: continue;
167: }
168:
169: final ComponentTag tag = (ComponentTag) element;
170:
171: // Make sure all tags of the base markup remember where they are
172: // from
173: if ((baseMarkup.getResource() != null)
174: && (tag.getMarkupClass() == null)) {
175: tag.setMarkupClass(baseMarkup.getResource()
176: .getMarkupClass());
177: }
178:
179: if (element instanceof WicketTag) {
180: WicketTag wtag = (WicketTag) element;
181:
182: // Found wicket.child in base markup. In case of 3+ level
183: // inheritance make sure the child tag is not from one of the
184: // deeper levels
185: if (wtag.isChildTag()
186: && (tag.getMarkupClass() == baseMarkup
187: .getResource().getMarkupClass())) {
188: if (wtag.isOpenClose()) {
189: // <wicket:child /> => <wicket:child>...</wicket:child>
190: childTag = wtag;
191: WicketTag childOpenTag = (WicketTag) wtag
192: .mutable();
193: childOpenTag.getXmlTag().setType(XmlTag.OPEN);
194: childOpenTag.setMarkupClass(baseMarkup
195: .getResource().getMarkupClass());
196: addMarkupElement(childOpenTag);
197: break;
198: } else if (wtag.isOpen()) {
199: // <wicket:child>
200: addMarkupElement(wtag);
201: break;
202: } else {
203: throw new WicketRuntimeException(
204: "Did not expect a </wicket:child> tag in "
205: + baseMarkup.toString());
206: }
207: }
208:
209: // Process the head of the extended markup only once
210: if (wicketHeadProcessed == false) {
211: // if </wicket:head> in base markup
212: if (wtag.isClose() && wtag.isHeadTag()) {
213: wicketHeadProcessed = true;
214:
215: // Add the current close tag
216: addMarkupElement(wtag);
217:
218: // Add the <wicket:head> body from the derived markup.
219: copyWicketHead(markup, extendIndex);
220:
221: // Do not add the current tag. It has already been
222: // added.
223: continue;
224: }
225:
226: // if <wicket:panel> or ... in base markup
227: if (wtag.isOpen()
228: && wtag.isMajorWicketComponentTag()) {
229: wicketHeadProcessed = true;
230:
231: // Add the <wicket:head> body from the derived markup.
232: copyWicketHead(markup, extendIndex);
233: }
234: }
235: }
236:
237: // Process the head of the extended markup only once
238: if (wicketHeadProcessed == false) {
239: // if <head> in base markup
240: if ((tag.isClose() && TagUtils.isHeadTag(tag))
241: || (tag.isOpen() && TagUtils.isBodyTag(tag))) {
242: wicketHeadProcessed = true;
243:
244: // Add the <wicket:head> body from the derived markup.
245: copyWicketHead(markup, extendIndex);
246: }
247: }
248:
249: // Make sure the body onLoad attribute from the extended markup is
250: // copied to the new markup
251: if (tag.isOpen() && TagUtils.isBodyTag(tag)) {
252: // Get the body onLoad attribute from derived markup
253: final String onLoad = getBodyOnLoadString(markup);
254:
255: String onLoadBase = tag.getAttributes().getString(
256: "onload");
257: if (onLoadBase == null) {
258: if (onLoad != null) {
259: ComponentTag mutableTag = tag.mutable();
260: mutableTag.getAttributes()
261: .put("onload", onLoad);
262: element = mutableTag;
263: }
264: } else if (onLoad != null) {
265: onLoadBase += onLoad;
266: ComponentTag mutableTag = tag.mutable();
267: mutableTag.getAttributes()
268: .put("onload", onLoadBase);
269: element = mutableTag;
270: }
271: }
272:
273: // Add the element to the merged list
274: addMarkupElement(element);
275: }
276:
277: if (baseIndex == baseMarkup.size()) {
278: throw new WicketRuntimeException(
279: "Expected to find <wicket:child/> in base markup: "
280: + baseMarkup.toString());
281: }
282:
283: // Now append all elements from the derived markup starting with
284: // <wicket:extend> until </wicket:extend> to the list
285: for (; extendIndex < markup.size(); extendIndex++) {
286: MarkupElement element = markup.get(extendIndex);
287: addMarkupElement(element);
288:
289: if (element instanceof WicketTag) {
290: WicketTag wtag = (WicketTag) element;
291: if (wtag.isExtendTag() && wtag.isClose()) {
292: break;
293: }
294: }
295: }
296:
297: if (extendIndex == markup.size()) {
298: throw new WicketRuntimeException(
299: "Missing close tag </wicket:extend> in derived markup: "
300: + markup.toString());
301: }
302:
303: // If <wicket:child> than skip the body and find </wicket:child>
304: if (((ComponentTag) baseMarkup.get(baseIndex)).isOpen()) {
305: for (baseIndex++; baseIndex < baseMarkup.size(); baseIndex++) {
306: MarkupElement element = baseMarkup.get(baseIndex);
307: if (element instanceof WicketTag) {
308: WicketTag tag = (WicketTag) element;
309: if (tag.isChildTag() && tag.isClose()) {
310: // Ok, skipped the childs content
311: tag.setMarkupClass(baseMarkup.getResource()
312: .getMarkupClass());
313: addMarkupElement(tag);
314: break;
315: } else {
316: throw new WicketRuntimeException(
317: "Wicket tags like <wicket:xxx> are not allowed in between <wicket:child> and </wicket:child> tags: "
318: + markup.toString());
319: }
320: } else if (element instanceof ComponentTag) {
321: throw new WicketRuntimeException(
322: "Wicket tags identified by wicket:id are not allowed in between <wicket:child> and </wicket:child> tags: "
323: + markup.toString());
324: }
325: }
326:
327: // </wicket:child> not found
328: if (baseIndex == baseMarkup.size()) {
329: throw new WicketRuntimeException(
330: "Expected to find </wicket:child> in base markup: "
331: + baseMarkup.toString());
332: }
333: } else {
334: // And now all remaining elements from the derived markup.
335: // But first add </wicket:child>
336: WicketTag childCloseTag = (WicketTag) childTag.mutable();
337: childCloseTag.getXmlTag().setType(XmlTag.CLOSE);
338: childCloseTag.setMarkupClass(baseMarkup.getResource()
339: .getMarkupClass());
340: addMarkupElement(childCloseTag);
341: }
342:
343: for (baseIndex++; baseIndex < baseMarkup.size(); baseIndex++) {
344: MarkupElement element = baseMarkup.get(baseIndex);
345: addMarkupElement(element);
346:
347: // Make sure all tags of the base markup remember where they are
348: // from
349: if ((element instanceof ComponentTag)
350: && (baseMarkup.getResource() != null)) {
351: ComponentTag tag = (ComponentTag) element;
352: tag.setMarkupClass(baseMarkup.getResource()
353: .getMarkupClass());
354: }
355: }
356:
357: // Automatically add <head> if missing and required. On a Page
358: // it must enclose ALL of the <wicket:head> tags.
359: // Note: HtmlHeaderSectionHandler does something similar, but because
360: // markup filters are not called for merged markup again, ...
361: if (Page.class.isAssignableFrom(markup.getResource()
362: .getMarkupClass())) {
363: // Find the position inside the markup for first <wicket:head>,
364: // last </wicket:head> and <head>
365: int hasOpenWicketHead = -1;
366: int hasCloseWicketHead = -1;
367: int hasHead = -1;
368: for (int i = 0; i < size(); i++) {
369: MarkupElement element = get(i);
370:
371: if ((hasOpenWicketHead == -1)
372: && (element instanceof WicketTag)
373: && ((WicketTag) element).isHeadTag()) {
374: hasOpenWicketHead = i;
375: } else if ((element instanceof WicketTag)
376: && ((WicketTag) element).isHeadTag()
377: && ((WicketTag) element).isClose()) {
378: hasCloseWicketHead = i;
379: } else if ((hasHead == -1)
380: && (element instanceof ComponentTag)
381: && TagUtils.isHeadTag((ComponentTag) element)) {
382: hasHead = i;
383: } else if ((hasHead != -1) && (hasOpenWicketHead != -1)) {
384: break;
385: }
386: }
387:
388: // If a <head> tag is missing, insert it automatically
389: if ((hasOpenWicketHead != -1) && (hasHead == -1)) {
390: final XmlTag headOpenTag = new XmlTag();
391: headOpenTag.setName("head");
392: headOpenTag.setType(XmlTag.OPEN);
393: final ComponentTag openTag = new ComponentTag(
394: headOpenTag);
395: openTag.setId(HtmlHeaderSectionHandler.HEADER_ID);
396:
397: final XmlTag headCloseTag = new XmlTag();
398: headCloseTag.setName(headOpenTag.getName());
399: headCloseTag.setType(XmlTag.CLOSE);
400: final ComponentTag closeTag = new ComponentTag(
401: headCloseTag);
402: closeTag.setOpenTag(openTag);
403: closeTag.setId(HtmlHeaderSectionHandler.HEADER_ID);
404:
405: addMarkupElement(hasOpenWicketHead, openTag);
406: addMarkupElement(hasCloseWicketHead + 2, closeTag);
407: }
408: }
409: }
410:
411: /**
412: * Append the wicket:head regions from the extended markup to the current
413: * markup
414: *
415: * @param markup
416: * @param extendIndex
417: */
418: private void copyWicketHead(final Markup markup, int extendIndex) {
419: boolean copy = false;
420: for (int i = 0; i < extendIndex; i++) {
421: MarkupElement elem = markup.get(i);
422: if (elem instanceof WicketTag) {
423: WicketTag etag = (WicketTag) elem;
424: if (etag.isHeadTag()) {
425: if (etag.isOpen()) {
426: copy = true;
427: } else {
428: addMarkupElement(elem);
429: break;
430: }
431: }
432: }
433:
434: if (copy == true) {
435: addMarkupElement(elem);
436: }
437: }
438: }
439: }
|