/******************************************************************************* * Copyright (c) 2007, 2015 David Green and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * David Green - initial API and implementation *******************************************************************************/ package org.eclipse.mylyn.wikitext.splitter; import static com.google.common.base.Preconditions.checkState; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.List; import org.eclipse.mylyn.wikitext.parser.Attributes; import org.eclipse.mylyn.wikitext.parser.DocumentBuilder; import org.eclipse.mylyn.wikitext.parser.ImageAttributes; import org.eclipse.mylyn.wikitext.parser.LinkAttributes; import org.eclipse.mylyn.wikitext.parser.Locator; import org.eclipse.mylyn.wikitext.parser.TableAttributes; import org.eclipse.mylyn.wikitext.parser.TableCellAttributes; import org.eclipse.mylyn.wikitext.parser.builder.HtmlDocumentBuilder; import org.eclipse.mylyn.wikitext.parser.builder.HtmlDocumentBuilder.Stylesheet; import org.eclipse.mylyn.wikitext.parser.outline.OutlineItem; /** * @author David Green * @since 3.0 */ public class SplittingHtmlDocumentBuilder extends DocumentBuilder { private SplitOutlineItem outline; private HtmlDocumentBuilder rootBuilder; private File rootFile; private boolean formatting; private HtmlDocumentBuilder out; private Writer writer; private File currentFile; private boolean navigationImages; private String navigationImagePath = "images"; //$NON-NLS-1$ private boolean embeddedTableOfContents; public void setRootBuilder(HtmlDocumentBuilder rootBuilder) { this.rootBuilder = rootBuilder; out = rootBuilder; } public HtmlDocumentBuilder getRootBuilder() { return rootBuilder; } @Override public void beginDocument() { if (rootBuilder == null || out == null || rootFile == null) { throw new IllegalStateException(); } currentFile = rootFile; out.beginDocument(); documentHeader(); } @Override public void acronym(String text, String definition) { out.acronym(text, definition); } public void addCssStylesheet(Stylesheet stylesheet) { out.addCssStylesheet(stylesheet); } @Override public void beginBlock(BlockType type, Attributes attributes) { out.beginBlock(type, attributes); } @Override public void beginHeading(int level, Attributes attributes) { SplitOutlineItem item = outline.getOutlineItemById(attributes.getId()); if (item != null && !currentFile.getName().equals(item.getSplitTarget())) { try { documentFooter(); out.endDocument(); if (writer != null) { writer.close(); writer = null; } currentFile = new File(rootFile.getParent(), item.getSplitTarget()); writer = new OutputStreamWriter(new FileOutputStream(currentFile), "UTF-8"); //$NON-NLS-1$ HtmlDocumentBuilder builder = new HtmlDocumentBuilder(writer, formatting); rootBuilder.copyConfiguration(builder); if (item.getLabel() != null) { String title = rootBuilder.getTitle(); if (title == null) { title = item.getLabel(); } else { title += " - " + item.getLabel(); //$NON-NLS-1$ } builder.setTitle(title); } out = builder; out.beginDocument(); documentHeader(); } catch (IOException e) { throw new RuntimeException(e); } } out.beginHeading(level, attributes); } private void documentFooter() { emitEmbeddedTableOfContentsFooter(); emitNavigation(false); } private void emitNavigation(boolean header) { String currentName = currentFile.getName(); List pageOrder = outline.getPageOrder(); SplitOutlineItem previous = null; SplitOutlineItem next = null; SplitOutlineItem current = null; boolean foundPage = false; for (SplitOutlineItem page : pageOrder) { if (page.getSplitTarget().equals(currentName)) { foundPage = true; current = page; } else if (!foundPage) { previous = page; } else { next = page; break; } } boolean rootPage = rootFile.getName().equals(currentFile.getName()); if (next == null && previous == null && rootPage) { return; } if (!header) { out.charactersUnescaped("
"); //$NON-NLS-1$ } TableAttributes tableAttributes = new TableAttributes(); tableAttributes.setCssClass("navigation"); //$NON-NLS-1$ tableAttributes.setCssStyle("width: 100%;"); //$NON-NLS-1$ tableAttributes.setBorder("0"); //$NON-NLS-1$ tableAttributes.setSummary("navigation"); //$NON-NLS-1$ out.beginBlock(BlockType.TABLE, tableAttributes); TableCellAttributes tableCellAttributes; if (header) { // header row, emit title of page out.beginBlock(BlockType.TABLE_ROW, new Attributes()); tableCellAttributes = new TableCellAttributes(); tableCellAttributes.setAlign("center"); //$NON-NLS-1$ tableCellAttributes.setCssStyle("width: 100%"); //$NON-NLS-1$ tableCellAttributes.setColspan("3"); //$NON-NLS-1$ out.beginBlock(BlockType.TABLE_CELL_HEADER, tableCellAttributes); if (rootPage) { out.characters(rootBuilder.getTitle()); } else { out.characters(current == null ? "" : current.getLabel()); //$NON-NLS-1$ } out.endBlock(); out.endBlock(); } // navigation row out.beginBlock(BlockType.TABLE_ROW, new Attributes()); LinkAttributes linkAttributes; tableCellAttributes = new TableCellAttributes(); tableCellAttributes.setAlign("left"); //$NON-NLS-1$ tableCellAttributes.setCssStyle("width: 20%"); //$NON-NLS-1$ out.beginBlock(BlockType.TABLE_CELL_NORMAL, tableCellAttributes); if (previous != null) { linkAttributes = new LinkAttributes(); linkAttributes.setTitle(previous.getLabel()); if (navigationImages) { ImageAttributes imageAttributes = new ImageAttributes(); imageAttributes.setAlt(Messages.getString("SplittingHtmlDocumentBuilder.Previous")); //$NON-NLS-1$ out.imageLink(linkAttributes, imageAttributes, previous.getSplitTarget(), computeNavImagePath(Messages.getString("SplittingHtmlDocumentBuilder.Previous_Image"))); //$NON-NLS-1$ } else { out.link(linkAttributes, previous.getSplitTarget(), Messages.getString("SplittingHtmlDocumentBuilder.Previous")); //$NON-NLS-1$ } } out.endBlock(); tableCellAttributes = new TableCellAttributes(); tableCellAttributes.setAlign("center"); //$NON-NLS-1$ tableCellAttributes.setCssStyle("width: 60%"); //$NON-NLS-1$ out.beginBlock(BlockType.TABLE_CELL_NORMAL, tableCellAttributes); if (!header && !rootPage) { linkAttributes = new LinkAttributes(); linkAttributes.setTitle(rootBuilder.getTitle()); if (navigationImages) { ImageAttributes imageAttributes = new ImageAttributes(); imageAttributes.setAlt(rootBuilder.getTitle()); out.imageLink(linkAttributes, imageAttributes, rootFile.getName(), computeNavImagePath(Messages.getString("SplittingHtmlDocumentBuilder.Home_Image"))); //$NON-NLS-1$ } else { out.link(linkAttributes, rootFile.getName(), Messages.getString("SplittingHtmlDocumentBuilder.Home")); //$NON-NLS-1$ } } out.endBlock(); tableCellAttributes = new TableCellAttributes(); tableCellAttributes.setAlign("right"); //$NON-NLS-1$ tableCellAttributes.setCssStyle("width: 20%"); //$NON-NLS-1$ out.beginBlock(BlockType.TABLE_CELL_NORMAL, tableCellAttributes); if (next != null) { linkAttributes = new LinkAttributes(); linkAttributes.setTitle(next.getLabel()); if (navigationImages) { ImageAttributes imageAttributes = new ImageAttributes(); imageAttributes.setAlt(Messages.getString("SplittingHtmlDocumentBuilder.Next")); //$NON-NLS-1$ out.imageLink(linkAttributes, imageAttributes, next.getSplitTarget(), computeNavImagePath(Messages.getString("SplittingHtmlDocumentBuilder.Next_Image"))); //$NON-NLS-1$ } else { out.link(linkAttributes, next.getSplitTarget(), Messages.getString("SplittingHtmlDocumentBuilder.Next")); //$NON-NLS-1$ } } out.endBlock(); out.endBlock(); // navigation row // navigation title row out.beginBlock(BlockType.TABLE_ROW, new Attributes()); tableCellAttributes = new TableCellAttributes(); tableCellAttributes.setAlign("left"); //$NON-NLS-1$ tableCellAttributes.setValign("top"); //$NON-NLS-1$ tableCellAttributes.setCssStyle("width: 20%"); //$NON-NLS-1$ out.beginBlock(BlockType.TABLE_CELL_NORMAL, tableCellAttributes); if (previous != null) { out.characters(previous.getLabel()); } out.endBlock(); tableCellAttributes = new TableCellAttributes(); tableCellAttributes.setAlign("center"); //$NON-NLS-1$ tableCellAttributes.setCssStyle("width: 60%"); //$NON-NLS-1$ out.beginBlock(BlockType.TABLE_CELL_NORMAL, tableCellAttributes); out.endBlock(); tableCellAttributes = new TableCellAttributes(); tableCellAttributes.setAlign("right"); //$NON-NLS-1$ tableCellAttributes.setValign("top"); //$NON-NLS-1$ tableCellAttributes.setCssStyle("width: 20%"); //$NON-NLS-1$ out.beginBlock(BlockType.TABLE_CELL_NORMAL, tableCellAttributes); if (next != null) { out.characters(next.getLabel()); } out.endBlock(); out.endBlock(); // navigation title row out.endBlock(); // table if (header) { out.charactersUnescaped("
"); //$NON-NLS-1$ } } private String computeNavImagePath(String imagePath) { if (navigationImagePath != null) { return navigationImagePath + "/" + imagePath; //$NON-NLS-1$ } return imagePath; } public String getNavigationImagePath() { return navigationImagePath; } public void setNavigationImagePath(String navigationImagePath) { this.navigationImagePath = navigationImagePath; if (this.navigationImagePath != null && this.navigationImagePath.endsWith("/")) { //$NON-NLS-1$ this.navigationImagePath = this.navigationImagePath.substring(0, this.navigationImagePath.length() - 1); } } private void documentHeader() { emitNavigation(true); emitEmbeddedTableOfContentsHeader(); } @Override public void beginSpan(SpanType type, Attributes attributes) { if (type == SpanType.LINK && attributes instanceof LinkAttributes) { LinkAttributes linkAttributes = (LinkAttributes) attributes; linkAttributes.setHref(adjustHref(linkAttributes.getHref())); } out.beginSpan(type, attributes); } @Override public void characters(String text) { out.characters(text); } @Override public void charactersUnescaped(String literal) { out.charactersUnescaped(literal); } public void copyConfiguration(HtmlDocumentBuilder other) { out.copyConfiguration(other); } @Override public void endBlock() { out.endBlock(); } @Override public void endDocument() { documentFooter(); out.endDocument(); if (writer != null) { try { writer.close(); } catch (IOException e) { throw new IllegalStateException(); } } out = null; } @Override public void flush() { if (out != null) { out.flush(); } } @Override public void endHeading() { out.endHeading(); } @Override public void endSpan() { out.endSpan(); } @Override public void entityReference(String entity) { out.entityReference(entity); } @Override public Locator getLocator() { return out.getLocator(); } @Override public void image(Attributes attributes, String url) { out.image(attributes, url); } @Override public void imageLink(Attributes linkAttributes, Attributes imageAttributes, String href, String imageUrl) { href = adjustHref(href); out.imageLink(linkAttributes, imageAttributes, href, imageUrl); } private String adjustHref(String href) { if (href != null && href.startsWith("#")) { //$NON-NLS-1$ href = getHrefOfHeading(href.substring(1)); } return href; } private String getHrefOfHeading(String headingId) { SplitOutlineItem target = outline.getOutlineItemById(headingId); if (target != null) { SplitOutlineItem pageTarget = target; while (pageTarget.getParent() != null && pageTarget.getSplitTarget().equals(pageTarget.getParent().getSplitTarget())) { pageTarget = pageTarget.getParent(); } if (!currentFile.getName().equals(pageTarget.getSplitTarget())) { String pageHref = pageTarget.getSplitTarget().replace(" ", "%20"); //$NON-NLS-1$//$NON-NLS-2$ return pageHref + '#' + headingId; } } return '#' + headingId; } @Override public void lineBreak() { out.lineBreak(); } @Override public void horizontalRule() { out.horizontalRule(); } @Override public void link(Attributes attributes, String hrefOrHashName, String text) { hrefOrHashName = adjustHref(hrefOrHashName); out.link(attributes, hrefOrHashName, text); } @Override public void setLocator(Locator locator) { if (out != null) { out.setLocator(locator); } } public void setOutline(SplitOutlineItem outline) { this.outline = outline; } public SplitOutlineItem getOutline() { return outline; } public void setRootFile(File rootFile) { this.rootFile = rootFile; } public void setNavigationImages(boolean navigationImages) { this.navigationImages = navigationImages; } public boolean isNavigationImages() { return navigationImages; } public void setFormatting(boolean formatting) { this.formatting = formatting; } public boolean isFormatting() { return formatting; } public void setEmbeddedTableOfContents(boolean embeddedTableOfContents) { this.embeddedTableOfContents = embeddedTableOfContents; } public boolean isEmbeddedTableOfContents() { return embeddedTableOfContents; } private void emitEmbeddedTableOfContents() { String currentName = currentFile.getName(); SplitOutlineItem pageItem = null; List pageOrder = outline.getPageOrder(); for (SplitOutlineItem item : pageOrder) { if (item.getSplitTarget().equals(currentName)) { pageItem = item; } } checkState(pageItem != null); emitToc(outline, 0); } private void emitToc(OutlineItem item, int level) { if (item.getChildren().isEmpty()) { return; } out.beginBlock(BlockType.NUMERIC_LIST, new Attributes()); for (OutlineItem child : item.getChildren()) { Attributes itemAttributes = new Attributes(); if (isExpandedInTableOfContents(child)) { itemAttributes.appendCssClass("expanded"); //$NON-NLS-1$ } else { itemAttributes.appendCssClass("collapsed"); //$NON-NLS-1$ } out.beginBlock(BlockType.LIST_ITEM, itemAttributes); out.link(getHrefOfHeading(child.getId()), child.getLabel()); emitToc(child, level + 1); out.endBlock(); } out.endBlock(); } private boolean isExpandedInTableOfContents(OutlineItem target) { SplitOutlineItem pageTarget = (SplitOutlineItem) target; while (pageTarget.getParent() != outline && pageTarget.getSplitTarget() == null) { pageTarget = pageTarget.getParent(); } if (pageTarget.getSplitTarget() != null && !currentFile.getName().equals(pageTarget.getSplitTarget())) { return false; } return true; } private void emitEmbeddedTableOfContentsHeader() { if (embeddedTableOfContents) { beginDiv("container"); //$NON-NLS-1$ beginDiv("row"); //$NON-NLS-1$ beginDiv("table-of-contents", "span2"); //$NON-NLS-1$//$NON-NLS-2$ out.beginHeading(1, new Attributes()); out.characters(Messages.getString("SplittingHtmlDocumentBuilder.TableOfContentsHeading")); //$NON-NLS-1$ out.endHeading(); emitEmbeddedTableOfContents(); endDiv(); // table of contents beginDiv("main-content", "span10"); //$NON-NLS-1$//$NON-NLS-2$ } } private void beginDiv(String... cssClasses) { Attributes attributes = new Attributes(); for (String cssClass : cssClasses) { attributes.appendCssClass(cssClass); } out.beginBlock(BlockType.DIV, attributes); } private void endDiv() { out.endBlock(); } private void emitEmbeddedTableOfContentsFooter() { if (embeddedTableOfContents) { endDiv(); // main content endDiv(); // row endDiv(); // container } } }