1 /******************************************************************************* 2 * Copyright (c) 2004, 2017 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.ui.internal.intro.impl.html; 15 16 import java.io.BufferedReader; 17 import java.io.IOException; 18 import java.io.InputStream; 19 import java.io.InputStreamReader; 20 import java.io.PrintWriter; 21 import java.io.StringWriter; 22 import java.lang.reflect.Field; 23 import java.net.URL; 24 import java.util.Map; 25 26 import org.eclipse.core.runtime.Platform; 27 import org.eclipse.help.internal.util.ProductPreferences; 28 import org.eclipse.swt.browser.Browser; 29 import org.eclipse.ui.internal.intro.impl.FontSelection; 30 import org.eclipse.ui.internal.intro.impl.IIntroConstants; 31 import org.eclipse.ui.internal.intro.impl.IntroPlugin; 32 import org.eclipse.ui.internal.intro.impl.model.AbstractBaseIntroElement; 33 import org.eclipse.ui.internal.intro.impl.model.AbstractIntroElement; 34 import org.eclipse.ui.internal.intro.impl.model.AbstractIntroPage; 35 import org.eclipse.ui.internal.intro.impl.model.IntroContentProvider; 36 import org.eclipse.ui.internal.intro.impl.model.IntroGroup; 37 import org.eclipse.ui.internal.intro.impl.model.IntroHTML; 38 import org.eclipse.ui.internal.intro.impl.model.IntroHead; 39 import org.eclipse.ui.internal.intro.impl.model.IntroImage; 40 import org.eclipse.ui.internal.intro.impl.model.IntroInjectedIFrame; 41 import org.eclipse.ui.internal.intro.impl.model.IntroLink; 42 import org.eclipse.ui.internal.intro.impl.model.IntroPageTitle; 43 import org.eclipse.ui.internal.intro.impl.model.IntroSeparator; 44 import org.eclipse.ui.internal.intro.impl.model.IntroText; 45 import org.eclipse.ui.internal.intro.impl.model.IntroTheme; 46 import org.eclipse.ui.internal.intro.impl.model.loader.ContentProviderManager; 47 import org.eclipse.ui.internal.intro.impl.model.util.BundleUtil; 48 import org.eclipse.ui.internal.intro.impl.presentations.BrowserIntroPartImplementation; 49 import org.eclipse.ui.internal.intro.impl.util.Log; 50 import org.eclipse.ui.intro.config.IIntroContentProvider; 51 import org.eclipse.ui.intro.config.IIntroContentProviderSite; 52 53 public class IntroHTMLGenerator { 54 55 private AbstractIntroPage introPage; 56 57 private IIntroContentProviderSite providerSite; 58 private boolean backgroundSizeWorks; 59 60 /** 61 * Generates the HTML code that will be presented in the browser widget for the provided intro 62 * page. 63 * 64 * @param page 65 * the page to generate HTML for 66 * @param presentation 67 * the presentation associated with this page. 68 */ generateHTMLforPage(AbstractIntroPage page, IIntroContentProviderSite providerSite)69 public HTMLElement generateHTMLforPage(AbstractIntroPage page, IIntroContentProviderSite providerSite) { 70 if (page == null) 71 return null; 72 this.introPage = page; 73 this.providerSite = providerSite; 74 75 initializeBackgroundSizeWorks(); 76 77 // generate and add the appropriate encoding to the top of the document 78 // generateEncoding(); 79 // create the main HTML element, and all of its contents. 80 return generateHTMLElement(); 81 } 82 initializeBackgroundSizeWorks()83 private void initializeBackgroundSizeWorks() { 84 // Internet Explorer <= 9 doesn't properly handle background-size 85 backgroundSizeWorks = true; 86 try { 87 if (isIE()) { 88 Class<?> ieClass = Class.forName("org.eclipse.swt.browser.IE"); //$NON-NLS-1$ 89 Field field = ieClass.getDeclaredField("IEVersion"); //$NON-NLS-1$ 90 field.setAccessible(true); 91 int value = field.getInt(ieClass); 92 // We specifically care about background-size which works in 9+ 93 backgroundSizeWorks = value <= 0 || value >= 9; 94 } 95 } catch(Exception e) { 96 // IE not found 97 } 98 } 99 isIE()100 private boolean isIE() { 101 return getBrowser() != null && "ie".equals(getBrowser().getBrowserType()); //$NON-NLS-1$ 102 } 103 104 /** 105 * Return the SWT Browser instance being used to render the intro. 106 * 107 * @return the browser or {@code null} if the browser could not be determined 108 */ getBrowser()109 private Browser getBrowser() { 110 if (providerSite instanceof BrowserIntroPartImplementation) { 111 return ((BrowserIntroPartImplementation) providerSite).getBrowser(); 112 } 113 return null; 114 } 115 116 /* 117 * private HTMLElement generateEncoding() { HTMLElement encoding = new HTMLElement(""); 118 * //$NON-NLS-1$ // TODO: figure out how to handle locale based encoding // As far as the HTML 119 * generator is concerned, this is probably as // simple as asking the model for the information 120 * return encoding; } 121 */ 122 123 /** 124 * Generates the HTML element and its content: 125 * 126 * <pre> 127 * 128 * <HTML> 129 * <HEAD> 130 * head content 131 * </HEAD> 132 * <BODY> 133 * body content 134 * </BODY> 135 * </HTML> 136 * 137 * </pre> 138 * 139 * @return the html HTMLElement 140 */ generateHTMLElement()141 private HTMLElement generateHTMLElement() { 142 // this is the outermost element, so it has no indent 143 int indentLevel = 0; 144 HTMLElement html = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_HTML, indentLevel, true); 145 HTMLElement head = generateHeadElement(indentLevel + 1); 146 HTMLElement body = generateBodyElement(indentLevel + 1, head); 147 html.addContent(head); 148 html.addContent(body); 149 return html; 150 } 151 152 /** 153 * Generates the HEAD element and its content: 154 * 155 * <pre> 156 * 157 * 158 * <HEAD> 159 * <BASE href="base_plugin_location> 160 * <style type="text/css">HTML, IMG { border: 0px; } </style> 161 * <TITLE>page title </TITLE> 162 * <LINK href="style sheet"> 163 * additional head content, if specified 164 * </HEAD> 165 * 166 * </pre> 167 * 168 * @param indentLevel 169 * the number of indents to insert before the element when it is printed 170 * @return the head HTMLElement 171 */ generateHeadElement(int indentLevel)172 private HTMLElement generateHeadElement(int indentLevel) { 173 HTMLElement head = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_HEAD, indentLevel, true); 174 addBrowserRenderingDirectives(head, indentLevel + 1); 175 // add the title 176 head.addContent(generateTitleElement(introPage.getTitle(), indentLevel + 1)); 177 head.addContent(generateUTF8CharsetElement(indentLevel + 1)); 178 // create the BASE element 179 String basePath = BundleUtil.getResolvedResourceLocation(introPage.getBase(), introPage.getBundle()); 180 HTMLElement base = generateBaseElement(indentLevel + 1, basePath); 181 if (base != null) 182 head.addContent(base); 183 // create the HTML style block 184 head.addContent(generateStyleElement(indentLevel + 1)); 185 // add the presentation style 186 String[] presentationStyles = IntroPlugin.getDefault().getIntroModelRoot().getPresentation() 187 .getImplementationStyles(); 188 if (presentationStyles != null && introPage.injectSharedStyle()) { 189 for (int i=0; i<presentationStyles.length; i++) 190 head.addContent(generateLinkElement(presentationStyles[i], indentLevel + 1)); 191 } 192 String pageStyle = introPage.getStyle(); 193 if (pageStyle != null) 194 head.addContent(generateLinkElement(pageStyle, indentLevel + 1)); 195 // add javascript 196 head.addContent(generateJavascriptElement(indentLevel + 1)); 197 198 // add the page's inherited style(s) 199 String[] pageStyles = introPage.getStyles(); 200 for (int i = 0; i < pageStyles.length; i++) { 201 pageStyle = pageStyles[i]; 202 if (pageStyle != null) 203 head.addContent(generateLinkElement(pageStyle, indentLevel + 1)); 204 } 205 // if there is additional head conent specified in an external file, 206 // include it. Additional head content can be specified at the 207 // implementation level (which would apply to ALL pages) and at the 208 // page level (which would apply only to that particular page). 209 // For the implementation's head contribution: 210 StringBuilder content = null; 211 IntroHead introHead = IntroPlugin.getDefault().getIntroModelRoot().getPresentation().getHead(); 212 if (introHead != null) { 213 content = readFromFile(introHead.getSrc(), introHead.getInlineEncoding()); 214 if (content != null) 215 head.addContent(content); 216 } 217 // For the page's head contribution: 218 // TODO: there should only be one of these at the page level, not a 219 // collection.. 220 IntroHead[] htmlHeads = introPage.getHTMLHeads(); 221 for (int i = 0; i < htmlHeads.length; i++) { 222 introHead = htmlHeads[i]; 223 if (introHead != null) { 224 content = readFromFile(introHead.getSrc(), introHead.getInlineEncoding()); 225 if (content != null) 226 head.addContent(content); 227 } 228 } 229 return head; 230 } 231 232 /** 233 * Add any browser-specific rendering quirks 234 * 235 * @param indentLevel 236 */ addBrowserRenderingDirectives(HTMLElement head, int indentLevel)237 private void addBrowserRenderingDirectives(HTMLElement head, int indentLevel) { 238 /* IE renders intranet content in Compat View by default */ 239 if (isIE() && "html5".equals(getThemeProperty("standardSupport"))) { //$NON-NLS-1$ //$NON-NLS-2$ 240 // "Edge mode tells Internet Explorer to display content in the highest mode available" 241 HTMLElement meta = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_META, indentLevel, true); 242 meta.addAttribute(IIntroHTMLConstants.ATTRIBUTE_HTTP_EQUIV, "X-UA-Compatible"); //$NON-NLS-1$ 243 meta.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CONTENT, "IE=edge"); //$NON-NLS-1$ 244 head.addContent(meta); 245 } 246 } 247 getThemeProperty(String key)248 private String getThemeProperty(String key) { 249 IntroTheme theme = introPage.getModelRoot().getTheme(); 250 if (theme == null) { 251 return null; 252 } 253 Map<String, String> properties = theme.getProperties(); 254 if (properties == null) { 255 return null; 256 } 257 return properties.get(key); 258 } 259 generateUTF8CharsetElement(int indentLevel)260 private HTMLElement generateUTF8CharsetElement(int indentLevel) { 261 HTMLElement meta = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_META, indentLevel, false); 262 meta.addAttribute(IIntroHTMLConstants.ATTRIBUTE_HTTP_EQUIV, IIntroHTMLConstants.CONTENT_TYPE); 263 meta.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CONTENT, IIntroHTMLConstants.TYPE_HTML_UTF_8); 264 return meta; 265 } 266 generateJavascriptElement(int indentLevel)267 private HTMLElement generateJavascriptElement(int indentLevel) { 268 String rel = "javascript/common.js"; //$NON-NLS-1$ 269 String abs = BundleUtil.getResolvedResourceLocation(rel, IntroPlugin.getDefault().getBundle()); 270 HTMLElement jselement = new FormattedHTMLElement("script", indentLevel, false); //$NON-NLS-1$ 271 jselement.addAttribute("type", "text/javascript"); //$NON-NLS-1$ //$NON-NLS-2$ 272 jselement.addAttribute("src", abs); //$NON-NLS-1$ 273 return jselement; 274 } 275 276 /** 277 * Generates the BODY element and its content: 278 * 279 * <pre> 280 * 281 * 282 * <BODY> 283 * <DIV id="pageId" class="pageClass"> 284 * page content 285 * </DIV> 286 * </BODY> 287 * 288 * </pre> 289 * 290 * @param indentLevel 291 * the number of indents to insert before the element when it is printed 292 * @return the body HTMLElement 293 */ generateBodyElement(int indentLevel, HTMLElement head)294 private HTMLElement generateBodyElement(int indentLevel, HTMLElement head) { 295 HTMLElement body = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_BODY, indentLevel, true); 296 // Create the div that contains the page content 297 String pageId = (introPage.getId() != null) ? introPage.getId() : IIntroHTMLConstants.DIV_ID_PAGE; 298 HTMLElement pageContentDiv = generateDivElement(pageId, indentLevel + 1); 299 if (introPage.getStyleId() != null) 300 pageContentDiv.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, introPage.getStyleId()); 301 if (introPage.getBackgroundImage() != null) 302 pageContentDiv.addAttribute(IIntroHTMLConstants.ATTRIBUTE_STYLE, 303 "background-image : url(" + introPage.getBackgroundImage() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ 304 305 // Add any children of the page, in the order they are defined 306 AbstractIntroElement[] children = introPage.getChildren(); 307 for (int i = 0; i < children.length; i++) { 308 AbstractIntroElement child = children[i]; 309 // use indentLevel + 2 here, since this element is contained within 310 // the pageContentDiv 311 HTMLElement childElement = generateIntroElement(child, indentLevel + 2); 312 if (childElement != null) { 313 addMixinStyle(childElement, child.getMixinStyle()); 314 pageContentDiv.addContent(childElement); 315 } 316 } 317 body.addContent(pageContentDiv); 318 return body; 319 } 320 321 /** 322 * Given an IntroElement, generate the appropriate HTMLElement 323 * 324 * @param element 325 * the IntroElement 326 * @param indentLevel 327 * the number of indents to insert before the element when it is printed 328 * @return an HTMLElement 329 */ generateIntroElement(AbstractIntroElement element, int indentLevel)330 private HTMLElement generateIntroElement(AbstractIntroElement element, int indentLevel) { 331 if (element == null) 332 return null; 333 // check to see if this element should be filtered from the HTML 334 // presentation 335 if (filteredFromPresentation(element)) 336 return null; 337 switch (element.getType()) { 338 case AbstractIntroElement.GROUP: 339 return generateIntroDiv((IntroGroup) element, indentLevel); 340 case AbstractIntroElement.LINK: 341 return generateIntroLink((IntroLink) element, indentLevel); 342 case AbstractIntroElement.HTML: 343 return generateIntroHTML((IntroHTML) element, indentLevel); 344 case AbstractIntroElement.CONTENT_PROVIDER: 345 return generateIntroContent((IntroContentProvider) element, indentLevel); 346 case AbstractIntroElement.IMAGE: 347 return generateIntroImage((IntroImage) element, indentLevel); 348 case AbstractIntroElement.HR: 349 return generateIntroSeparator((IntroSeparator) element, indentLevel); 350 case AbstractIntroElement.TEXT: 351 return generateIntroText((IntroText) element, indentLevel); 352 case AbstractIntroElement.PAGE_TITLE: 353 return generateIntroTitle((IntroPageTitle) element, indentLevel); 354 case AbstractIntroElement.INJECTED_IFRAME: 355 return generateIntroInjectedIFrame((IntroInjectedIFrame) element, indentLevel); 356 default: 357 return null; 358 } 359 } 360 361 /** 362 * Create a div element and its content from an IntroDiv: 363 * 364 * <pre> 365 * 366 * 367 * <div id="attrvalue"> 368 * <h4><span class="div-label">attrvalue</span><h4> 369 * any defined divs, links, html, images, text, includes 370 * </div> 371 * 372 * </pre> 373 * 374 * @param element 375 * the IntroDiv 376 * @param indentLevel 377 * the number of indents to insert before the element when it is printed 378 * @return a div HTMLElement 379 */ generateIntroDiv(IntroGroup element, int indentLevel)380 private HTMLElement generateIntroDiv(IntroGroup element, int indentLevel) { 381 // Create the outer div element 382 HTMLElement divElement = generateDivElement(element.getId(), indentLevel); 383 HTMLElement childContainer = divElement; 384 // if a div class was specified, add it 385 if (element.getStyleId() != null) 386 divElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, element.getStyleId()); 387 // Create the div label, if specified 388 if (element.getLabel() != null) { 389 if (element.isExpandable()) { 390 HTMLElement divLabel = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_SPAN, 391 indentLevel + 2, false); 392 divLabel.addContent(element.getLabel()); 393 divLabel.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, 394 "section-title");//$NON-NLS-1$ 395 String clientId = element.getId() + "-content"; //$NON-NLS-1$ 396 String toggleClosedId = element.getId() + "-toggle-closed"; //$NON-NLS-1$ 397 String toggleOpenId = element.getId() + "-toggle-open"; //$NON-NLS-1$ 398 String href = "#"; //$NON-NLS-1$ 399 HTMLElement link = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_ANCHOR, 400 indentLevel + 1, true); 401 link.addAttribute(IIntroHTMLConstants.ATTRIBUTE_HREF, href); 402 link.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, "section-title-link"); //$NON-NLS-1$ 403 StringBuilder call = new StringBuilder(); 404 call.append("return (toggleSection('");//$NON-NLS-1$ 405 call.append(clientId); 406 call.append("','");//$NON-NLS-1$ 407 call.append(toggleClosedId); 408 call.append("','");//$NON-NLS-1$ 409 call.append(toggleOpenId); 410 call.append("'))"); //$NON-NLS-1$ 411 link.addAttribute("onClick", call.toString()); //$NON-NLS-1$ 412 link.addContent(divLabel); 413 divElement.addContent(link); 414 // Add toggle images 415 HTMLElement toggleImageClosed = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_IMG, 416 indentLevel + 2, false, false); 417 toggleImageClosed.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, toggleClosedId); 418 toggleImageClosed.addAttribute(IIntroHTMLConstants.ATTRIBUTE_SRC, BundleUtil 419 .getResolvedResourceLocation(IIntroHTMLConstants.IMAGE_SRC_BLANK, 420 IIntroConstants.PLUGIN_ID)); 421 toggleImageClosed.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, "section-toggle-image-closed"); //$NON-NLS-1$ 422 if (element.isExpanded()) 423 toggleImageClosed.addAttribute(IIntroHTMLConstants.ATTRIBUTE_STYLE, "display: none"); //$NON-NLS-1$ 424 link.addContent(toggleImageClosed); 425 HTMLElement toggleImageOpen = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_IMG, 426 indentLevel + 2, false, false); 427 toggleImageOpen.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, toggleOpenId); 428 toggleImageOpen.addAttribute(IIntroHTMLConstants.ATTRIBUTE_SRC, BundleUtil 429 .getResolvedResourceLocation(IIntroHTMLConstants.IMAGE_SRC_BLANK, 430 IIntroConstants.PLUGIN_ID)); 431 toggleImageOpen.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, "section-toggle-image-open"); //$NON-NLS-1$ 432 if (element.isExpanded()) 433 toggleImageOpen.addAttribute(IIntroHTMLConstants.ATTRIBUTE_STYLE, "display: inline"); //$NON-NLS-1$ 434 link.addContent(toggleImageOpen); 435 childContainer = generateDivElement(clientId, indentLevel + 1); 436 childContainer.addAttribute("class", "section-body"); //$NON-NLS-1$//$NON-NLS-2$ 437 if (element.isExpanded()) 438 childContainer.addAttribute(IIntroHTMLConstants.ATTRIBUTE_STYLE, "display: block"); //$NON-NLS-1$ 439 divElement.addContent(childContainer); 440 } else { 441 HTMLElement divLabel = generateTextElement(IIntroHTMLConstants.ELEMENT_H4, null, 442 IIntroHTMLConstants.SPAN_CLASS_DIV_LABEL, element.getLabel(), indentLevel + 1); 443 divElement.addContent(divLabel); 444 } 445 } 446 if (element.getBackgroundImage() != null) { 447 String imageUrl = element.getBackgroundImage(); 448 imageUrl = BundleUtil.getResolvedResourceLocation(element.getBase(), imageUrl, element 449 .getBundle()); 450 String style; 451 if (Platform.getWS().equals(Platform.WS_WIN32) && !backgroundSizeWorks && imageUrl.toLowerCase().endsWith(".png")) { //$NON-NLS-1$ 452 // IE 5.5+ does not handle alphas in PNGs without 453 // this hack. Remove when IE7 becomes widespread 454 style = "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + imageUrl + "', sizingMethod='crop');"; //$NON-NLS-1$ //$NON-NLS-2$ 455 } else { 456 style = "background-image : url(" + imageUrl + ")"; //$NON-NLS-1$ //$NON-NLS-2$ 457 } 458 divElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_STYLE, style); 459 } 460 // Add any children of the div, in the order they are defined 461 AbstractIntroElement[] children = element.getChildren(); 462 for (int i = 0; i < children.length; i++) { 463 AbstractIntroElement child = children[i]; 464 HTMLElement childElement = generateIntroElement(child, indentLevel + 1); 465 if (childElement != null) { 466 addMixinStyle(childElement, child.getMixinStyle()); 467 childContainer.addContent(childElement); 468 } 469 } 470 return divElement; 471 } 472 addMixinStyle(HTMLElement element, String mixinStyle)473 private void addMixinStyle(HTMLElement element, String mixinStyle) { 474 if (mixinStyle == null) 475 return; 476 String key = "class"; //$NON-NLS-1$ 477 String original = element.getElementAttributes().get(key); 478 if (original == null) 479 original = mixinStyle; 480 else 481 original += " " + mixinStyle; //$NON-NLS-1$ 482 element.addAttribute(key, original); 483 } 484 485 /** 486 * Generates an anchor (link) element and its content from an IntroLink: 487 * 488 * <pre> 489 * 490 * <A id=linkId class="link" href=linkHref> 491 * <IMG src="blank.gif"> 492 * <SPAN class="link-label">linkLabel </SPAN> 493 * <P><SPAN>text</SPAN></P> 494 * </A> 495 * 496 * </pre> 497 * 498 * @param element 499 * the IntroLink 500 * @param indentLevel 501 * the number of indents to insert before the element when it is printed 502 * @return an anchor (<A>) HTMLElement 503 */ generateIntroLink(IntroLink element, int indentLevel)504 private HTMLElement generateIntroLink(IntroLink element, int indentLevel) { 505 String styleId = element.getStyleId(); 506 boolean useTable = ProductPreferences.isRTL() && "content-link".equals(styleId); //$NON-NLS-1$ 507 HTMLElement anchor1 = generateAnchorElement(element, indentLevel); 508 HTMLElement anchor2 = null; 509 HTMLElement labelAnchor = anchor1; 510 int indentBase = indentLevel; 511 if (useTable) { 512 indentBase = indentLevel + 1; 513 anchor2 = generateAnchorElement(element, indentLevel + 1); 514 labelAnchor = anchor2; 515 } 516 // add <IMG src="blank.gif"> 517 String blankImageURL = BundleUtil.getResolvedResourceLocation(IIntroHTMLConstants.IMAGE_SRC_BLANK, 518 IIntroConstants.PLUGIN_ID); 519 if (blankImageURL != null) { 520 anchor1.addContent(generateImageElement(blankImageURL, null, null, IIntroHTMLConstants.IMAGE_CLASS_BG, 521 indentBase + 1)); 522 } 523 // add link image, if one is specified 524 if (element.getImg() != null) { 525 HTMLElement img = generateIntroElement(element.getImg(), indentBase + 1); 526 if (img != null) 527 anchor1.addContent(img); 528 } 529 if (!useTable) { 530 HTMLElement imageDiv = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_DIV, indentBase+1, false); 531 imageDiv.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, 532 IIntroHTMLConstants.LINK_EXTRA_DIV); 533 anchor1.addContent(imageDiv); 534 } 535 // add <SPAN class="link-label">linkLabel</SPAN> 536 if (element.getLabel() != null) { 537 HTMLElement label = generateSpanElement(IIntroHTMLConstants.SPAN_CLASS_LINK_LABEL, 538 indentBase + 2); 539 label.addContent(element.getLabel()); 540 labelAnchor.addContent(label); 541 } 542 IntroText linkText = element.getIntroText(); 543 if (linkText != null && linkText.getText() != null) { 544 HTMLElement text = generateIntroElement(linkText, indentBase + 3); 545 if (text != null) 546 labelAnchor.addContent(text); 547 } 548 if (!useTable) { 549 return anchor1; 550 } 551 HTMLElement table = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_TABLE, indentLevel, false); 552 HTMLElement tr = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_TR, indentLevel + 1, false); 553 table.addContent(tr); 554 HTMLElement td1 = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_TD, indentLevel + 1, false); 555 HTMLElement td2 = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_TD, indentLevel + 1, false); 556 tr.addContent(td1); 557 tr.addContent(td2); 558 td1.addContent(anchor1); 559 td2.addContent(anchor2); 560 return table; 561 } 562 563 /** 564 * Generate the appropriate HTML from an IntroHTML. If the IntroHTML type is "inline", then the 565 * content from the referenced file is emitted as-is into a div element. If the type is "embed", 566 * an OBJECT html element is created whose <code>data</code> attribute is equal to the 567 * IntroHTML's <code>src</code> value 568 * 569 * @param element 570 * the IntroHTML 571 * @param indentLevel 572 * the number of indents to insert before the element when it is printed 573 * @return an HTMLElement 574 */ generateIntroHTML(IntroHTML element, int indentLevel)575 private HTMLElement generateIntroHTML(IntroHTML element, int indentLevel) { 576 if (element.isInlined()) 577 return generateInlineIntroHTML(element, indentLevel); 578 579 return generateEmbeddedIntroHTML(element, indentLevel); 580 } 581 582 /** 583 * Generate an image element from an IntroImage: 584 * 585 * <pre> 586 * 587 * <IMG src=imageSrc id=imageId> 588 * 589 * </pre> 590 * 591 * @param element 592 * the IntroImage 593 * @param indentLevel 594 * the number of indents to insert before the element when it is printed 595 * @return an img HTMLElement 596 */ generateIntroImage(IntroImage element, int indentLevel)597 private HTMLElement generateIntroImage(IntroImage element, int indentLevel) { 598 HTMLElement imageElement = generateImageElement(element.getSrc(), element.getAlt(),element.getTitle(), element 599 .getStyleId(), indentLevel); 600 if (element.getId() != null) 601 imageElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, element.getId()); 602 return imageElement; 603 } 604 generateIntroSeparator(IntroSeparator element, int indentLevel)605 private HTMLElement generateIntroSeparator(IntroSeparator element, int indentLevel) { 606 HTMLElement hrElement = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_HR, indentLevel, false); 607 if (element.getId() != null) 608 hrElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, element.getId()); 609 if (element.getStyleId() != null) 610 hrElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_STYLE, element.getStyleId()); 611 return hrElement; 612 } 613 614 /** 615 * Generate a paragraph (<P>) element from an IntroText. The paragraph element will 616 * contain a span element that will contain the actual text. Providing the span element provides 617 * additional flexibility for CSS designers. 618 * 619 * <pre> 620 * 621 * 622 * <P><SPAN>spanContent</SPAN></P> 623 * 624 * </pre> 625 * 626 * @param element 627 * the IntroText 628 * @param indentLevel 629 * the number of indents to insert before the element when it is printed 630 * @return a paragraph HTMLElement 631 */ generateIntroText(IntroText element, int indentLevel)632 private HTMLElement generateIntroText(IntroText element, int indentLevel) { 633 String spanClass = (element.getStyleId() != null) ? element.getStyleId() 634 : IIntroHTMLConstants.SPAN_CLASS_TEXT; 635 HTMLElement textElement = generateTextElement(IIntroHTMLConstants.ELEMENT_PARAGRAPH, element.getId(), 636 spanClass, element.getText(), indentLevel); 637 return textElement; 638 } 639 640 /** 641 * @param element 642 * @param indentLevel 643 * @return 644 */ generateIntroInjectedIFrame(IntroInjectedIFrame element, int indentLevel)645 private HTMLElement generateIntroInjectedIFrame(IntroInjectedIFrame element, int indentLevel) { 646 HTMLElement iframe = generateIFrameElement(element.getIFrameURL(), "0", //$NON-NLS-1$ 647 "auto", indentLevel); //$NON-NLS-1$ 648 return iframe; 649 } 650 651 /** 652 * @param element 653 * @param indentLevel 654 * @return 655 */ generateIntroTitle(IntroPageTitle element, int indentLevel)656 private HTMLElement generateIntroTitle(IntroPageTitle element, int indentLevel) { 657 HTMLElement titleElement = generateHeaderDiv(element.getId(), element.getStyleId(), 658 IIntroHTMLConstants.ELEMENT_H1, element.getTitle(), indentLevel); 659 return titleElement; 660 } 661 662 /** 663 * Generate "inline" content from an IntroHTML. The content from the file referenced by the 664 * IntroHTML's <code>src</code> attribute is emitted as-is into a div element: 665 * 666 * <pre> 667 * 668 * 669 * <div id="attrvalue" class="attrvalue2"> 670 * content from file specified in src attribute 671 * </div> 672 * 673 * </pre> 674 * 675 * @param element 676 * the IntroHTML 677 * @param indentLevel 678 * the number of indents to insert before the element when it is printed 679 * @return a div HTMLElement, or null if there was a problem reading from the file 680 */ generateInlineIntroHTML(IntroHTML element, int indentLevel)681 private HTMLElement generateInlineIntroHTML(IntroHTML element, int indentLevel) { 682 // make sure to ask model for encoding. If encoding is null (ie: not 683 // specified in 684 // markup, local encoding is used. 685 StringBuilder content = readFromFile(element.getSrc(), element.getInlineEncoding()); 686 if (content != null && content.length() > 0) { 687 // Create the outer div element 688 String divClass = (element.getStyleId() != null) ? element.getStyleId() 689 : IIntroHTMLConstants.DIV_CLASS_INLINE_HTML; 690 HTMLElement divElement = generateDivElement(element.getId(), divClass, indentLevel); 691 // add the content of the specified file into the div element 692 divElement.addContent(content); 693 return divElement; 694 } 695 return null; 696 } 697 698 /** 699 * Includes HTML content that is created by an IIntroContentProvider implementation. 700 * 701 * @param element 702 * @param indentLevel 703 * @return 704 */ generateIntroContent(IntroContentProvider element, int indentLevel)705 private HTMLElement generateIntroContent(IntroContentProvider element, int indentLevel) { 706 // create a new div to wrap the content 707 HTMLElement divElement = generateDivElement(element.getId(), 708 IIntroHTMLConstants.DIV_CLASS_PROVIDED_CONTENT, indentLevel); 709 710 // If we've already loaded the content provider for this element, 711 // retrieve it, otherwise load the class 712 IIntroContentProvider providerClass = ContentProviderManager.getInst().getContentProvider(element); 713 if (providerClass == null) 714 // content provider never created before, create it. 715 providerClass = ContentProviderManager.getInst().createContentProvider(element, providerSite); 716 717 if (providerClass != null) { 718 StringWriter stringWriter = new StringWriter(); 719 try (PrintWriter pw = new PrintWriter(stringWriter)) { 720 // create the specialized content 721 providerClass.createContent(element.getId(), pw); 722 // add the content of the specified file into the div element 723 stringWriter.flush(); 724 divElement.addContent(stringWriter.toString()); 725 } 726 } else { 727 // we couldn't load the content provider, so add any alternate 728 // text content if there is any 729 IntroText htmlText = element.getIntroText(); 730 if (htmlText != null && htmlText.getText() != null) { 731 String textClass = (htmlText.getStyleId() != null) ? htmlText.getStyleId() 732 : IIntroHTMLConstants.SPAN_CLASS_TEXT; 733 HTMLElement text = generateTextElement(IIntroHTMLConstants.ELEMENT_PARAGRAPH, htmlText 734 .getId(), textClass, element.getText(), indentLevel); 735 if (text != null) 736 divElement.addContent(text); 737 } 738 } 739 return divElement; 740 } 741 742 /** 743 * Generate "embedded" content from an IntroHTML. An OBJECT html element is created whose 744 * <code>data</code> attribute is equal to the IntroHTML's <code>src</code> value. 745 * 746 * <pre> 747 * 748 * <OBJECT type="text/html" data="attrvalue"> 749 * alternative text in case the object can not be rendered 750 * </OBJECT> 751 * 752 * </pre> 753 * 754 * @param element 755 * the IntroHTML 756 * @param indentLevel 757 * the number of indents to insert before the element when it is printed 758 * @return an object HTMLElement 759 */ generateEmbeddedIntroHTML(IntroHTML element, int indentLevel)760 private HTMLElement generateEmbeddedIntroHTML(IntroHTML element, int indentLevel) { 761 HTMLElement objectElement = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_OBJECT, indentLevel, 762 true); 763 objectElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_TYPE, IIntroHTMLConstants.OBJECT_TYPE); 764 if (element.getId() != null) 765 objectElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, element.getId()); 766 if (element.getSrc() != null) 767 objectElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_DATA, element.getSrc()); 768 if (element.getStyleId() != null) 769 objectElement.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, element.getStyleId()); 770 // The alternative content is added in case the browser can not render 771 // the specified content. 772 IntroText htmlText = element.getIntroText(); 773 if (htmlText != null && htmlText.getText() != null) { 774 String textClass = (htmlText.getStyleId() != null) ? htmlText.getStyleId() 775 : IIntroHTMLConstants.SPAN_CLASS_TEXT; 776 HTMLElement text = generateTextElement(IIntroHTMLConstants.ELEMENT_PARAGRAPH, htmlText.getId(), 777 textClass, element.getText(), indentLevel); 778 if (text != null) 779 objectElement.addContent(text); 780 } 781 if (element.getIntroImage() != null) { 782 HTMLElement img = generateIntroImage(element.getIntroImage(), indentLevel); 783 if (img != null) 784 objectElement.addContent(img); 785 } 786 return objectElement; 787 } 788 789 /** 790 * Generates the BASE element for the head of the html document. Each document can have only one 791 * base element 792 * 793 * <pre> 794 * 795 * 796 * <BASE href=baseURL> 797 * </pre> 798 * 799 * @param indentLevel 800 * @param baseURL 801 * @return 802 */ generateBaseElement(int indentLevel, String baseURL)803 private HTMLElement generateBaseElement(int indentLevel, String baseURL) { 804 HTMLElement base = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_BASE, indentLevel, true, 805 false); 806 if (baseURL != null) 807 base.addAttribute(IIntroHTMLConstants.ATTRIBUTE_HREF, baseURL); 808 return base; 809 } 810 811 /** 812 * Generates the style element that goes into HEAD: 813 * 814 * <pre> 815 * 816 * <style type="text/css">HTML, IMG { border: 0px; } </style> 817 * 818 * </pre> 819 * 820 * @param indentLevel 821 * the number of indents to insert before the element when it is printed 822 * @return the style HTMLElement 823 */ generateStyleElement(int indentLevel)824 private HTMLElement generateStyleElement(int indentLevel) { 825 HTMLElement style = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_STYLE, indentLevel, false); 826 style.addAttribute(IIntroHTMLConstants.ATTRIBUTE_TYPE, IIntroHTMLConstants.LINK_STYLE); 827 style.addContent(IIntroHTMLConstants.STYLE_HTML); 828 IntroTheme theme = introPage.getModelRoot().getTheme(); 829 if (theme != null && theme.isScalable() 830 && FontSelection.FONT_RELATIVE.equals(FontSelection.getFontStyle())) { 831 String sizeStyle = FontSelection.generatePageFontStyle(); 832 style.addContent(sizeStyle); 833 } 834 return style; 835 } 836 837 /** 838 * Generates the title element and its content: 839 * 840 * <pre> 841 * 842 * <TITLE>intro title</TITLE> 843 * 844 * </pre> 845 * 846 * @param title 847 * the title of this intro page 848 * @param indentLevel 849 * the number of indents to insert before the element when it is printed 850 * @return the title HTMLElement 851 */ generateTitleElement(String title, int indentLevel)852 private HTMLElement generateTitleElement(String title, int indentLevel) { 853 HTMLElement titleElement = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_TITLE, indentLevel, 854 false); 855 if (title != null) 856 titleElement.addContent(title); 857 return titleElement; 858 } 859 860 /** 861 * Generates a link element that refers to a cascading style sheet (CSS): 862 * 863 * <pre> 864 * 865 * 866 * <LINK rel="stylesheet" style="text/css" href="style sheet"> 867 * </pre> 868 * 869 * @param href 870 * the value of the href attribute for this link element 871 * @param indentLevel 872 * the number of indents to insert before the element when it is printed 873 * @return a link HTMLElement 874 */ generateLinkElement(String href, int indentLevel)875 private HTMLElement generateLinkElement(String href, int indentLevel) { 876 HTMLElement link = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_LINK, indentLevel, true, 877 false); 878 link.addAttribute(IIntroHTMLConstants.ATTRIBUTE_RELATIONSHIP, IIntroHTMLConstants.LINK_REL); 879 link.addAttribute(IIntroHTMLConstants.ATTRIBUTE_TYPE, IIntroHTMLConstants.LINK_STYLE); 880 if (href != null) 881 link.addAttribute(IIntroHTMLConstants.ATTRIBUTE_HREF, href); 882 return link; 883 } 884 885 /** 886 * Generate an anchor element: 887 * 888 * <pre> 889 * 890 * <A id=linkId class=linkClass href=linkHref> </A> 891 * 892 * </pre> 893 * 894 * @param link 895 * the IntroLink element that contains the value for the id and href attributes 896 * @param indentLevel 897 * the number of indents to insert before the element when it is printed 898 * @return an anchor (<A>) HTMLElement 899 */ generateAnchorElement(IntroLink link, int indentLevel)900 private HTMLElement generateAnchorElement(IntroLink link, int indentLevel) { 901 HTMLElement anchor = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_ANCHOR, indentLevel, true); 902 if (link.getId() != null) 903 anchor.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, link.getId()); 904 if (link.getUrl() != null) 905 anchor.addAttribute(IIntroHTMLConstants.ATTRIBUTE_HREF, link.getUrl()); 906 if (link.getStyleId() != null) 907 anchor.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, link.getStyleId()); 908 else 909 anchor.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, IIntroHTMLConstants.ANCHOR_CLASS_LINK); 910 return anchor; 911 } 912 913 /** 914 * Generates a div block that contains a header and span element: 915 * 916 * <pre> 917 * 918 * 919 * <DIV id=divId> 920 * <H><SPAN>spanContent </SPAN> </H> 921 * </DIV> 922 * 923 * </pre> 924 * 925 * @param divId 926 * the id of the div to create 927 * @param divClass 928 * the class of the div 929 * @param headerType 930 * what type of header to create (e.g., H1, H2, etc) 931 * @param spanContent 932 * the span content 933 * @param indentLevel 934 * the number of indents to insert before the element when it is printed 935 * @return a div HTMLElement that contains a header 936 */ generateHeaderDiv(String divId, String divClass, String headerType, String spanContent, int indentLevel)937 private HTMLElement generateHeaderDiv(String divId, String divClass, String headerType, 938 String spanContent, int indentLevel) { 939 // create the text element: <P><SPAN>spanContent</SPAN></P> 940 HTMLElement text = generateTextElement(headerType, null, null, spanContent, indentLevel + 1); 941 // create the containing div element 942 HTMLElement div = generateDivElement(divId, divClass, indentLevel); 943 div.addContent(text); 944 return div; 945 } 946 947 /** 948 * Generates a span element inside a text element, where the text element can be a P 949 * (paragraph), or any of the H (Header) elements. Providing the span element provides 950 * additional flexibility for CSS designers. 951 * 952 * <pre> 953 * 954 * <P><SPAN>spanContent</SPAN></P> 955 * 956 * </pre> 957 * 958 * @param type 959 * the type of text element to create (e.g., P, H1, H2, etc) 960 * @param spanID 961 * the id of the span element, or null 962 * @param spanClass 963 * the class of the span element, or null 964 * @param spanContent 965 * the span content 966 * @param indentLevel 967 * the number of indents to insert before the element when it is printed 968 * @return a text HTMLElement that contains a span element 969 */ generateTextElement(String type, String spanID, String spanClass, String spanContent, int indentLevel)970 private HTMLElement generateTextElement(String type, String spanID, String spanClass, String spanContent, 971 int indentLevel) { 972 // Create the span: <SPAN>spanContent</SPAN> 973 HTMLElement span = new HTMLElement(IIntroHTMLConstants.ELEMENT_SPAN); 974 if (spanID != null) 975 span.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, spanID); 976 if (spanClass != null) 977 span.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, spanClass); 978 if (spanContent != null) 979 span.addContent(spanContent); 980 if (type != null) { 981 // Create the enclosing text element: <P><SPAN>spanContent</SPAN></P> 982 HTMLElement text = new FormattedHTMLElement(type, indentLevel, false); 983 text.addContent(span); 984 return text; 985 } else { 986 return span; 987 } 988 } 989 990 /** 991 * Generates a DIV element with the provided indent, id, and class. 992 * 993 * @param divId 994 * value for the div's id attribute 995 * @param divClass 996 * value for the div's class attribute 997 * @param indentLevel 998 * the number of indents to insert before the element when it is printed 999 * @return a div HTMLElement 1000 */ generateDivElement(String divId, String divClass, int indentLevel)1001 private HTMLElement generateDivElement(String divId, String divClass, int indentLevel) { 1002 HTMLElement div = generateDivElement(divId, indentLevel); 1003 div.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, divClass); 1004 return div; 1005 } 1006 1007 /** 1008 * Generates a DIV element with the provided indent and id. 1009 * 1010 * @param divId 1011 * value for the div's id attribute 1012 * @param indentLevel 1013 * the number of indents to insert before the element when it is printed 1014 * @return a div HTMLElement 1015 */ generateDivElement(String divId, int indentLevel)1016 private HTMLElement generateDivElement(String divId, int indentLevel) { 1017 HTMLElement div = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_DIV, indentLevel, true); 1018 if (divId != null) 1019 div.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ID, divId); 1020 return div; 1021 } 1022 1023 /** 1024 * Generates an IMG element: 1025 * 1026 * <pre> 1027 * 1028 * 1029 * <IMG src=imageSrc alt=altText> 1030 * 1031 * </pre> 1032 * 1033 * @param imageSrc 1034 * the value to be supplied to the src attribute 1035 * @param indentLevel 1036 * the number of indents to insert before the element when it is printed 1037 * @return an img HTMLElement 1038 */ generateImageElement(String imageSrc, String altText, String title, String imageClass, int indentLevel)1039 private HTMLElement generateImageElement(String imageSrc, String altText, String title, String imageClass, 1040 int indentLevel) { 1041 HTMLElement image = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_IMG, indentLevel, true, 1042 false); 1043 boolean pngOnWin32 = imageSrc != null && Platform.getWS().equals(Platform.WS_WIN32) 1044 && !backgroundSizeWorks && imageSrc.toLowerCase().endsWith(".png"); //$NON-NLS-1$ 1045 if (imageSrc == null || pngOnWin32) { 1046 // we must handle PNGs here - IE does not support alpha blanding well. 1047 // We will set the alpha image loader and load the real image 1048 // that way. The 'src' attribute in the image itself will 1049 // get the blank image. 1050 String blankImageURL = BundleUtil.getResolvedResourceLocation( 1051 IIntroHTMLConstants.IMAGE_SRC_BLANK, IIntroConstants.PLUGIN_ID); 1052 if (blankImageURL != null) { 1053 image.addAttribute(IIntroHTMLConstants.ATTRIBUTE_SRC, blankImageURL); 1054 if (pngOnWin32) { 1055 String style = "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + imageSrc + "', sizingMethod='image')"; //$NON-NLS-1$//$NON-NLS-2$ 1056 image.addAttribute(IIntroHTMLConstants.ATTRIBUTE_STYLE, style); 1057 } 1058 } 1059 } else 1060 image.addAttribute(IIntroHTMLConstants.ATTRIBUTE_SRC, imageSrc); 1061 if (altText == null) 1062 altText = ""; //$NON-NLS-1$ 1063 image.addAttribute(IIntroHTMLConstants.ATTRIBUTE_ALT, altText); 1064 if (title != null) { 1065 image.addAttribute(IIntroHTMLConstants.ATTRIBUTE_TITLE, title); 1066 } 1067 if (imageClass != null) 1068 image.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, imageClass); 1069 return image; 1070 } 1071 1072 /** 1073 * Generate a span element 1074 * 1075 * <pre> 1076 * 1077 * <SPAN class=spanClass> </SPAN> 1078 * 1079 * 1080 * </pre> 1081 * 1082 * @param spanClass 1083 * the value to be supplied to the class attribute 1084 * @param indentLevel 1085 * the number of indents to insert before the element when it is printed 1086 * @return a span HTMLElement 1087 */ generateSpanElement(String spanClass, int indentLevel)1088 private HTMLElement generateSpanElement(String spanClass, int indentLevel) { 1089 HTMLElement span = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_SPAN, indentLevel, false); 1090 span.addAttribute(IIntroHTMLConstants.ATTRIBUTE_CLASS, spanClass); 1091 return span; 1092 } 1093 1094 /** 1095 * Generate a span element 1096 * 1097 * <pre> 1098 * 1099 * <iframe src="localPage1.xhtml" frameborder="1" scrolling="auto" longdesc="localPage1.xhtml"> 1100 * </pre> 1101 * 1102 * @param spanClass 1103 * the value to be supplied to the class attribute 1104 * @param indentLevel 1105 * the number of indents to insert before the element when it is printed 1106 * @return a span HTMLElement 1107 */ generateIFrameElement(String src, String frameborder, String scrolling, int indentLevel)1108 private HTMLElement generateIFrameElement(String src, String frameborder, String scrolling, 1109 int indentLevel) { 1110 HTMLElement iframe = new FormattedHTMLElement(IIntroHTMLConstants.ELEMENT_IFrame, indentLevel, false); 1111 if (src != null) 1112 iframe.addAttribute(IIntroHTMLConstants.ATTRIBUTE_SRC, src); 1113 if (frameborder != null) 1114 iframe.addAttribute(IIntroHTMLConstants.ATTRIBUTE_FRAMEBORDER, frameborder); 1115 if (scrolling != null) 1116 iframe.addAttribute(IIntroHTMLConstants.ATTRIBUTE_SCROLLING, scrolling); 1117 return iframe; 1118 } 1119 1120 1121 1122 filteredFromPresentation(AbstractIntroElement element)1123 private boolean filteredFromPresentation(AbstractIntroElement element) { 1124 if (element.isOfType(AbstractIntroElement.BASE_ELEMENT)) 1125 return ((AbstractBaseIntroElement) element).isFiltered(); 1126 1127 return false; 1128 } 1129 1130 /** 1131 * Reads the content of the file referred to by the <code>src</code> parameter and returns the 1132 * content in the form of a StringBuilder. If the file read contains substitution segments of the 1133 * form $plugin:plugin_id$ then this method will make the proper substitution (the segment will 1134 * be replaced with the absolute path to the plugin with id plugin_id). 1135 * 1136 * @param src - 1137 * the file that contains the target conent 1138 * @param charsetName - 1139 * the encoding of the file to be read. If null, local encoding is used. But the 1140 * default of the model is UTF-8, so we should not get a null encoding. 1141 * @return a StringBuilder containing the content in the file, or null 1142 */ readFromFile(String src, String charsetName)1143 private StringBuilder readFromFile(String src, String charsetName) { 1144 if (src == null) 1145 return null; 1146 InputStream stream = null; 1147 StringBuilder content = new StringBuilder(); 1148 BufferedReader reader = null; 1149 try { 1150 URL url = new URL(src); 1151 stream = url.openStream(); 1152 // TODO: Do we need to worry about the encoding here? e.g.: 1153 // reader = new BufferedReader(new InputStreamReader(stream, 1154 // ResourcesPlugin.getEncoding())); 1155 if (charsetName == null) 1156 reader = new BufferedReader(new InputStreamReader(stream)); 1157 else 1158 reader = new BufferedReader(new InputStreamReader(stream, charsetName)); 1159 while (true) { 1160 int character = reader.read(); 1161 if (character == -1) // EOF 1162 break; // done reading file 1163 1164 else if (character == PluginIdParser.SUBSTITUTION_BEGIN) { // possible 1165 // substitution 1166 PluginIdParser parser = new PluginIdParser(character, reader); 1167 // If a valid plugin id was found in the proper format, text 1168 // will be the absolute path to that plugin. Otherwise, text 1169 // will simply be all characters read up to (but not 1170 // including) 1171 // the next dollar sign that follows the one just found. 1172 String text = parser.parsePluginId(); 1173 if (text != null) 1174 content.append(text); 1175 } else { 1176 // make sure character is in char range before making cast 1177 if (character > 0x00 && character < 0xffff) 1178 content.append((char) character); 1179 else 1180 content.append(character); 1181 } 1182 } 1183 } catch (Exception exception) { 1184 Log.error("Error reading from file", exception); //$NON-NLS-1$ 1185 } finally { 1186 try { 1187 if (reader != null) 1188 reader.close(); 1189 if (stream != null) 1190 stream.close(); 1191 } catch (IOException e) { 1192 Log.error("Error closing input stream", e); //$NON-NLS-1$ 1193 return null; 1194 } 1195 } 1196 return content; 1197 } 1198 1199 /** 1200 * A helper class to help identify substitution strings in a content file. A properly formatted 1201 * substitution string is of the form: <code>$plugin:plugin_id$</code> where plugin_id is the 1202 * valid id of an installed plugin. The substitution string will be replaced with the absolute 1203 * path to the plugin. 1204 * 1205 * An example usage of the string substution: The html file <code>inline.html</code> is 1206 * included in your intro via the html inline mechanism . This file needs to reference a 1207 * resource that is located in another plugin. The following might be found in inline.html: 1208 * {@code 1209 * <a href="$plugin:test.plugin$html/test.html">link to file</a> 1210 * } When this file 1211 * is read in, the relevant section will be replaced as follows: {@code 1212 * <a href="file:/install_path/plugins/test.plugin/html/test.html">link to file</a> 1213 * } 1214 */ 1215 private static class PluginIdParser { 1216 1217 private BufferedReader reader; 1218 1219 private static final char SUBSTITUTION_BEGIN = '$'; 1220 1221 private static final char SUBSTITUTION_END = '$'; 1222 1223 // tokenContent will contain all characters read by the parser, starting 1224 // with and including the initial $ token. 1225 private StringBuilder tokenContent; 1226 1227 // pluginId will contain the content between the "$plugin:" segment 1228 // and the closing "$" token 1229 private StringBuilder pluginId; 1230 PluginIdParser(int tokenBegin, BufferedReader bufferedreader)1231 protected PluginIdParser(int tokenBegin, BufferedReader bufferedreader) { 1232 reader = bufferedreader; 1233 tokenContent = new StringBuilder(); 1234 pluginId = new StringBuilder(); 1235 // make sure tokenBegin is in char range before making cast 1236 if (tokenBegin > 0x00 && tokenBegin < 0xffff) 1237 tokenContent.append((char) tokenBegin); 1238 } 1239 1240 /** 1241 * This method should be called after the initial substitution identifier has been read in 1242 * (the substition string begins and ends with the "$" character). A properly formatted 1243 * substitution string is of the form: <code>"$plugin:plugin_id$</code>- the initial "$" 1244 * is immediately followed by the "plugin:" segment - the <code>plugin_id </code> refers to 1245 * a valid, installed plugin - the substitution string is terminated by a closing "$" If the 1246 * above conditions are not met, no substitution occurs. If the above conditions are met, 1247 * the content between (and including) the opening and closing "$" characters will be 1248 * replaced by the absolute path to the plugin 1249 * 1250 * @return 1251 */ parsePluginId()1252 protected String parsePluginId() { 1253 if (reader == null || tokenContent == null || pluginId == null) 1254 return null; 1255 1256 try { 1257 // Mark the current position of the reader so we can roll 1258 // back to this point if the proper "plugin:" segment is not 1259 // found. 1260 // Use 1024 as our readAheadLimit 1261 reader.mark(0x400); 1262 if (findValidPluginSegment()) { 1263 String pluginPath = getPluginPath(); 1264 if (pluginPath == null) { 1265 // Didn't find a valid plugin id. 1266 // return tokenContent, which contains all characters 1267 // read up to (not including) the last $. (if the 1268 // last $ is part of a subsequent "$plugin:" segment 1269 // it can still be processed properly) 1270 return tokenContent.toString(); 1271 } 1272 return pluginPath; 1273 } 1274 1275 // The "plugin:" segment was not found. Reset the reader 1276 // so we can continue reading character by character. 1277 reader.reset(); 1278 return tokenContent.toString(); 1279 1280 } catch (IOException exception) { 1281 Log.error("Error reading from file", exception); //$NON-NLS-1$ 1282 return tokenContent.toString(); 1283 } 1284 } 1285 1286 /** 1287 * This method should be called after an initial substitution character has been found (that 1288 * is, after a $). It looks at the subsequent characters in the input stream to determine if 1289 * they match the expected <code>plugin:</code> segment of the substitution string. If the 1290 * expected characters are found, they will be appended to the tokenContent StringBuilder and 1291 * the method will return true. If they are not found, false is returned and the caller 1292 * should reset the BufferedReader to the position it was in before this method was called. 1293 * 1294 * Resetting the reader ensures that the characters read in this method can be re-examined 1295 * in case one of them happens to be the beginning of a valid substitution segment. 1296 * 1297 * @return true if the next characters match <code>plugin:</code>, and false otherwise. 1298 */ findValidPluginSegment()1299 private boolean findValidPluginSegment() { 1300 final char[] PLUGIN_SEGMENT = { 'p', 'l', 'u', 'g', 'i', 'n', ':' }; 1301 char[] streamContent = new char[PLUGIN_SEGMENT.length]; 1302 try { 1303 int peek = reader.read(streamContent, 0, PLUGIN_SEGMENT.length); 1304 if ((peek == PLUGIN_SEGMENT.length) 1305 && (HTMLUtil.equalCharArrayContent(streamContent, PLUGIN_SEGMENT))) { 1306 // we have found the "$plugin:" segment 1307 tokenContent.append(streamContent); 1308 return true; 1309 } 1310 // The "plugin:" segment did not immediately follow the initial 1311 // $. 1312 return false; 1313 } catch (IOException exception) { 1314 Log.error("Error reading from file", exception); //$NON-NLS-1$ 1315 return false; 1316 } 1317 } 1318 1319 /** 1320 * This method continues to read from the input stream until either the end of the file is 1321 * reached, or until a character is found that indicates the end of the substitution. If the 1322 * SUBSTITUTION_END character is found, the method looks up the plugin id that has been 1323 * built up to see if it is a valid id. If so, return the absolute path to that plugin. If 1324 * not, return null. 1325 * 1326 * This method assumes that the reader is positioned just after a valid <code>plugin:</code> 1327 * segment in a substitution string. 1328 * 1329 * @return absolute path of the plugin id, if valid. null otherwise 1330 */ getPluginPath()1331 private String getPluginPath() { 1332 try { 1333 while (true) { 1334 int nextChar = reader.read(); 1335 1336 if (nextChar == -1) { 1337 // reached EOF while looking for closing $ 1338 return null; 1339 } else if (nextChar == SUBSTITUTION_END) { // end of plugin 1340 // id 1341 // look up the plugin id. If it is a valid id 1342 // return the absolute path to this plugin. 1343 // otherwise return null. 1344 String path = BundleUtil.getResolvedBundleLocation(pluginId.toString()); 1345 1346 // If the plugin id was not valid, reset reader to the 1347 // previous mark. The mark should be at the character 1348 // just before the last dollar sign. 1349 if (path == null) 1350 reader.reset(); 1351 1352 return path; 1353 } else { // we have a regular character 1354 // mark the most recent non-dollar char in case we don't 1355 // find a valid plugin id and have to roll back 1356 // Use 1024 as our readAheadLimit 1357 reader.mark(0x400); 1358 // Add this character to the pluginId and tokenContent 1359 // String. 1360 // make sure we have a valid character before performing 1361 // cast 1362 if (nextChar > 0x00 && nextChar < 0xffff) { 1363 tokenContent.append((char) nextChar); 1364 // only include non-whitespace characters in plugin 1365 // id 1366 if (!Character.isWhitespace((char) nextChar)) 1367 pluginId.append((char) nextChar); 1368 } else { 1369 tokenContent.append(nextChar); 1370 pluginId.append(nextChar); 1371 } 1372 } 1373 } 1374 } catch (IOException exception) { 1375 Log.error("Error reading from file", exception); //$NON-NLS-1$ 1376 return null; 1377 } 1378 } 1379 } 1380 1381 } 1382