| /******************************************************************************* |
| * Copyright (c) 2004, 2009 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ui.internal.intro.impl.html; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.lang.reflect.Field; |
| import java.net.URL; |
| import java.util.Map; |
| |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.help.internal.util.ProductPreferences; |
| import org.eclipse.swt.browser.Browser; |
| import org.eclipse.ui.internal.intro.impl.FontSelection; |
| import org.eclipse.ui.internal.intro.impl.IIntroConstants; |
| import org.eclipse.ui.internal.intro.impl.IntroPlugin; |
| import org.eclipse.ui.internal.intro.impl.model.AbstractBaseIntroElement; |
| import org.eclipse.ui.internal.intro.impl.model.AbstractIntroElement; |
| import org.eclipse.ui.internal.intro.impl.model.AbstractIntroPage; |
| import org.eclipse.ui.internal.intro.impl.model.IntroContentProvider; |
| import org.eclipse.ui.internal.intro.impl.model.IntroGroup; |
| import org.eclipse.ui.internal.intro.impl.model.IntroHTML; |
| import org.eclipse.ui.internal.intro.impl.model.IntroHead; |
| import org.eclipse.ui.internal.intro.impl.model.IntroImage; |
| import org.eclipse.ui.internal.intro.impl.model.IntroInjectedIFrame; |
| import org.eclipse.ui.internal.intro.impl.model.IntroLink; |
| import org.eclipse.ui.internal.intro.impl.model.IntroPageTitle; |
| import org.eclipse.ui.internal.intro.impl.model.IntroSeparator; |
| import org.eclipse.ui.internal.intro.impl.model.IntroText; |
| import org.eclipse.ui.internal.intro.impl.model.IntroTheme; |
| import org.eclipse.ui.internal.intro.impl.model.loader.ContentProviderManager; |
| import org.eclipse.ui.internal.intro.impl.model.util.BundleUtil; |
| import org.eclipse.ui.internal.intro.impl.presentations.BrowserIntroPartImplementation; |
| import org.eclipse.ui.internal.intro.impl.util.Log; |
| import org.eclipse.ui.intro.config.IIntroContentProvider; |
| import org.eclipse.ui.intro.config.IIntroContentProviderSite; |
| |
| public class IntroHTMLGenerator { |
| |
| private AbstractIntroPage introPage; |
| |
| private IIntroContentProviderSite providerSite; |
| private boolean backgroundSizeWorks; |
| |
| /** |
| * Generates the HTML code that will be presented in the browser widget for the provided intro |
| * page. |
| * |
| * @param page |
| * the page to generate HTML for |
| * @param presentation |
| * the presentation associated with this page. |
| */ |
| public HTMLElement generateHTMLforPage(AbstractIntroPage page, IIntroContentProviderSite providerSite) { |
| if (page == null) |
| return null; |
| this.introPage = page; |
| this.providerSite = providerSite; |
| |
| initializeBackgroundSizeWorks(); |
| |
| // generate and add the appropriate encoding to the top of the document |
| // generateEncoding(); |
| // create the main HTML element, and all of its contents. |
| return generateHTMLElement(); |
| } |
| |
| private void initializeBackgroundSizeWorks() { |
| // Internet Explorer <= 9 doesn't properly handle background-size |
| backgroundSizeWorks = true; |
| try { |
| if (isIE()) { |
| Class<?> ieClass = Class.forName("org.eclipse.swt.browser.IE"); //$NON-NLS-1$ |
| Field field = ieClass.getDeclaredField("IEVersion"); //$NON-NLS-1$ |
| field.setAccessible(true); |
| int value = field.getInt(ieClass); |
| // We specifically care about background-size which works in 9+ |
| backgroundSizeWorks = value <= 0 || value >= 9; |
| } |
| } catch(Exception e) { |
| // IE not found |
| } |
| } |
| |
| private boolean isIE() { |
| return getBrowser() != null && "ie".equals(getBrowser().getBrowserType()); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Return the SWT Browser instance being used to render the intro. |
| * |
| * @return the browser or {@code null} if the browser could not be determined |
| */ |
| private Browser getBrowser() { |
| if (providerSite instanceof BrowserIntroPartImplementation) { |
| return ((BrowserIntroPartImplementation) providerSite).getBrowser(); |
| } |
| return null; |
| } |
| |
| /* |
| * private HTMLElement generateEncoding() { HTMLElement encoding = new HTMLElement(""); |
| * //$NON-NLS-1$ // TODO: figure out how to handle locale based encoding // As far as the HTML |
| * generator is concerned, this is probably as // simple as asking the model for the information |
| * return encoding; } |
| */ |
| |
| /** |
| * Generates the HTML element and its content: |
| * |
| * <pre> |
| * |
| * <HTML> |
| * <HEAD> |
| * head content |
| * </HEAD> |
| * <BODY> |
| * body content |
| * </BODY> |
| * </HTML> |
| * |
| * </pre> |
| * |
| * @return the html HTMLElement |
| */ |
| private HTMLElement generateHTMLElement() { |
| // this is the outermost element, so it has no indent |
| int indentLevel = 0; |
| HTMLElement html = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_HTML, indentLevel, true); |
| HTMLElement head = generateHeadElement(indentLevel + 1); |
| HTMLElement body = generateBodyElement(indentLevel + 1, head); |
| html.addContent(head); |
| html.addContent(body); |
| return html; |
| } |
| |
| /** |
| * Generates the HEAD element and its content: |
| * |
| * <pre> |
| * |
| * |
| * <HEAD> |
| * <BASE href="base_plugin_location> |
| * <style type="text/css">HTML, IMG { border: 0px; } </style> |
| * <TITLE>page title </TITLE> |
| * <LINK href="style sheet"> |
| * additional head content, if specified |
| * </HEAD> |
| * |
| * </pre> |
| * |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return the head HTMLElement |
| */ |
| private HTMLElement generateHeadElement(int indentLevel) { |
| HTMLElement head = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_HEAD, indentLevel, true); |
| addBrowserRenderingDirectives(head, indentLevel + 1); |
| // add the title |
| head.addContent(generateTitleElement(introPage.getTitle(), indentLevel + 1)); |
| head.addContent(generateUTF8CharsetElement(indentLevel + 1)); |
| // create the BASE element |
| String basePath = BundleUtil.getResolvedResourceLocation(introPage.getBase(), introPage.getBundle()); |
| HTMLElement base = generateBaseElement(indentLevel + 1, basePath); |
| if (base != null) |
| head.addContent(base); |
| // create the HTML style block |
| head.addContent(generateStyleElement(indentLevel + 1)); |
| // add the presentation style |
| String[] presentationStyles = IntroPlugin.getDefault().getIntroModelRoot().getPresentation() |
| .getImplementationStyles(); |
| if (presentationStyles != null && introPage.injectSharedStyle()) { |
| for (int i=0; i<presentationStyles.length; i++) |
| head.addContent(generateLinkElement(presentationStyles[i], indentLevel + 1)); |
| } |
| String pageStyle = introPage.getStyle(); |
| if (pageStyle != null) |
| head.addContent(generateLinkElement(pageStyle, indentLevel + 1)); |
| // add javascript |
| head.addContent(generateJavascriptElement(indentLevel + 1)); |
| |
| // add the page's inherited style(s) |
| String[] pageStyles = introPage.getStyles(); |
| for (int i = 0; i < pageStyles.length; i++) { |
| pageStyle = pageStyles[i]; |
| if (pageStyle != null) |
| head.addContent(generateLinkElement(pageStyle, indentLevel + 1)); |
| } |
| // if there is additional head conent specified in an external file, |
| // include it. Additional head content can be specified at the |
| // implementation level (which would apply to ALL pages) and at the |
| // page level (which would apply only to that particular page). |
| // For the implementation's head contribution: |
| StringBuffer content = null; |
| IntroHead introHead = IntroPlugin.getDefault().getIntroModelRoot().getPresentation().getHead(); |
| if (introHead != null) { |
| content = readFromFile(introHead.getSrc(), introHead.getInlineEncoding()); |
| if (content != null) |
| head.addContent(content); |
| } |
| // For the page's head contribution: |
| // TODO: there should only be one of these at the page level, not a |
| // collection.. |
| IntroHead[] htmlHeads = introPage.getHTMLHeads(); |
| for (int i = 0; i < htmlHeads.length; i++) { |
| introHead = htmlHeads[i]; |
| if (introHead != null) { |
| content = readFromFile(introHead.getSrc(), introHead.getInlineEncoding()); |
| if (content != null) |
| head.addContent(content); |
| } |
| } |
| return head; |
| } |
| |
| /** |
| * Add any browser-specific rendering quirks |
| * |
| * @param indentLevel |
| */ |
| private void addBrowserRenderingDirectives(HTMLElement head, int indentLevel) { |
| /* IE renders intranet content in Compat View by default */ |
| if (isIE() && "html5".equals(getThemeProperty("standardSupport"))) { //$NON-NLS-1$ //$NON-NLS-2$ |
| // "Edge mode tells Internet Explorer to display content in the highest mode available" |
| HTMLElement meta = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_META, indentLevel, true); |
| meta.addAttribute(IIntroHTMLConstants.ATTRIBUTE_HTTP_EQUIV, "X-UA-Compatible"); //$NON-NLS-1$ |
| meta.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CONTENT, "IE=edge"); //$NON-NLS-1$ |
| head.addContent(meta); |
| } |
| } |
| |
| private String getThemeProperty(String key) { |
| IntroTheme theme = introPage.getModelRoot().getTheme(); |
| if (theme == null) { |
| return null; |
| } |
| Map<String, String> properties = theme.getProperties(); |
| if (properties == null) { |
| return null; |
| } |
| return properties.get(key); |
| } |
| |
| private HTMLElement generateUTF8CharsetElement(int indentLevel) { |
| HTMLElement meta = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_META, indentLevel, false); |
| meta.addAttribute(IIntroHTMLConstants.ATTRIBUTE_HTTP_EQUIV, IIntroHTMLConstants.CONTENT_TYPE); |
| meta.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CONTENT, IIntroHTMLConstants.TYPE_HTML_UTF_8); |
| return meta; |
| } |
| |
| private HTMLElement generateJavascriptElement(int indentLevel) { |
| String rel = "javascript/common.js"; //$NON-NLS-1$ |
| String abs = BundleUtil.getResolvedResourceLocation(rel, IntroPlugin.getDefault().getBundle()); |
| HTMLElement jselement = new FormattedHTMLElement("script", indentLevel, false); //$NON-NLS-1$ |
| jselement.addAttribute("type", "text/javascript"); //$NON-NLS-1$ //$NON-NLS-2$ |
| jselement.addAttribute("src", abs); //$NON-NLS-1$ |
| return jselement; |
| } |
| |
| /** |
| * Generates the BODY element and its content: |
| * |
| * <pre> |
| * |
| * |
| * <BODY> |
| * <DIV id="pageId" class="pageClass"> |
| * page content |
| * </DIV> |
| * </BODY> |
| * |
| * </pre> |
| * |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return the body HTMLElement |
| */ |
| private HTMLElement generateBodyElement(int indentLevel, HTMLElement head) { |
| HTMLElement body = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_BODY, indentLevel, true); |
| // Create the div that contains the page content |
| String pageId = (introPage.getId() != null) ? introPage.getId() : IIntroHTMLConstants.DIV_ID_PAGE; |
| HTMLElement pageContentDiv = generateDivElement(pageId, indentLevel + 1); |
| if (introPage.getStyleId() != null) |
| pageContentDiv.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, introPage.getStyleId()); |
| if (introPage.getBackgroundImage() != null) |
| pageContentDiv.addAttribute(IIntroHTMLConstants.ATTRIBUTE_STYLE, |
| "background-image : url(" + introPage.getBackgroundImage() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| // Add any children of the page, in the order they are defined |
| AbstractIntroElement[] children = introPage.getChildren(); |
| for (int i = 0; i < children.length; i++) { |
| AbstractIntroElement child = children[i]; |
| // use indentLevel + 2 here, since this element is contained within |
| // the pageContentDiv |
| HTMLElement childElement = generateIntroElement(child, indentLevel + 2); |
| if (childElement != null) { |
| addMixinStyle(childElement, child.getMixinStyle()); |
| pageContentDiv.addContent(childElement); |
| } |
| } |
| body.addContent(pageContentDiv); |
| return body; |
| } |
| |
| /** |
| * Given an IntroElement, generate the appropriate HTMLElement |
| * |
| * @param element |
| * the IntroElement |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return an HTMLElement |
| */ |
| private HTMLElement generateIntroElement(AbstractIntroElement element, int indentLevel) { |
| if (element == null) |
| return null; |
| // check to see if this element should be filtered from the HTML |
| // presentation |
| if (filteredFromPresentation(element)) |
| return null; |
| switch (element.getType()) { |
| case AbstractIntroElement.GROUP: |
| return generateIntroDiv((IntroGroup) element, indentLevel); |
| case AbstractIntroElement.LINK: |
| return generateIntroLink((IntroLink) element, indentLevel); |
| case AbstractIntroElement.HTML: |
| return generateIntroHTML((IntroHTML) element, indentLevel); |
| case AbstractIntroElement.CONTENT_PROVIDER: |
| return generateIntroContent((IntroContentProvider) element, indentLevel); |
| case AbstractIntroElement.IMAGE: |
| return generateIntroImage((IntroImage) element, indentLevel); |
| case AbstractIntroElement.HR: |
| return generateIntroSeparator((IntroSeparator) element, indentLevel); |
| case AbstractIntroElement.TEXT: |
| return generateIntroText((IntroText) element, indentLevel); |
| case AbstractIntroElement.PAGE_TITLE: |
| return generateIntroTitle((IntroPageTitle) element, indentLevel); |
| case AbstractIntroElement.INJECTED_IFRAME: |
| return generateIntroInjectedIFrame((IntroInjectedIFrame) element, indentLevel); |
| default: |
| return null; |
| } |
| } |
| |
| /** |
| * Create a div element and its content from an IntroDiv: |
| * |
| * <pre> |
| * |
| * |
| * <div id="attrvalue"> |
| * <h4><span class="div-label">attrvalue</span><h4> |
| * any defined divs, links, html, images, text, includes |
| * </div> |
| * |
| * </pre> |
| * |
| * @param element |
| * the IntroDiv |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return a div HTMLElement |
| */ |
| private HTMLElement generateIntroDiv(IntroGroup element, int indentLevel) { |
| // Create the outer div element |
| HTMLElement divElement = generateDivElement(element.getId(), indentLevel); |
| HTMLElement childContainer = divElement; |
| // if a div class was specified, add it |
| if (element.getStyleId() != null) |
| divElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, element.getStyleId()); |
| // Create the div label, if specified |
| if (element.getLabel() != null) { |
| if (element.isExpandable()) { |
| HTMLElement divLabel = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_SPAN, |
| indentLevel + 2, false); |
| divLabel.addContent(element.getLabel()); |
| divLabel.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, |
| "section-title");//$NON-NLS-1$ |
| String clientId = element.getId() + "-content"; //$NON-NLS-1$ |
| String toggleClosedId = element.getId() + "-toggle-closed"; //$NON-NLS-1$ |
| String toggleOpenId = element.getId() + "-toggle-open"; //$NON-NLS-1$ |
| String href = "#"; //$NON-NLS-1$ |
| HTMLElement link = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_ANCHOR, |
| indentLevel + 1, true); |
| link.addAttribute(IIntroHTMLConstants.ATTRIBUTE_HREF, href); |
| link.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, "section-title-link"); //$NON-NLS-1$ |
| StringBuffer call = new StringBuffer(); |
| call.append("return (toggleSection('");//$NON-NLS-1$ |
| call.append(clientId); |
| call.append("','");//$NON-NLS-1$ |
| call.append(toggleClosedId); |
| call.append("','");//$NON-NLS-1$ |
| call.append(toggleOpenId); |
| call.append("'))"); //$NON-NLS-1$ |
| link.addAttribute("onClick", call.toString()); //$NON-NLS-1$ |
| link.addContent(divLabel); |
| divElement.addContent(link); |
| // Add toggle images |
| HTMLElement toggleImageClosed = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_IMG, |
| indentLevel + 2, false, false); |
| toggleImageClosed.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, toggleClosedId); |
| toggleImageClosed.addAttribute(IIntroHTMLConstants.ATTRIBUTE_SRC, BundleUtil |
| .getResolvedResourceLocation(IIntroHTMLConstants.IMAGE_SRC_BLANK, |
| IIntroConstants.PLUGIN_ID)); |
| toggleImageClosed.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, "section-toggle-image-closed"); //$NON-NLS-1$ |
| if (element.isExpanded()) |
| toggleImageClosed.addAttribute(IIntroHTMLConstants.ATTRIBUTE_STYLE, "display: none"); //$NON-NLS-1$ |
| link.addContent(toggleImageClosed); |
| HTMLElement toggleImageOpen = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_IMG, |
| indentLevel + 2, false, false); |
| toggleImageOpen.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, toggleOpenId); |
| toggleImageOpen.addAttribute(IIntroHTMLConstants.ATTRIBUTE_SRC, BundleUtil |
| .getResolvedResourceLocation(IIntroHTMLConstants.IMAGE_SRC_BLANK, |
| IIntroConstants.PLUGIN_ID)); |
| toggleImageOpen.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, "section-toggle-image-open"); //$NON-NLS-1$ |
| if (element.isExpanded()) |
| toggleImageOpen.addAttribute(IIntroHTMLConstants.ATTRIBUTE_STYLE, "display: inline"); //$NON-NLS-1$ |
| link.addContent(toggleImageOpen); |
| childContainer = generateDivElement(clientId, indentLevel + 1); |
| childContainer.addAttribute("class", "section-body"); //$NON-NLS-1$//$NON-NLS-2$ |
| if (element.isExpanded()) |
| childContainer.addAttribute(IIntroHTMLConstants.ATTRIBUTE_STYLE, "display: block"); //$NON-NLS-1$ |
| divElement.addContent(childContainer); |
| } else { |
| HTMLElement divLabel = generateTextElement(IIntroHTMLConstants.ELEMENT_H4, null, |
| IIntroHTMLConstants.SPAN_CLASS_DIV_LABEL, element.getLabel(), indentLevel + 1); |
| divElement.addContent(divLabel); |
| } |
| } |
| if (element.getBackgroundImage() != null) { |
| String imageUrl = element.getBackgroundImage(); |
| imageUrl = BundleUtil.getResolvedResourceLocation(element.getBase(), imageUrl, element |
| .getBundle()); |
| String style; |
| if (Platform.getWS().equals(Platform.WS_WIN32) && !backgroundSizeWorks && imageUrl.toLowerCase().endsWith(".png")) { //$NON-NLS-1$ |
| // IE 5.5+ does not handle alphas in PNGs without |
| // this hack. Remove when IE7 becomes widespread |
| style = "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + imageUrl + "', sizingMethod='crop');"; //$NON-NLS-1$ //$NON-NLS-2$ |
| } else { |
| style = "background-image : url(" + imageUrl + ")"; //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| divElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_STYLE, style); |
| } |
| // Add any children of the div, in the order they are defined |
| AbstractIntroElement[] children = element.getChildren(); |
| for (int i = 0; i < children.length; i++) { |
| AbstractIntroElement child = children[i]; |
| HTMLElement childElement = generateIntroElement(child, indentLevel + 1); |
| if (childElement != null) { |
| addMixinStyle(childElement, child.getMixinStyle()); |
| childContainer.addContent(childElement); |
| } |
| } |
| return divElement; |
| } |
| |
| private void addMixinStyle(HTMLElement element, String mixinStyle) { |
| if (mixinStyle == null) |
| return; |
| String key = "class"; //$NON-NLS-1$ |
| String original = (String) element.getElementAttributes().get(key); |
| if (original == null) |
| original = mixinStyle; |
| else |
| original += " " + mixinStyle; //$NON-NLS-1$ |
| element.addAttribute(key, original); |
| } |
| |
| /** |
| * Generates an anchor (link) element and its content from an IntroLink: |
| * |
| * <pre> |
| * |
| * <A id=linkId class="link" href=linkHref> |
| * <IMG src="blank.gif"> |
| * <SPAN class="link-label">linkLabel </SPAN> |
| * <P><SPAN>text</SPAN></P> |
| * </A> |
| * |
| * </pre> |
| * |
| * @param element |
| * the IntroLink |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return an anchor (<A>) HTMLElement |
| */ |
| private HTMLElement generateIntroLink(IntroLink element, int indentLevel) { |
| String styleId = element.getStyleId(); |
| boolean useTable = ProductPreferences.isRTL() && "content-link".equals(styleId); //$NON-NLS-1$ |
| HTMLElement anchor1 = generateAnchorElement(element, indentLevel); |
| HTMLElement anchor2 = null; |
| HTMLElement labelAnchor = anchor1; |
| int indentBase = indentLevel; |
| if (useTable) { |
| indentBase = indentLevel + 1; |
| anchor2 = generateAnchorElement(element, indentLevel + 1); |
| labelAnchor = anchor2; |
| } |
| // add <IMG src="blank.gif"> |
| String blankImageURL = BundleUtil.getResolvedResourceLocation(IIntroHTMLConstants.IMAGE_SRC_BLANK, |
| IIntroConstants.PLUGIN_ID); |
| if (blankImageURL != null) { |
| anchor1.addContent(generateImageElement(blankImageURL, null, null, IIntroHTMLConstants.IMAGE_CLASS_BG, |
| indentBase + 1)); |
| } |
| // add link image, if one is specified |
| if (element.getImg() != null) { |
| HTMLElement img = generateIntroElement(element.getImg(), indentBase + 1); |
| if (img != null) |
| anchor1.addContent(img); |
| } |
| if (!useTable) { |
| HTMLElement imageDiv = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_DIV, indentBase+1, false); |
| imageDiv.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, |
| IIntroHTMLConstants.LINK_EXTRA_DIV); |
| anchor1.addContent(imageDiv); |
| } |
| // add <SPAN class="link-label">linkLabel</SPAN> |
| if (element.getLabel() != null) { |
| HTMLElement label = generateSpanElement(IIntroHTMLConstants.SPAN_CLASS_LINK_LABEL, |
| indentBase + 2); |
| label.addContent(element.getLabel()); |
| labelAnchor.addContent(label); |
| } |
| IntroText linkText = element.getIntroText(); |
| if (linkText != null && linkText.getText() != null) { |
| HTMLElement text = generateIntroElement(linkText, indentBase + 3); |
| if (text != null) |
| labelAnchor.addContent(text); |
| } |
| if (!useTable) { |
| return anchor1; |
| } |
| HTMLElement table = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_TABLE, indentLevel, false); |
| HTMLElement tr = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_TR, indentLevel + 1, false); |
| table.addContent(tr); |
| HTMLElement td1 = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_TD, indentLevel + 1, false); |
| HTMLElement td2 = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_TD, indentLevel + 1, false); |
| tr.addContent(td1); |
| tr.addContent(td2); |
| td1.addContent(anchor1); |
| td2.addContent(anchor2); |
| return table; |
| } |
| |
| /** |
| * Generate the appropriate HTML from an IntroHTML. If the IntroHTML type is "inline", then the |
| * content from the referenced file is emitted as-is into a div element. If the type is "embed", |
| * an OBJECT html element is created whose <code>data</code> attribute is equal to the |
| * IntroHTML's <code>src</code> value |
| * |
| * @param element |
| * the IntroHTML |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return an HTMLElement |
| */ |
| private HTMLElement generateIntroHTML(IntroHTML element, int indentLevel) { |
| if (element.isInlined()) |
| return generateInlineIntroHTML(element, indentLevel); |
| |
| return generateEmbeddedIntroHTML(element, indentLevel); |
| } |
| |
| /** |
| * Generate an image element from an IntroImage: |
| * |
| * <pre> |
| * |
| * <IMG src=imageSrc id=imageId> |
| * |
| * </pre> |
| * |
| * @param element |
| * the IntroImage |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return an img HTMLElement |
| */ |
| private HTMLElement generateIntroImage(IntroImage element, int indentLevel) { |
| HTMLElement imageElement = generateImageElement(element.getSrc(), element.getAlt(),element.getTitle(), element |
| .getStyleId(), indentLevel); |
| if (element.getId() != null) |
| imageElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, element.getId()); |
| return imageElement; |
| } |
| |
| private HTMLElement generateIntroSeparator(IntroSeparator element, int indentLevel) { |
| HTMLElement hrElement = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_HR, indentLevel, false); |
| if (element.getId() != null) |
| hrElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, element.getId()); |
| if (element.getStyleId() != null) |
| hrElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_STYLE, element.getStyleId()); |
| return hrElement; |
| } |
| |
| /** |
| * Generate a paragraph (<P>) element from an IntroText. The paragraph element will |
| * contain a span element that will contain the actual text. Providing the span element provides |
| * additional flexibility for CSS designers. |
| * |
| * <pre> |
| * |
| * |
| * <P><SPAN>spanContent</SPAN></P> |
| * |
| * </pre> |
| * |
| * @param element |
| * the IntroText |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return a paragraph HTMLElement |
| */ |
| private HTMLElement generateIntroText(IntroText element, int indentLevel) { |
| String spanClass = (element.getStyleId() != null) ? element.getStyleId() |
| : IIntroHTMLConstants.SPAN_CLASS_TEXT; |
| HTMLElement textElement = generateTextElement(IIntroHTMLConstants.ELEMENT_PARAGRAPH, element.getId(), |
| spanClass, element.getText(), indentLevel); |
| return textElement; |
| } |
| |
| /** |
| * @param element |
| * @param indentLevel |
| * @return |
| */ |
| private HTMLElement generateIntroInjectedIFrame(IntroInjectedIFrame element, int indentLevel) { |
| HTMLElement iframe = generateIFrameElement(element.getIFrameURL(), "0", //$NON-NLS-1$ |
| "auto", indentLevel); //$NON-NLS-1$ |
| return iframe; |
| } |
| |
| /** |
| * @param element |
| * @param indentLevel |
| * @return |
| */ |
| private HTMLElement generateIntroTitle(IntroPageTitle element, int indentLevel) { |
| HTMLElement titleElement = generateHeaderDiv(element.getId(), element.getStyleId(), |
| IIntroHTMLConstants.ELEMENT_H1, element.getTitle(), indentLevel); |
| return titleElement; |
| } |
| |
| /** |
| * Generate "inline" content from an IntroHTML. The content from the file referenced by the |
| * IntroHTML's <code>src</code> attribute is emitted as-is into a div element: |
| * |
| * <pre> |
| * |
| * |
| * <div id="attrvalue" class="attrvalue2"> |
| * content from file specified in src attribute |
| * </div> |
| * |
| * </pre> |
| * |
| * @param element |
| * the IntroHTML |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return a div HTMLElement, or null if there was a problem reading from the file |
| */ |
| private HTMLElement generateInlineIntroHTML(IntroHTML element, int indentLevel) { |
| // make sure to ask model for encoding. If encoding is null (ie: not |
| // specified in |
| // markup, local encoding is used. |
| StringBuffer content = readFromFile(element.getSrc(), element.getInlineEncoding()); |
| if (content != null && content.length() > 0) { |
| // Create the outer div element |
| String divClass = (element.getStyleId() != null) ? element.getStyleId() |
| : IIntroHTMLConstants.DIV_CLASS_INLINE_HTML; |
| HTMLElement divElement = generateDivElement(element.getId(), divClass, indentLevel); |
| // add the content of the specified file into the div element |
| divElement.addContent(content); |
| return divElement; |
| } |
| return null; |
| } |
| |
| /** |
| * Includes HTML content that is created by an IIntroContentProvider implementation. |
| * |
| * @param element |
| * @param indentLevel |
| * @return |
| */ |
| private HTMLElement generateIntroContent(IntroContentProvider element, int indentLevel) { |
| // create a new div to wrap the content |
| HTMLElement divElement = generateDivElement(element.getId(), |
| IIntroHTMLConstants.DIV_CLASS_PROVIDED_CONTENT, indentLevel); |
| |
| // If we've already loaded the content provider for this element, |
| // retrieve it, otherwise load the class |
| IIntroContentProvider providerClass = ContentProviderManager.getInst().getContentProvider(element); |
| if (providerClass == null) |
| // content provider never created before, create it. |
| providerClass = ContentProviderManager.getInst().createContentProvider(element, providerSite); |
| |
| if (providerClass != null) { |
| StringWriter stringWriter = new StringWriter(); |
| PrintWriter pw = new PrintWriter(stringWriter); |
| // create the specialized content |
| providerClass.createContent(element.getId(), pw); |
| // add the content of the specified file into the div element |
| stringWriter.flush(); |
| divElement.addContent(stringWriter.toString()); |
| pw.close(); |
| } else { |
| // we couldn't load the content provider, so add any alternate |
| // text content if there is any |
| IntroText htmlText = element.getIntroText(); |
| if (htmlText != null && htmlText.getText() != null) { |
| String textClass = (htmlText.getStyleId() != null) ? htmlText.getStyleId() |
| : IIntroHTMLConstants.SPAN_CLASS_TEXT; |
| HTMLElement text = generateTextElement(IIntroHTMLConstants.ELEMENT_PARAGRAPH, htmlText |
| .getId(), textClass, element.getText(), indentLevel); |
| if (text != null) |
| divElement.addContent(text); |
| } |
| } |
| return divElement; |
| } |
| |
| /** |
| * Generate "embedded" content from an IntroHTML. An OBJECT html element is created whose |
| * <code>data</code> attribute is equal to the IntroHTML's <code>src</code> value. |
| * |
| * <pre> |
| * |
| * <OBJECT type="text/html" data="attrvalue"> |
| * alternative text in case the object can not be rendered |
| * </OBJECT> |
| * |
| * </pre> |
| * |
| * @param element |
| * the IntroHTML |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return an object HTMLElement |
| */ |
| private HTMLElement generateEmbeddedIntroHTML(IntroHTML element, int indentLevel) { |
| HTMLElement objectElement = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_OBJECT, indentLevel, |
| true); |
| objectElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_TYPE, IIntroHTMLConstants.OBJECT_TYPE); |
| if (element.getId() != null) |
| objectElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, element.getId()); |
| if (element.getSrc() != null) |
| objectElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_DATA, element.getSrc()); |
| if (element.getStyleId() != null) |
| objectElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, element.getStyleId()); |
| // The alternative content is added in case the browser can not render |
| // the specified content. |
| IntroText htmlText = element.getIntroText(); |
| if (htmlText != null && htmlText.getText() != null) { |
| String textClass = (htmlText.getStyleId() != null) ? htmlText.getStyleId() |
| : IIntroHTMLConstants.SPAN_CLASS_TEXT; |
| HTMLElement text = generateTextElement(IIntroHTMLConstants.ELEMENT_PARAGRAPH, htmlText.getId(), |
| textClass, element.getText(), indentLevel); |
| if (text != null) |
| objectElement.addContent(text); |
| } |
| if (element.getIntroImage() != null) { |
| HTMLElement img = generateIntroImage(element.getIntroImage(), indentLevel); |
| if (img != null) |
| objectElement.addContent(img); |
| } |
| return objectElement; |
| } |
| |
| /** |
| * Generates the BASE element for the head of the html document. Each document can have only one |
| * base element |
| * |
| * <pre> |
| * |
| * |
| * <BASE href=baseURL> |
| * </pre> |
| * |
| * @param indentLevel |
| * @param baseURL |
| * @return |
| */ |
| private HTMLElement generateBaseElement(int indentLevel, String baseURL) { |
| HTMLElement base = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_BASE, indentLevel, true, |
| false); |
| if (baseURL != null) |
| base.addAttribute(IIntroHTMLConstants.ATTRIBUTE_HREF, baseURL); |
| return base; |
| } |
| |
| /** |
| * Generates the style element that goes into HEAD: |
| * |
| * <pre> |
| * |
| * <style type="text/css">HTML, IMG { border: 0px; } </style> |
| * |
| * </pre> |
| * |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return the style HTMLElement |
| */ |
| private HTMLElement generateStyleElement(int indentLevel) { |
| HTMLElement style = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_STYLE, indentLevel, false); |
| style.addAttribute(IIntroHTMLConstants.ATTRIBUTE_TYPE, IIntroHTMLConstants.LINK_STYLE); |
| style.addContent(IIntroHTMLConstants.STYLE_HTML); |
| IntroTheme theme = introPage.getModelRoot().getTheme(); |
| if (theme != null && theme.isScalable() |
| && FontSelection.FONT_RELATIVE.equals(FontSelection.getFontStyle())) { |
| String sizeStyle = FontSelection.generatePageFontStyle(); |
| style.addContent(sizeStyle); |
| } |
| return style; |
| } |
| |
| /** |
| * Generates the title element and its content: |
| * |
| * <pre> |
| * |
| * <TITLE>intro title</TITLE> |
| * |
| * </pre> |
| * |
| * @param title |
| * the title of this intro page |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return the title HTMLElement |
| */ |
| private HTMLElement generateTitleElement(String title, int indentLevel) { |
| HTMLElement titleElement = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_TITLE, indentLevel, |
| false); |
| if (title != null) |
| titleElement.addContent(title); |
| return titleElement; |
| } |
| |
| /** |
| * Generates a link element that refers to a cascading style sheet (CSS): |
| * |
| * <pre> |
| * |
| * |
| * <LINK rel="stylesheet" style="text/css" href="style sheet"> |
| * </pre> |
| * |
| * @param href |
| * the value of the href attribute for this link element |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return a link HTMLElement |
| */ |
| private HTMLElement generateLinkElement(String href, int indentLevel) { |
| HTMLElement link = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_LINK, indentLevel, true, |
| false); |
| link.addAttribute(IIntroHTMLConstants.ATTRIBUTE_RELATIONSHIP, IIntroHTMLConstants.LINK_REL); |
| link.addAttribute(IIntroHTMLConstants.ATTRIBUTE_TYPE, IIntroHTMLConstants.LINK_STYLE); |
| if (href != null) |
| link.addAttribute(IIntroHTMLConstants.ATTRIBUTE_HREF, href); |
| return link; |
| } |
| |
| /** |
| * Generate an anchor element: |
| * |
| * <pre> |
| * |
| * <A id=linkId class=linkClass href=linkHref> </A> |
| * |
| * </pre> |
| * |
| * @param link |
| * the IntroLink element that contains the value for the id and href attributes |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return an anchor (<A>) HTMLElement |
| */ |
| private HTMLElement generateAnchorElement(IntroLink link, int indentLevel) { |
| HTMLElement anchor = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_ANCHOR, indentLevel, true); |
| if (link.getId() != null) |
| anchor.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, link.getId()); |
| if (link.getUrl() != null) |
| anchor.addAttribute(IIntroHTMLConstants.ATTRIBUTE_HREF, link.getUrl()); |
| if (link.getStyleId() != null) |
| anchor.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, link.getStyleId()); |
| else |
| anchor.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, IIntroHTMLConstants.ANCHOR_CLASS_LINK); |
| return anchor; |
| } |
| |
| /** |
| * Generates a div block that contains a header and span element: |
| * |
| * <pre> |
| * |
| * |
| * <DIV id=divId> |
| * <H><SPAN>spanContent </SPAN> </H> |
| * </DIV> |
| * |
| * </pre> |
| * |
| * @param divId |
| * the id of the div to create |
| * @param divClass |
| * the class of the div |
| * @param headerType |
| * what type of header to create (e.g., H1, H2, etc) |
| * @param spanContent |
| * the span content |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return a div HTMLElement that contains a header |
| */ |
| private HTMLElement generateHeaderDiv(String divId, String divClass, String headerType, |
| String spanContent, int indentLevel) { |
| // create the text element: <P><SPAN>spanContent</SPAN></P> |
| HTMLElement text = generateTextElement(headerType, null, null, spanContent, indentLevel + 1); |
| // create the containing div element |
| HTMLElement div = generateDivElement(divId, divClass, indentLevel); |
| div.addContent(text); |
| return div; |
| } |
| |
| /** |
| * Generates a span element inside a text element, where the text element can be a P |
| * (paragraph), or any of the H (Header) elements. Providing the span element provides |
| * additional flexibility for CSS designers. |
| * |
| * <pre> |
| * |
| * <P><SPAN>spanContent</SPAN></P> |
| * |
| * </pre> |
| * |
| * @param type |
| * the type of text element to create (e.g., P, H1, H2, etc) |
| * @param spanID |
| * the id of the span element, or null |
| * @param spanClass |
| * the class of the span element, or null |
| * @param spanContent |
| * the span content |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return a text HTMLElement that contains a span element |
| */ |
| private HTMLElement generateTextElement(String type, String spanID, String spanClass, String spanContent, |
| int indentLevel) { |
| // Create the span: <SPAN>spanContent</SPAN> |
| HTMLElement span = new HTMLElement(IIntroHTMLConstants.ELEMENT_SPAN); |
| if (spanID != null) |
| span.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, spanID); |
| if (spanClass != null) |
| span.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, spanClass); |
| if (spanContent != null) |
| span.addContent(spanContent); |
| if (type != null) { |
| // Create the enclosing text element: <P><SPAN>spanContent</SPAN></P> |
| HTMLElement text = new FormattedHTMLElement(type, indentLevel, false); |
| text.addContent(span); |
| return text; |
| } else { |
| return span; |
| } |
| } |
| |
| /** |
| * Generates a DIV element with the provided indent, id, and class. |
| * |
| * @param divId |
| * value for the div's id attribute |
| * @param divClass |
| * value for the div's class attribute |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return a div HTMLElement |
| */ |
| private HTMLElement generateDivElement(String divId, String divClass, int indentLevel) { |
| HTMLElement div = generateDivElement(divId, indentLevel); |
| div.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, divClass); |
| return div; |
| } |
| |
| /** |
| * Generates a DIV element with the provided indent and id. |
| * |
| * @param divId |
| * value for the div's id attribute |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return a div HTMLElement |
| */ |
| private HTMLElement generateDivElement(String divId, int indentLevel) { |
| HTMLElement div = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_DIV, indentLevel, true); |
| if (divId != null) |
| div.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, divId); |
| return div; |
| } |
| |
| /** |
| * Generates an IMG element: |
| * |
| * <pre> |
| * |
| * |
| * <IMG src=imageSrc alt=altText> |
| * |
| * </pre> |
| * |
| * @param imageSrc |
| * the value to be supplied to the src attribute |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return an img HTMLElement |
| */ |
| private HTMLElement generateImageElement(String imageSrc, String altText, String title, String imageClass, |
| int indentLevel) { |
| HTMLElement image = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_IMG, indentLevel, true, |
| false); |
| boolean pngOnWin32 = imageSrc != null && Platform.getWS().equals(Platform.WS_WIN32) |
| && !backgroundSizeWorks && imageSrc.toLowerCase().endsWith(".png"); //$NON-NLS-1$ |
| if (imageSrc == null || pngOnWin32) { |
| // we must handle PNGs here - IE does not support alpha blanding well. |
| // We will set the alpha image loader and load the real image |
| // that way. The 'src' attribute in the image itself will |
| // get the blank image. |
| String blankImageURL = BundleUtil.getResolvedResourceLocation( |
| IIntroHTMLConstants.IMAGE_SRC_BLANK, IIntroConstants.PLUGIN_ID); |
| if (blankImageURL != null) { |
| image.addAttribute(IIntroHTMLConstants.ATTRIBUTE_SRC, blankImageURL); |
| if (pngOnWin32) { |
| String style = "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + imageSrc + "', sizingMethod='image')"; //$NON-NLS-1$//$NON-NLS-2$ |
| image.addAttribute(IIntroHTMLConstants.ATTRIBUTE_STYLE, style); |
| } |
| } |
| } else |
| image.addAttribute(IIntroHTMLConstants.ATTRIBUTE_SRC, imageSrc); |
| if (altText == null) |
| altText = ""; //$NON-NLS-1$ |
| image.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ALT, altText); |
| if (title != null) { |
| image.addAttribute(IIntroHTMLConstants.ATTRIBUTE_TITLE, title); |
| } |
| if (imageClass != null) |
| image.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, imageClass); |
| return image; |
| } |
| |
| /** |
| * Generate a span element |
| * |
| * <pre> |
| * |
| * <SPAN class=spanClass> </SPAN> |
| * |
| * |
| * </pre> |
| * |
| * @param spanClass |
| * the value to be supplied to the class attribute |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return a span HTMLElement |
| */ |
| private HTMLElement generateSpanElement(String spanClass, int indentLevel) { |
| HTMLElement span = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_SPAN, indentLevel, false); |
| span.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, spanClass); |
| return span; |
| } |
| |
| /** |
| * Generate a span element |
| * |
| * <pre> |
| * |
| * <iframe src="localPage1.xhtml" frameborder="1" scrolling="auto" longdesc="localPage1.xhtml"> |
| * </pre> |
| * |
| * @param spanClass |
| * the value to be supplied to the class attribute |
| * @param indentLevel |
| * the number of indents to insert before the element when it is printed |
| * @return a span HTMLElement |
| */ |
| private HTMLElement generateIFrameElement(String src, String frameborder, String scrolling, |
| int indentLevel) { |
| HTMLElement iframe = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_IFrame, indentLevel, false); |
| if (src != null) |
| iframe.addAttribute(IIntroHTMLConstants.ATTRIBUTE_SRC, src); |
| if (frameborder != null) |
| iframe.addAttribute(IIntroHTMLConstants.ATTRIBUTE_FRAMEBORDER, frameborder); |
| if (scrolling != null) |
| iframe.addAttribute(IIntroHTMLConstants.ATTRIBUTE_SCROLLING, scrolling); |
| return iframe; |
| } |
| |
| |
| |
| |
| private boolean filteredFromPresentation(AbstractIntroElement element) { |
| if (element.isOfType(AbstractIntroElement.BASE_ELEMENT)) |
| return ((AbstractBaseIntroElement) element).isFiltered(); |
| |
| return false; |
| } |
| |
| /** |
| * Reads the content of the file referred to by the <code>src</code> parameter and returns the |
| * content in the form of a StringBuffer. If the file read contains substitution segments of the |
| * form $plugin:plugin_id$ then this method will make the proper substitution (the segment will |
| * be replaced with the absolute path to the plugin with id plugin_id). |
| * |
| * @param src - |
| * the file that contains the target conent |
| * @param charsetName - |
| * the encoding of the file to be read. If null, local encoding is used. But the |
| * default of the model is UTF-8, so we should not get a null encoding. |
| * @return a StringBuffer containing the content in the file, or null |
| */ |
| private StringBuffer readFromFile(String src, String charsetName) { |
| if (src == null) |
| return null; |
| InputStream stream = null; |
| StringBuffer content = new StringBuffer(); |
| BufferedReader reader = null; |
| try { |
| URL url = new URL(src); |
| stream = url.openStream(); |
| // TODO: Do we need to worry about the encoding here? e.g.: |
| // reader = new BufferedReader(new InputStreamReader(stream, |
| // ResourcesPlugin.getEncoding())); |
| if (charsetName == null) |
| reader = new BufferedReader(new InputStreamReader(stream)); |
| else |
| reader = new BufferedReader(new InputStreamReader(stream, charsetName)); |
| while (true) { |
| int character = reader.read(); |
| if (character == -1) // EOF |
| break; // done reading file |
| |
| else if (character == PluginIdParser.SUBSTITUTION_BEGIN) { // possible |
| // substitution |
| PluginIdParser parser = new PluginIdParser(character, reader); |
| // If a valid plugin id was found in the proper format, text |
| // will be the absolute path to that plugin. Otherwise, text |
| // will simply be all characters read up to (but not |
| // including) |
| // the next dollar sign that follows the one just found. |
| String text = parser.parsePluginId(); |
| if (text != null) |
| content.append(text); |
| } else { |
| // make sure character is in char range before making cast |
| if (character > 0x00 && character < 0xffff) |
| content.append((char) character); |
| else |
| content.append(character); |
| } |
| } |
| } catch (Exception exception) { |
| Log.error("Error reading from file", exception); //$NON-NLS-1$ |
| } finally { |
| try { |
| if (reader != null) |
| reader.close(); |
| if (stream != null) |
| stream.close(); |
| } catch (IOException e) { |
| Log.error("Error closing input stream", e); //$NON-NLS-1$ |
| return null; |
| } |
| } |
| return content; |
| } |
| |
| /** |
| * A helper class to help identify substitution strings in a content file. A properly formatted |
| * substitution string is of the form: <code>$plugin:plugin_id$</code> where plugin_id is the |
| * valid id of an installed plugin. The substitution string will be replaced with the absolute |
| * path to the plugin. |
| * |
| * An example usage of the string substution: The html file <code>inline.html</code> is |
| * included in your intro via the html inline mechanism . This file needs to reference a |
| * resource that is located in another plugin. The following might be found in inline.html: |
| * <code> |
| * <a href="$plugin:test.plugin$html/test.html">link to file</a> |
| * </code> When this file |
| * is read in, the relevant section will be replaced as follows: <code> |
| * <a href="file:/install_path/plugins/test.plugin/html/test.html">link to file</a> |
| * </code> |
| * |
| */ |
| private static class PluginIdParser { |
| |
| private BufferedReader reader; |
| |
| private static final char SUBSTITUTION_BEGIN = '$'; |
| |
| private static final char SUBSTITUTION_END = '$'; |
| |
| // tokenContent will contain all characters read by the parser, starting |
| // with and including the initial $ token. |
| private StringBuffer tokenContent; |
| |
| // pluginId will contain the content between the "$plugin:" segment |
| // and the closing "$" token |
| private StringBuffer pluginId; |
| |
| protected PluginIdParser(int tokenBegin, BufferedReader bufferedreader) { |
| reader = bufferedreader; |
| tokenContent = new StringBuffer(); |
| pluginId = new StringBuffer(); |
| // make sure tokenBegin is in char range before making cast |
| if (tokenBegin > 0x00 && tokenBegin < 0xffff) |
| tokenContent.append((char) tokenBegin); |
| } |
| |
| /** |
| * This method should be called after the initial substitution identifier has been read in |
| * (the substition string begins and ends with the "$" character). A properly formatted |
| * substitution string is of the form:</code> "$plugin:plugin_id$</code>- the initial "$" |
| * is immediately followed by the "plugin:" segment - the <code>plugin_id </code> refers to |
| * a valid, installed plugin - the substitution string is terminated by a closing "$" If the |
| * above conditions are not met, no substitution occurs. If the above conditions are met, |
| * the content between (and including) the opening and closing "$" characters will be |
| * replaced by the absolute path to the plugin |
| * |
| * @return |
| */ |
| protected String parsePluginId() { |
| if (reader == null || tokenContent == null || pluginId == null) |
| return null; |
| |
| try { |
| // Mark the current position of the reader so we can roll |
| // back to this point if the proper "plugin:" segment is not |
| // found. |
| // Use 1024 as our readAheadLimit |
| reader.mark(0x400); |
| if (findValidPluginSegment()) { |
| String pluginPath = getPluginPath(); |
| if (pluginPath == null) { |
| // Didn't find a valid plugin id. |
| // return tokenContent, which contains all characters |
| // read up to (not including) the last $. (if the |
| // last $ is part of a subsequent "$plugin:" segment |
| // it can still be processed properly) |
| return tokenContent.toString(); |
| } |
| return pluginPath; |
| } |
| |
| // The "plugin:" segment was not found. Reset the reader |
| // so we can continue reading character by character. |
| reader.reset(); |
| return tokenContent.toString(); |
| |
| } catch (IOException exception) { |
| Log.error("Error reading from file", exception); //$NON-NLS-1$ |
| return tokenContent.toString(); |
| } |
| } |
| |
| /** |
| * This method should be called after an initial substitution character has been found (that |
| * is, after a $). It looks at the subsequent characters in the input stream to determine if |
| * they match the expected <code>plugin:</code> segment of the substitution string. If the |
| * expected characters are found, they will be appended to the tokenContent StringBuffer and |
| * the method will return true. If they are not found, false is returned and the caller |
| * should reset the BufferedReader to the position it was in before this method was called. |
| * |
| * Resetting the reader ensures that the characters read in this method can be re-examined |
| * in case one of them happens to be the beginning of a valid substitution segment. |
| * |
| * @return true if the next characters match <code>plugin:</code>, and false otherwise. |
| */ |
| private boolean findValidPluginSegment() { |
| final char[] PLUGIN_SEGMENT = { 'p', 'l', 'u', 'g', 'i', 'n', ':' }; |
| char[] streamContent = new char[PLUGIN_SEGMENT.length]; |
| try { |
| int peek = reader.read(streamContent, 0, PLUGIN_SEGMENT.length); |
| if ((peek == PLUGIN_SEGMENT.length) |
| && (HTMLUtil.equalCharArrayContent(streamContent, PLUGIN_SEGMENT))) { |
| // we have found the "$plugin:" segment |
| tokenContent.append(streamContent); |
| return true; |
| } |
| // The "plugin:" segment did not immediately follow the initial |
| // $. |
| return false; |
| } catch (IOException exception) { |
| Log.error("Error reading from file", exception); //$NON-NLS-1$ |
| return false; |
| } |
| } |
| |
| /** |
| * This method continues to read from the input stream until either the end of the file is |
| * reached, or until a character is found that indicates the end of the substitution. If the |
| * SUBSTITUTION_END character is found, the method looks up the plugin id that has been |
| * built up to see if it is a valid id. If so, return the absolute path to that plugin. If |
| * not, return null. |
| * |
| * This method assumes that the reader is positioned just after a valid <code>plugin:</code> |
| * segment in a substitution string. |
| * |
| * @return absolute path of the plugin id, if valid. null otherwise |
| */ |
| private String getPluginPath() { |
| try { |
| while (true) { |
| int nextChar = reader.read(); |
| |
| if (nextChar == -1) { |
| // reached EOF while looking for closing $ |
| return null; |
| } else if (nextChar == SUBSTITUTION_END) { // end of plugin |
| // id |
| // look up the plugin id. If it is a valid id |
| // return the absolute path to this plugin. |
| // otherwise return null. |
| String path = BundleUtil.getResolvedBundleLocation(pluginId.toString()); |
| |
| // If the plugin id was not valid, reset reader to the |
| // previous mark. The mark should be at the character |
| // just before the last dollar sign. |
| if (path == null) |
| reader.reset(); |
| |
| return path; |
| } else { // we have a regular character |
| // mark the most recent non-dollar char in case we don't |
| // find a valid plugin id and have to roll back |
| // Use 1024 as our readAheadLimit |
| reader.mark(0x400); |
| // Add this character to the pluginId and tokenContent |
| // String. |
| // make sure we have a valid character before performing |
| // cast |
| if (nextChar > 0x00 && nextChar < 0xffff) { |
| tokenContent.append((char) nextChar); |
| // only include non-whitespace characters in plugin |
| // id |
| if (!Character.isWhitespace((char) nextChar)) |
| pluginId.append((char) nextChar); |
| } else { |
| tokenContent.append(nextChar); |
| pluginId.append(nextChar); |
| } |
| } |
| } |
| } catch (IOException exception) { |
| Log.error("Error reading from file", exception); //$NON-NLS-1$ |
| return null; |
| } |
| } |
| } |
| |
| } |