001: /*******************************************************************************
002: * Copyright (c) 2004, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.intro.impl.model;
011:
012: import java.util.Hashtable;
013: import java.util.Vector;
014:
015: import org.eclipse.core.runtime.IPath;
016: import org.eclipse.core.runtime.Path;
017: import org.eclipse.core.runtime.Platform;
018: import org.eclipse.help.internal.UAElement;
019: import org.eclipse.help.internal.UAElementFactory;
020: import org.eclipse.help.internal.dynamic.DocumentProcessor;
021: import org.eclipse.help.internal.dynamic.FilterHandler;
022: import org.eclipse.help.internal.dynamic.ProcessorHandler;
023: import org.eclipse.ui.internal.intro.impl.IIntroConstants;
024: import org.eclipse.ui.internal.intro.impl.model.loader.ExtensionPointManager;
025: import org.eclipse.ui.internal.intro.impl.model.loader.IntroContentParser;
026: import org.eclipse.ui.internal.intro.impl.model.loader.ModelLoaderUtil;
027: import org.eclipse.ui.internal.intro.impl.model.util.BundleUtil;
028: import org.eclipse.ui.internal.intro.impl.model.util.ModelUtil;
029: import org.eclipse.ui.internal.intro.impl.util.IntroEvaluationContext;
030: import org.eclipse.ui.internal.intro.impl.util.Log;
031: import org.eclipse.ui.internal.intro.impl.util.StringUtil;
032: import org.osgi.framework.Bundle;
033: import org.w3c.dom.Document;
034: import org.w3c.dom.Element;
035: import org.w3c.dom.Node;
036: import org.w3c.dom.NodeList;
037:
038: /**
039: * Base class for all Intro pages, inlcuding HomePage. Adds page specific
040: * support:
041: * <ul>
042: * <li>support for page styles, and style inheritance</li>
043: * <li>support for XHTML via a DOM instance var. Resolving the page is also
044: * handled here.</li>
045: * <li>a pge has the concept of being an IFramePage. This is indicated by the
046: * isIFrame flag. A page is an IFramePage when it is not defined in any content
047: * file, but instead is actually created at runtime. This is done to display a
048: * Help System topic embedded in any given div. The current page is cloned, its
049: * id is mangled with "_embedDivId", the content of the div pointed to by
050: * embedDivId is replaced with an IFrame that loads the Help System topic.</li>
051: * </ul>
052: */
053: public abstract class AbstractIntroPage extends AbstractIntroContainer {
054:
055: protected static final String TAG_PAGE = "page"; //$NON-NLS-1$
056: private static final String ATT_STYLE = "style"; //$NON-NLS-1$
057: private static final String ATT_ALT_STYLE = "alt-style"; //$NON-NLS-1$
058: private static final String ATT_CONTENT = "content"; //$NON-NLS-1$
059: private static final String ATT_SHARED_STYLE = "shared-style"; //$NON-NLS-1$
060: private static final String INVALID_CONTENT = "invalidPage/invalidPage.xhtml"; //$NON-NLS-1$
061: private static final String INVALID_CONTENT_BASE = "invalidPage"; //$NON-NLS-1$
062:
063: private String style;
064: private String altStyle;
065: private String sharedStyle;
066: private IntroPageTitle title;
067: private String content;
068:
069: // if iframe is not null, this indicates that this page was cloned at
070: // runtime from another page whose id was "originalId".
071: private IntroInjectedIFrame iframe;
072:
073: // id of page from which this page was cloned.
074: private String originalId;
075:
076: // DOM representing XHTML content. DOM is only cached in the case of XHTML
077: // content.
078: private Document dom;
079:
080: private DocumentProcessor domProcessor;
081:
082: // set when the content file is loaded (ie: loadChildren is called)
083: private boolean isXHTMLPage;
084:
085: // Model base attribute is stored in parent class. This base attribute here
086: // is to cache the initial location of the content file. When content
087: // attribute is defined, the base in the model becomes relative to
088: // new xml file. However, in the case of XHTML content, and when
089: // presentation is UI forms, we need to reuse initial content file location.
090: private String initialBase;
091:
092: /**
093: * The vectors to hold all inhertied styles and alt styles from included
094: * elements. They are lazily created when children are resolved (ie:
095: * includes are resolved) OR when extensions are resolved and styles need to
096: * be added to the target page.
097: * <p>
098: * Style Rules:
099: * <ul>
100: * <li>For includes, merge-style controls wether or not the enclosing page
101: * inherits the styles of the target.
102: * <li>If a page is including a shared div, merging target styles into this
103: * page is ignored. Shared divs do not have styles.</li>
104: * <li>For extensions, if the style or alt-style is not defined, that means
105: * that no style inheritence is needed, and the style of the target page are
106: * not updated.
107: * <li>If an extension is extending a shared div, merging the styles of
108: * this extension into the target is ignored. Shared divs do not have
109: * styles.</li>
110: * <li>Shared hashtable has alt-styles as keys and bundles as values.</li>
111: * </ul>
112: */
113: private Vector styles;
114: private Hashtable altStyles;
115:
116: /**
117: * Parent class for all pages. Make sure to set the bundle to where the
118: * pages are loaded from. This means that the bundle for root may be
119: * different from the bundle for all the other pages. Only pages do this
120: * logic and so other model objects might have the wrong bundle cached if
121: * the resource was loaded from an nl directory.
122: *
123: * @param element
124: */
125: AbstractIntroPage(Element element, Bundle bundle, String base) {
126: super (element, bundle, base);
127: this .initialBase = base;
128: content = getAttribute(element, ATT_CONTENT);
129: if (content == null) {
130: //Delaying init until we have the model
131: // so that we can resolve theme
132: //init(element, bundle, base);
133: } else {
134: // Content is not null. Resolve it. Other page attributes (style,
135: // alt-style...) will be loaded when xml content file is loaded
136: // since we need to pick them up from external xml content file. In
137: // the case where this external content file is XHTML and we have
138: // HTML presentation, page attributes are simply not loaded. In the
139: // case where we have XHTML in a UI forms presentation, we will need
140: // to load initial page attributes.
141: // BASE: since content is being loaded from another xml file, point
142: // the base of this page to be relative to the new xml file
143: // location.
144: IPath subBase = ModelUtil.getParentFolderPath(content);
145: this .base = new Path(base).append(subBase).toString();
146: content = BundleUtil.getResolvedResourceLocation(base,
147: content, bundle);
148: }
149: // load shared-style attribure. This is needed in the XML and in the
150: // XHTML cases. Default is to include shared style.
151: this .sharedStyle = getAttribute(element, ATT_SHARED_STYLE);
152: if (sharedStyle == null)
153: sharedStyle = "true"; //$NON-NLS-1$
154:
155: }
156:
157: public void setParent(AbstractIntroElement parent) {
158: super .setParent(parent);
159: if (content == null)
160: init(element, getBundle(), initialBase);
161: }
162:
163: /**
164: * Returns unresolved content value as found in the source file.
165: * the source file.
166: * @return the unresolved content value
167: */
168:
169: public String getRawContent() {
170: return getAttribute(element, ATT_CONTENT);
171: }
172:
173: /**
174: * Initialize styles. Take first style in style attribute and make it the
175: * page style. Then put other styles in styles vectors. Make sure to resolve
176: * each style.
177: *
178: * @param element
179: * @param bundle
180: */
181: private void init(Element element, Bundle bundle, String base) {
182: String[] styleValues = getAttributeList(element, ATT_STYLE);
183: if (styleValues != null && styleValues.length > 0) {
184: style = styleValues[0];
185: style = BundleUtil.getResolvedResourceLocation(base, style,
186: bundle);
187: for (int i = 1; i < styleValues.length; i++) {
188: String style = styleValues[i];
189: style = BundleUtil.getResolvedResourceLocation(base,
190: style, bundle);
191: addStyle(style);
192: }
193: }
194:
195: String[] altStyleValues = getAttributeList(element,
196: ATT_ALT_STYLE);
197: if (altStyleValues != null && altStyleValues.length > 0) {
198: altStyle = altStyleValues[0];
199: altStyle = BundleUtil.getResolvedResourceLocation(base,
200: altStyle, bundle);
201: for (int i = 1; i < altStyleValues.length; i++) {
202: String style = altStyleValues[i];
203: style = BundleUtil.getResolvedResourceLocation(base,
204: style, bundle);
205: addAltStyle(style, bundle);
206: }
207: }
208: }
209:
210: /**
211: * The page's title. Each page can have one title.
212: *
213: * @return Returns the title of this page.
214: */
215: public String getTitle() {
216: // title is a child of the page, and so we have to load children first.
217: // We also have to resolve children because someone might be including a
218: // title. Update title instance after all includes and extensions have
219: // been resolved.
220: getChildren();
221: if (title == null) {
222: // there should only be one title child per page. safe to cast.
223: IntroPageTitle[] titles = (IntroPageTitle[]) getChildrenOfType(AbstractIntroElement.PAGE_TITLE);
224: if (titles.length > 0)
225: title = titles[0];
226: }
227:
228: if (title == null)
229: // still null. no title.
230: return null;
231: return title.getTitle();
232: }
233:
234: /**
235: * @return Returns the style.
236: */
237: public String getStyle() {
238: return style;
239: }
240:
241: /**
242: * @return Returns the alt_style.
243: */
244: public String getAltStyle() {
245: return altStyle;
246: }
247:
248: /**
249: * Gets all the inherited styles of this page. Styles can be inherited from
250: * includes or from configExtensions.
251: * <p>
252: * Note: this call needs to get all the children of this page, and so it
253: * will resolve this page. might be expensive.
254: *
255: * @return Returns all the inherited styles of this page. Returns an empty
256: * array if page is not expandable or does not have inherited
257: * styles.
258: */
259: public String[] getStyles() {
260: // call get children first to resolve includes and populate styles
261: // vector. Resolving children will initialize the style vectors.
262: getChildren();
263: if (styles == null)
264: // style vector is still null because page does not have styles.
265: return new String[0];
266: String[] stylesArray = new String[styles.size()];
267: styles.copyInto(stylesArray);
268: return stylesArray;
269: }
270:
271: /**
272: * Gets all the inherited alt-styles of this page (ie: styles for the SWT
273: * presentation). A hashtable is returned that has inhertied alt-styles as
274: * keys, and plugin descriptors as values. This is needed to be able to load
275: * resources from the inherited target plugin. Note: this call needs to get
276: * all the children of this page, and so its will resolve this page. might
277: * be expensive.
278: *
279: * @return Returns all the inherited styles of this page. Returns an empty
280: * hashtable if page is not expandable, does not have any includes,
281: * or has includes that do not merge styles.
282: */
283: public Hashtable getAltStyles() {
284: // call get children first to resolve includes and populate hashtable.
285: // Resolving children will initialize the style vectors.
286: getChildren();
287: return altStyles;
288: }
289:
290: /**
291: * Adds the given style to the list. Style is not added if it already exists
292: * in the list.
293: *
294: * @param style
295: */
296: protected void addStyle(String style) {
297: if (!initStyles(style))
298: return;
299: if (styles.contains(style))
300: return;
301: styles.add(style);
302: }
303:
304: public void insertStyle(String style, int location) {
305: if (!initStyles(style))
306: return;
307: if (styles.contains(style))
308: return;
309: styles.add(location, style);
310: }
311:
312: /**
313: * Adds the given style to the list.Style is not added if it already exists
314: * in the list.
315: *
316: * @param altStyle
317: */
318: protected void addAltStyle(String altStyle, Bundle bundle) {
319: if (!initAltStyles(altStyle))
320: return;
321: if (altStyles.containsKey(altStyle))
322: return;
323: altStyles.put(altStyle, bundle);
324: }
325:
326: /**
327: * Util method to add given styles to the list.
328: *
329: */
330: protected void addStyles(String[] styles) {
331: if (styles == null)
332: return;
333: for (int i = 0; i < styles.length; i++)
334: addStyle(styles[i]);
335: }
336:
337: /**
338: * Util method to add map of altstyles to list.
339: */
340: protected void addAltStyles(Hashtable altStyles) {
341: if (altStyles == null)
342: return;
343: if (this .altStyles == null)
344: // delay creation until needed.
345: this .altStyles = new Hashtable();
346: this .altStyles.putAll(altStyles);
347: }
348:
349: private boolean initStyles(String style) {
350: if (style == null)
351: return false;
352: if (this .styles == null)
353: // delay creation until needed.
354: this .styles = new Vector();
355: return true;
356: }
357:
358: private boolean initAltStyles(String style) {
359: if (style == null)
360: return false;
361: if (this .altStyles == null)
362: // delay creation until needed.
363: this .altStyles = new Hashtable();
364: return true;
365: }
366:
367: /*
368: * (non-Javadoc)
369: *
370: * @see org.eclipse.ui.internal.intro.impl.model.IntroElement#getType()
371: */
372: public int getType() {
373: return AbstractIntroElement.ABSTRACT_PAGE;
374: }
375:
376: /*
377: * Override parent behavior to lazily initialize styles vectors. This will
378: * only be called once, if resolved == false. In the case of DOM model,
379: * resolve this page's full DOM.
380: *
381: * @see org.eclipse.ui.internal.intro.impl.model.AbstractIntroContainer#resolveChildren()
382: */
383: protected void resolveChildren() {
384: // flag would be set
385: if (isXHTMLPage)
386: resolvePage();
387: else
388: super .resolveChildren();
389: }
390:
391: /**
392: * Override parent behavior to add support for HEAD & Title element in pages
393: * only, and not in divs.
394: *
395: * @see org.eclipse.ui.internal.intro.impl.model.AbstractIntroContainer#getModelChild(org.eclipse.core.runtime.IConfigurationElement)
396: */
397: protected AbstractIntroElement getModelChild(Element childElement,
398: Bundle bundle, String base) {
399: AbstractIntroElement child = null;
400: if (childElement.getNodeName().equalsIgnoreCase(
401: IntroHead.TAG_HEAD)) {
402: child = new IntroHead(childElement, bundle, base);
403: } else if (childElement.getNodeName().equalsIgnoreCase(
404: IntroPageTitle.TAG_TITLE)) {
405: // if we have a title, only add it as a child if we did not load one
406: // before. A page can only have one title.
407: if (title == null) {
408: child = new IntroPageTitle(childElement, bundle);
409: }
410: }
411: if (child != null)
412: return child;
413: return super .getModelChild(childElement, bundle, base);
414: }
415:
416: /**
417: * Returns all head contributions in this page. There can be more than one
418: * head contribution in the page;
419: *
420: * @return
421: */
422: public IntroHead[] getHTMLHeads() {
423: return (IntroHead[]) getChildrenOfType(AbstractIntroElement.HEAD);
424: }
425:
426: /**
427: * load the children of this container. Override parent behavior because we
428: * want to support loading content from other xml files. The design is that
429: * only the id and content from the existing page are honored. All other
430: * attributes are what they are defined in the external paget. For XHTML
431: * content, all info is in the xhtml page. If we fail to load the page,
432: * display the Invalid Page page.
433: */
434: protected void loadChildren() {
435: if (content == null) {
436: // no content. do regular loading.
437: super .loadChildren();
438: return;
439: }
440:
441: // content attribute is defined. It either points to an XHTML file, or
442: // an introContent.xml file. Process each case. Assume it is an
443: // introContent file.
444: // INTRO: XHTML file is loaded needlessly when we have XHTML content and
445: // SWT presentation.
446: IntroContentParser parser = new IntroContentParser(content);
447: Document dom = parser.getDocument();
448: if (dom == null) {
449: // bad xml. This could be bad XHTML or bad intro XML. Parser would
450: // have logged fact. Load dom for invalid page, and make sure to
451: // force an extract on parent folder to enabling jarring.
452: Bundle introBundle = Platform
453: .getBundle(IIntroConstants.PLUGIN_ID);
454: ModelUtil.ensureFileURLsExist(introBundle, INVALID_CONTENT);
455:
456: String invalidContentFilePath = BundleUtil
457: .getResolvedResourceLocation(INVALID_CONTENT,
458: introBundle);
459: parser = new IntroContentParser(invalidContentFilePath);
460: dom = parser.getDocument();
461: // make sure to override all attributes to resolve the Invalid
462: // Page page correctly.
463: content = invalidContentFilePath;
464: this .base = INVALID_CONTENT_BASE;
465: setBundle(introBundle);
466: }
467:
468: // parse content depending on type. Make sure to set the loaded flag
469: // accordingly, otherwise content file will always be parsed.
470: if (parser.hasXHTMLContent()) {
471: loadXHTMLContent(dom);
472: // make sure to use correct base.
473: init(element, getBundle(), initialBase);
474: super .loadChildren();
475: } else
476: // load the first page with correct id, from content xml file.
477: loadXMLContent(dom);
478: }
479:
480: /**
481: * Load the xml content from the introContent.xml file pointed to by the
482: * content attribute, and loaded into the passed DOM. Load the first page
483: * with correct id from this content file.
484: *
485: * @param dom
486: */
487: private void loadXMLContent(Document dom) {
488: Element[] pages = ModelUtil.getElementsByTagName(dom,
489: AbstractIntroPage.TAG_PAGE);
490: if (pages.length == 0) {
491: Log.warning("Content file has no pages."); //$NON-NLS-1$
492: return;
493: }
494: // point the element of this page to the new element. Pick first page
495: // with matching id. Make sure to disable loading of children of current
496: // element if a matching page in the external content file is not found.
497: boolean foundMatchingPage = false;
498: for (int i = 0; i < pages.length; i++) {
499: Element pageElement = pages[i];
500: if (pageElement.getAttribute(AbstractIntroIdElement.ATT_ID)
501: .equals(getId())) {
502: this .element = pageElement;
503: // call init on the new element. the filtering and the style-id
504: // are loaded by the parent class.
505: init(pageElement, getBundle(), base);
506: // INTRO: revisit. Special processing here should be made more
507: // general. we know id is correct.
508: style_id = getAttribute(element,
509: AbstractBaseIntroElement.ATT_STYLE_ID);
510: filteredFrom = getAttribute(element,
511: AbstractBaseIntroElement.ATT_FILTERED_FROM);
512: sharedStyle = getAttribute(element, ATT_SHARED_STYLE);
513: if (sharedStyle == null)
514: sharedStyle = "true"; //$NON-NLS-1$
515: foundMatchingPage = true;
516: }
517: }
518: if (foundMatchingPage)
519: // now do children loading as usual.
520: super .loadChildren();
521: else {
522: // page was not found in content file. Perform load actions, and log
523: // fact. init the children vector.
524: children = new Vector();
525: loaded = true;
526: // free DOM model for memory performance.
527: element = null;
528: Log
529: .warning("Content file does not have page with id= " + getId()); //$NON-NLS-1$
530: }
531: }
532:
533: private void loadXHTMLContent(Document dom) {
534: // no need to load any children since we use XSLT to print XHTML
535: // content. Simply cache DOM.
536: this .dom = dom;
537: this .isXHTMLPage = true;
538: // init empty children vector.
539: children = new Vector();
540: loaded = true;
541: }
542:
543: /**
544: * Returns the DOM representing an external XHTML file. May return null if
545: * extension content is 3.0 format. The page is resolved before returning,
546: * meaning includes are resolved, and the base of the page is defined.
547: *
548: * @return
549: */
550: public Document getResolvedDocument() {
551: // we need to force a getChildren to resolve the page.
552: getChildren();
553: return dom;
554: }
555:
556: /**
557: * Returns the DOM representing an external XHTML file. May return null if
558: * extension content is 3.0 format. The page is NOT resolved before
559: * returning. It is retruned as given by the dom parser.
560: *
561: * @return
562: */
563: public Document getDocument() {
564: // we only need to load children here.
565: if (!loaded)
566: loadChildren();
567: return dom;
568: }
569:
570: /**
571: * Returns whether or not we have an XHTML page as the content for this
572: * page. The XHTML page is defined through the content attribute. This
573: * method forces the content file to be parsed and loaded in memory.
574: *
575: * @return
576: */
577: public boolean isXHTMLPage() {
578: // we need to force loading of children since we need to determine
579: // content type. Load the children without resolving (for performance),
580: // this will set the XHTML flag at the page level.
581: if (!loaded)
582: loadChildren();
583: return isXHTMLPage;
584: }
585:
586: /**
587: * Deep searches all children in this container's DOM for the first child
588: * with the given id. The element retrieved must have the passed local name.
589: * Note that in an XHTML file (aka DOM) elements should have a unique id
590: * within the scope of a document. We use local name because this allows for
591: * finding intro anchors, includes and dynamic content element regardless of
592: * whether or not an xmlns was used in the xml. note: could not have used
593: * inheritance from parent container because return type for parent is intro
594: * legacy model.
595: *
596: */
597: public Element findDomChild(String id, String localElementName) {
598: if (!loaded)
599: loadChildren();
600: // using getElementById is tricky and we need to have intro XHTML
601: // modules to properly use this method.
602: return ModelUtil.getElementById(dom, id, localElementName);
603: }
604:
605: /**
606: * Search for any element with the given id.
607: *
608: * @param id
609: * @return
610: */
611: public Element findDomChild(String id) {
612: return findDomChild(id, "*"); //$NON-NLS-1$
613:
614: }
615:
616: /**
617: * Resolves the full page. It is called just before the page needs to be
618: * displayed.
619: * <li>adds a BASE child to the DOM HEAD element, if one is not defined.
620: * All intro documents must have a base defined to resolve all urls.</li>
621: * <li>resolves all includes in the page. This means importing target DOM.
622: * </li>
623: * <li>resolves all XHTML attributes for resources, eg: src, href
624: * attributes.</li>
625: */
626: protected void resolvePage() {
627: // insert base meta-tag,
628: ModelUtil.insertBase(dom, ModelUtil
629: .getParentFolderOSString(content));
630:
631: // resolve all relative resources relative to content file. Do it before
632: // inserting shared style to enable comparing fully qualified styles.
633: ModelUtil.updateResourceAttributes(dom.getDocumentElement(),
634: this );
635:
636: // now add shared style.
637: IntroModelRoot modelRoot = (IntroModelRoot) getParent();
638: IntroPartPresentation presentation = modelRoot
639: .getPresentation();
640: String[] styles = presentation != null ? presentation
641: .getImplementationStyles() : null;
642: if (styles != null && injectSharedStyle()) {
643: for (int i = 0; i < styles.length; i++)
644: ModelUtil.insertStyle(dom, styles[i]);
645: }
646:
647: // filter the content
648: if (domProcessor == null) {
649: domProcessor = new DocumentProcessor(
650: new ProcessorHandler[] { new FilterHandler(
651: IntroEvaluationContext.getContext()) });
652: }
653: UAElement element = UAElementFactory.newElement(dom
654: .getDocumentElement());
655: domProcessor.process(element, null);
656:
657: // and resolve includes.
658: resolveIncludes();
659:
660: // now remove all anchors from this page.
661: ModelUtil.removeAllElements(dom, IntroAnchor.TAG_ANCHOR);
662: resolved = true;
663: }
664:
665: /**
666: * Resolves all includes in this page. This means importing the DOM of the
667: * target path into the current page DOM, and resolving XHTML attributes for
668: * resources.
669: */
670: protected void resolveIncludes() {
671: // get all includes elements in DOM.
672: NodeList includes = dom.getElementsByTagNameNS("*", //$NON-NLS-1$
673: IntroInclude.TAG_INCLUDE);
674: // get the array version of the include nodelist to work around
675: // replaceChild() DOM api design.
676: Node[] nodes = ModelUtil.getArray(includes);
677: for (int i = 0; i < nodes.length; i++) {
678: Element includeElement = (Element) nodes[i];
679: IntroInclude include = new IntroInclude(includeElement,
680: getBundle());
681: // result[0] is target parent page, result[1] is target element.
682: Object[] results = findDOMIncludeTarget(include);
683: Element targetElement = (Element) results[1];
684: if (targetElement == null) {
685: String message = "Could not resolve following include: " //$NON-NLS-1$
686: + ModelLoaderUtil.getLogString(getBundle(),
687: includeElement, IntroInclude.ATT_PATH);
688: Log.warning(message);
689: return;
690: }
691:
692: // insert the target element instead of the include.
693: Node targetNode = dom.importNode(targetElement, true);
694: // update the src attribute of this node, if defined by w3
695: // specs.
696: AbstractIntroPage page = ((AbstractIntroPage) results[0]);
697: ModelUtil.updateResourceAttributes((Element) targetNode,
698: page);
699: // use of replace API to remove include element is tricky. It
700: // confuses the NodeList used in the loop above. Removing an include
701: // removes it from the NodeList. Used cloned Array instead.
702: includeElement.getParentNode().replaceChild(targetNode,
703: includeElement);
704: }
705: }
706:
707: /**
708: * Find the target Element pointed to by the path in the include. It is
709: * assumed that configId always points to an external config, and not the
710: * same config of the inlcude.
711: *
712: * @param include
713: * @param path
714: * @return
715: */
716: private Object[] findDOMIncludeTarget(IntroInclude include) {
717: String path = include.getPath();
718: IntroModelRoot targetModelRoot = (IntroModelRoot) getParentPage()
719: .getParent();
720: String targetConfigID = include.getConfigId();
721: if (targetConfigID != null)
722: targetModelRoot = ExtensionPointManager.getInst().getModel(
723: targetConfigID);
724: if (targetModelRoot == null)
725: // if the target config was not found, skip this include.
726: return null;
727: return findDOMTarget(targetModelRoot, path);
728:
729: }
730:
731: /**
732: * Finds the child element that corresponds to the given path in the passed
733: * model.
734: *
735: * @param model
736: * model containing target path.
737: * @param path
738: * the path to look for
739: * @param results
740: * two object array that will return the target intro page as the
741: * first result, and the actual target DOM Element as the second
742: * result. It is gauranteed to not be null. Content may be null.
743: * @return target DOM element
744: */
745: public Object[] findDOMTarget(IntroModelRoot model, String path) {
746: Object[] results = new Object[2];
747: // path must be pageId/anchorID in the case of of XHTML pages.
748: // pages.
749: String[] pathSegments = StringUtil.split(path, "/"); //$NON-NLS-1$
750: if (pathSegments.length != 2)
751: // path does not have correct format. Return empty results.
752: return results;
753:
754: // save to cast.
755: AbstractIntroPage targetPage = (AbstractIntroPage) model
756: .findChild(pathSegments[0], ABSTRACT_PAGE);
757:
758: if (targetPage != null) {
759: results[0] = targetPage;
760: Element targetElement = targetPage
761: .findDomChild(pathSegments[1]);
762: if (targetElement != null)
763: results[1] = targetElement;
764: }
765: return results;
766: }
767:
768: /**
769: * @return Returns the content.
770: */
771: public String getContent() {
772: return content;
773: }
774:
775: /**
776: * Deep copy since class has mutable objects.
777: */
778: public Object clone() throws CloneNotSupportedException {
779: AbstractIntroPage clone = (AbstractIntroPage) super .clone();
780: if (title != null) {
781: IntroPageTitle clonedTitle = (IntroPageTitle) title.clone();
782: clonedTitle.setParent(clone);
783: clone.title = clonedTitle;
784: }
785: // styles are safe for a shallow copy.
786: if (styles != null)
787: clone.styles = (Vector) styles.clone();
788: if (altStyles != null)
789: clone.altStyles = (Hashtable) altStyles.clone();
790: return clone;
791: }
792:
793: /**
794: * Used when cloning pages to assign a unique id. Cache original id before
795: * setting.
796: *
797: * @param id
798: */
799: public void setId(String id) {
800: this .originalId = this .id;
801: this .id = id;
802: }
803:
804: /*
805: * Creates an IFrame and injects it as the only child of the specified path.
806: */
807: public boolean injectIFrame(String url, String embedTarget) {
808: // embed url as IFrame in target div. We need to find target div in
809: // this cloned page not in the original page.
810: IntroGroup divToReplace = (IntroGroup) findTarget(embedTarget);
811: if (divToReplace == null) {
812: // we failed to find embed div, log and exit.
813: Log.warning("Failed to find embedTarget: " + embedTarget //$NON-NLS-1$
814: + " in page " + getId()); //$NON-NLS-1$
815: return false;
816: }
817:
818: this .iframe = new IntroInjectedIFrame(getElement(), getBundle());
819: this .iframe.setParent(divToReplace);
820: this .iframe.setIFrameURL(url);
821: divToReplace.clearChildren();
822: divToReplace.addChild(iframe);
823: return true;
824: }
825:
826: /**
827: * Return true if this page is a cloned page that has an IFrame.
828: *
829: * @return
830: */
831: public boolean isIFramePage() {
832: return (iframe != null) ? true : false;
833: }
834:
835: public String getUnmangledId() {
836: if (isIFramePage())
837: return originalId;
838: return id;
839: }
840:
841: /**
842: * Set the url of the embedded IFrame, if this page is an IFrame page.
843: *
844: * @param url
845: */
846: public void setIFrameURL(String url) {
847: if (!isIFramePage())
848: return;
849: this .iframe.setIFrameURL(url);
850: }
851:
852: /**
853: * Return the url of the embedded IFrame, if this page is an IFrame page.
854: *
855: * @param url
856: */
857: public String getIFrameURL() {
858: if (!isIFramePage())
859: return null;
860: return this .iframe.getIFrameURL();
861: }
862:
863: /**
864: * Returns the raw or unprocessed base location.
865: */
866: public String getInitialBase() {
867: return initialBase;
868: }
869:
870: /**
871: * Return the url of the embedded IFrame, if this page is an IFrame page.
872: *
873: * @param url
874: */
875: public boolean injectSharedStyle() {
876: return this .sharedStyle.equals("true") ? true : false; //$NON-NLS-1$
877: }
878:
879: }
|