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 	 *                        &lt;HTML&gt;
129 	 *                        &lt;HEAD&gt;
130 	 *                        head content
131 	 *                        &lt;/HEAD&gt;
132 	 *                        &lt;BODY&gt;
133 	 *                        body content
134 	 *                        &lt;/BODY&gt;
135 	 *                        &lt;/HTML&gt;
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 	 *                        &lt;HEAD&gt;
159 	 *                        &lt;BASE href=&quot;base_plugin_location&gt;
160 	 *                        &lt;style type=&quot;text/css&quot;&gt;HTML, IMG { border: 0px; } &lt;/style&gt;
161 	 *                        &lt;TITLE&gt;page title &lt;/TITLE&gt;
162 	 *                        &lt;LINK href=&quot;style sheet&quot;&gt;
163 	 *                        additional head content, if specified
164 	 *                        &lt;/HEAD&gt;
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 	 *                        &lt;BODY&gt;
283 	 *                        &lt;DIV id=&quot;pageId&quot; class=&quot;pageClass&quot;&gt;
284 	 *                        page content
285 	 *                        &lt;/DIV&gt;
286 	 *                        &lt;/BODY&gt;
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 	 *                        &lt;div id=&quot;attrvalue&quot;&gt;
368 	 *                        &lt;h4&gt;&lt;span class=&quot;div-label&quot;&gt;attrvalue&lt;/span&gt;&lt;h4&gt;
369 	 *                        any defined divs, links, html, images, text, includes
370 	 *                        &lt;/div&gt;
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 	 *                        &lt;A id=linkId class=&quot;link&quot; href=linkHref&gt;
491 	 *                        &lt;IMG src=&quot;blank.gif&quot;&gt;
492 	 *                        &lt;SPAN class=&quot;link-label&quot;&gt;linkLabel &lt;/SPAN&gt;
493 	 *                        &lt;P&gt;&lt;SPAN&gt;text&lt;/SPAN&gt;&lt;/P&gt;
494 	 *                        &lt;/A&gt;
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 (&lt;A&gt;) 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 	 *                        &lt;IMG src=imageSrc id=imageId&gt;
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 (&lt;P&gt;) 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 	 *                        &lt;P&gt;&lt;SPAN&gt;spanContent&lt;/SPAN&gt;&lt;/P&gt;
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 	 *                        &lt;div id=&quot;attrvalue&quot; class=&quot;attrvalue2&quot;&gt;
670 	 *                        content from file specified in src attribute
671 	 *                        &lt;/div&gt;
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 	 *                        &lt;OBJECT type=&quot;text/html&quot; data=&quot;attrvalue&quot;&gt;
749 	 *                        alternative text in case the object can not be rendered
750 	 *                        &lt;/OBJECT&gt;
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 	 *                      	&lt;BASE href=baseURL&gt;
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 	 *                        &lt;style type=&quot;text/css&quot;&gt;HTML, IMG { border: 0px; } &lt;/style&gt;
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 	 *                        &lt;TITLE&gt;intro title&lt;/TITLE&gt;
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 	 *                        &lt;LINK rel=&quot;stylesheet&quot; style=&quot;text/css&quot; href=&quot;style sheet&quot;&gt;
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 	 *                        &lt;A id=linkId class=linkClass href=linkHref&gt; &lt;/A&gt;
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 (&lt;A&gt;) 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 	 *                        &lt;DIV id=divId&gt;
920 	 *                        &lt;H&gt;&lt;SPAN&gt;spanContent &lt;/SPAN&gt; &lt;/H&gt;
921 	 *                        &lt;/DIV&gt;
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 	 *                        &lt;P&gt;&lt;SPAN&gt;spanContent&lt;/SPAN&gt;&lt;/P&gt;
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 	 *                        &lt;IMG src=imageSrc alt=altText&gt;
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 	 *                        &lt;SPAN class=spanClass&gt; &lt;/SPAN&gt;
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 	 *                     &lt;iframe src=&quot;localPage1.xhtml&quot; frameborder=&quot;1&quot; scrolling=&quot;auto&quot; longdesc=&quot;localPage1.xhtml&quot;&gt;
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