org.xmind.ui.internal.notes.RichDocumentBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.xmind.ui.internal.notes.RichDocumentBuilder.java

Source

/* ******************************************************************************
 * Copyright (c) 2006-2012 XMind Ltd. and others.
 * 
 * This file is a part of XMind 3. XMind releases 3 and
 * above are dual-licensed under the Eclipse Public License (EPL),
 * which is available at http://www.eclipse.org/legal/epl-v10.html
 * and the GNU Lesser General Public License (LGPL), 
 * which is available at http://www.gnu.org/licenses/lgpl.html
 * See http://www.xmind.net/license.html for details.
 * 
 * Contributors:
 *     XMind Ltd. - initial API and implementation
 *******************************************************************************/
package org.xmind.ui.internal.notes;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;

import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.GlyphMetrics;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.xmind.core.IHtmlNotesContent;
import org.xmind.core.IHyperlinkSpan;
import org.xmind.core.IImageSpan;
import org.xmind.core.INotesContent;
import org.xmind.core.IParagraph;
import org.xmind.core.IPlainNotesContent;
import org.xmind.core.ISpan;
import org.xmind.core.ITextSpan;
import org.xmind.core.internal.dom.NumberUtils;
import org.xmind.core.style.IStyle;
import org.xmind.core.style.IStyled;
import org.xmind.ui.resources.ColorUtils;
import org.xmind.ui.resources.FontUtils;
import org.xmind.ui.richtext.Hyperlink;
import org.xmind.ui.richtext.IRichDocument;
import org.xmind.ui.richtext.ImagePlaceHolder;
import org.xmind.ui.richtext.LineStyle;
import org.xmind.ui.richtext.RichDocument;
import org.xmind.ui.richtext.RichTextUtils;
import org.xmind.ui.style.StyleUtils;
import org.xmind.ui.style.Styles;

public class RichDocumentBuilder {

    private static boolean DEBUG = false;

    private RichDocumentNotesAdapter adapter;

    private IRichDocument result;

    private StringBuilder totalText = new StringBuilder();

    private List<StyleRange> textStyles = new ArrayList<StyleRange>();

    private List<LineStyle> lineStyles = new ArrayList<LineStyle>();

    private List<ImagePlaceHolder> images = new ArrayList<ImagePlaceHolder>();

    private List<Hyperlink> hyperlinks = new ArrayList<Hyperlink>();

    private int lineIndex = 0;

    private int totalOffset = 0;

    private Stack<LineStyle> lineStyleStack = new Stack<LineStyle>();

    private Stack<StyleRange> textStyleStack = new Stack<StyleRange>();

    private StringBuilder currentLine = null;

    private StyleRange lastTextStyle = null;

    public RichDocumentBuilder(RichDocumentNotesAdapter adapter) {
        this.adapter = adapter;
    }

    public void build(INotesContent content) {
        if (content == null) {
            if (DEBUG)
                System.out.println("empty content"); //$NON-NLS-1$
            result = new RichDocument();
            return;
        }

        if (content instanceof IPlainNotesContent) {
            String text = ((IPlainNotesContent) content).getTextContent();
            if (text == null || "".equals(text)) { //$NON-NLS-1$
                if (DEBUG)
                    System.out.println("empty plain content"); //$NON-NLS-1$
                result = new RichDocument();
                return;
            }
            if (DEBUG)
                System.out.println("plain content"); //$NON-NLS-1$
            result = new RichDocument(text);
            return;
        }

        if (!(content instanceof IHtmlNotesContent)) {
            if (DEBUG)
                System.out.println("unknown content format"); //$NON-NLS-1$
            result = new RichDocument();
            return;
        }

        IHtmlNotesContent html = (IHtmlNotesContent) content;

        //        if (DEBUG)
        //            System.out.println(content);
        //        content = DOMUtils.makeElementText(content, NS.XMAP,
        //                WorkbookUtils.TAG_RICH_CONTENT, NS.Xhtml, NS.Xlink, NS.SVG);
        //        Document doc;
        //        try {
        //            doc = DOMUtils.loadDocument(content.getBytes());
        //        } catch (Throwable e) {
        //            if (DEBUG) {
        //                e.printStackTrace();
        //            } else {
        //                Logger.log(e, "Failed to parse html notes"); //$NON-NLS-1$
        //            }
        //            result = new RichDocument();
        //            return;
        //        }

        readContent(html);
        //        
        //        readElement(doc.getDocumentElement());
        if (endsWith(totalText, NotesConstants.LINE_DELIMITER)) {
            if (DEBUG) {
                System.out.println("-- delete last line delimiter -- "); //$NON-NLS-1$
            }
            deleteLastLineDelimiter();
        }
        result = new RichDocument(totalText.toString());
        result.setTextStyles(textStyles.toArray(new StyleRange[textStyles.size()]));
        result.setLineStyles(lineStyles.toArray(new LineStyle[lineStyles.size()]));
        result.setImages(images.toArray(new ImagePlaceHolder[images.size()]));
        result.setHyperlinks(hyperlinks.toArray(new Hyperlink[hyperlinks.size()]));

        if (DEBUG) {
            System.out.println(result.get());
            System.out.println(Arrays.toString(result.getTextStyles()));
            System.out.println(Arrays.toString(result.getLineStyles()));
            System.out.println(Arrays.toString(result.getImages()));
            System.out.println(Arrays.toString(result.getHyperlinks()));
        }
    }

    private boolean endsWith(StringBuilder sb, String s) {
        if (s.length() == 0 || sb.length() < s.length())
            return false;

        for (int i = 0; i < s.length(); i++) {
            char c = sb.charAt(sb.length() - i - 1);
            char c2 = s.charAt(s.length() - i - 1);
            if (c != c2)
                return false;
        }
        return true;
    }

    private void deleteLastLineDelimiter() {
        int length = NotesConstants.LENGTH_DELIMITER;
        totalText.delete(totalText.length() - length, totalText.length());
        if (lastTextStyle != null && lastTextStyle.start + lastTextStyle.length == totalOffset) {
            if (lastTextStyle.length <= length) {
                textStyles.remove(lastTextStyle);
            } else {
                lastTextStyle.length -= length;
            }
        }
    }

    private void readContent(IHtmlNotesContent html) {
        for (IParagraph p : html.getParagraphs()) {
            readParagraph(p);
        }
    }

    private void readParagraph(IParagraph p) {
        if (currentLine != null) {
            endLine();
        }
        if (DEBUG) {
            System.out.println("start line: " + lineIndex); //$NON-NLS-1$
        }
        LineStyle currentLineStyle = createLineStyle(p);
        if (currentLineStyle != null) {
            currentLineStyle.lineIndex = lineIndex;
            lineStyles.add(currentLineStyle);
            if (DEBUG)
                System.out.println("line style added: " + currentLineStyle); //$NON-NLS-1$
        }
        lineStyleStack.push(currentLineStyle);
        if (DEBUG)
            System.out.println("line style pushed: " + currentLineStyle); //$NON-NLS-1$
        currentLine = new StringBuilder();
        readParagraphContent(p);
        endLine();
        lineStyleStack.pop();
    }

    private LineStyle createLineStyle(IParagraph p) {
        LineStyle currentLineStyle = newLineStyle();
        IStyle style = getStyle(p);
        if (style != null) {
            String alignment = style.getProperty(Styles.TextAlign);
            if (DEBUG) {
                System.out.println("alingment: " + alignment); //$NON-NLS-1$
            }
            if (alignment != null) {
                currentLineStyle.alignment = toSWTAlignment(alignment);
            }
            //            String bullet = style.getProperty(Styles.TextBullet);
            //            if (DEBUG)
            //                System.out.println("bullet: " + bullet); //$NON-NLS-1$
            //            if (bullet != null) {
            //                boolean isBullet = Styles.TEXT_STYLE_BULLET.equals(bullet) ? true
            //                        : false;
            //                currentLineStyle.bullet = isBullet;
            //            }
            String bulletStyle = style.getProperty(Styles.TextBullet);
            if (DEBUG)
                System.out.println("bulletStyle: " + bulletStyle); //$NON-NLS-1$
            if (bulletStyle != null) {
                currentLineStyle.bulletStyle = getBulletStyle(bulletStyle);
            }
        } else if (DEBUG) {
            System.out.println("no line style"); //$NON-NLS-1$
        }
        return currentLineStyle;
    }

    private String getBulletStyle(String bulletStyle) {
        if (LineStyle.BULLET.equals(bulletStyle))
            return LineStyle.BULLET;
        else if (LineStyle.NUMBER.equals(bulletStyle))
            return LineStyle.NUMBER;
        return LineStyle.NONE_STYLE;
    }

    private void readParagraphContent(IParagraph p) {
        for (ISpan span : p.getSpans()) {
            if (span instanceof IImageSpan) {
                readImage((IImageSpan) span);
            } else if (span instanceof ITextSpan) {
                readText((ITextSpan) span);
            } else if (span instanceof IHyperlinkSpan) {
                readHyperlink((IHyperlinkSpan) span);
            }
        }
    }

    //    private void readElementChildren(Element ele) {
    //        NodeList children = ele.getChildNodes();
    //        for (int i = 0; i < children.getLength(); i++) {
    //            Node child = children.item(i);
    //            short nodeType = child.getNodeType();
    //            if (nodeType == Node.ELEMENT_NODE) {
    //                readElement((Element) child);
    //            } else if (nodeType == Node.TEXT_NODE) {
    //                readText((Text) child);
    //            }
    //        }
    //    }

    private void endLine() {
        if (DEBUG) {
            System.out.println("end line: " + lineIndex); //$NON-NLS-1$
        }
        sumLineIndent();
        appendLineDelimiter();
        lineIndex++;
        currentLine = null;
    }

    private void ensureLineStart() {
        if (currentLine != null)
            return;

        LineStyle currentLineStyle = newLineStyle();
        currentLineStyle.lineIndex = lineIndex;
        if (!lineStyleStack.isEmpty())
            lineStyleStack.pop();
        lineStyleStack.push(currentLineStyle);
        currentLine = new StringBuilder();
    }

    private void appendLineDelimiter() {
        totalText.append(NotesConstants.LINE_DELIMITER);
        if (lastTextStyle != null && lastTextStyle.start + lastTextStyle.length == totalOffset) {
            lastTextStyle.length += NotesConstants.LENGTH_DELIMITER;
        }
        totalOffset += NotesConstants.LENGTH_DELIMITER;
    }

    private void sumLineIndent() {
        if (currentLine == null)
            return;

        if (lineStyleStack.isEmpty())
            return;

        int indent = calcIndentCount(currentLine);
        LineStyle lastLineStyle = lineStyleStack.peek();
        lastLineStyle.indent = indent;
    }

    private LineStyle newLineStyle() {
        LineStyle lastLineStyle = lineStyleStack.isEmpty() ? null : lineStyleStack.peek();
        if (DEBUG) {
            System.out.println("last line style: " + lastLineStyle); //$NON-NLS-1$
        }
        if (lastLineStyle == null)
            return (LineStyle) RichTextUtils.DEFAULT_LINE_STYLE.clone();
        return (LineStyle) lastLineStyle.clone();
    }

    private StyleRange newTextStyle() {
        StyleRange lastTextStyle = textStyleStack.isEmpty() ? null : textStyleStack.peek();
        if (lastTextStyle == null)
            return null;
        return (StyleRange) lastTextStyle.clone();
    }

    private void readImage(IImageSpan span) {
        String uri = span.getSource();//DOMUtils.getAttribute(ele, DOMConstants.ATTR_SRC);
        if (uri != null) {
            Image image = adapter.getImageFromUrl(uri);
            if (image != null) {
                ensureLineStart();
                String s = ImagePlaceHolder.PLACE_HOLDER;
                int length = s.length();
                totalText.append(s);
                currentLine.append(s);
                StyleRange style = (StyleRange) RichTextUtils.DEFAULT_STYLE.clone();
                style.start = totalOffset;
                style.length = length;
                //                style.data = image;
                Rectangle rect = image.getBounds();
                style.metrics = new GlyphMetrics(rect.height, 0, rect.width);
                ImagePlaceHolder imagePlaceHolder = new ImagePlaceHolder(totalOffset, image);
                images.add(imagePlaceHolder);

                if (!RichTextUtils.merge(style, lastTextStyle)) {
                    textStyles.add(style);
                    lastTextStyle = style;
                }
                totalOffset += length;
            }
        }
    }

    private void readHyperlink(IHyperlinkSpan span) {
        String urlString = span.getHref();
        int start = totalOffset;
        for (ISpan s : span.getSpans()) {
            if (s instanceof ITextSpan) {
                readText((ITextSpan) s);
            } else if (s instanceof IImageSpan) {
                readImage((IImageSpan) s);
            }
        }
        int length = totalOffset - start;
        Hyperlink hyperlink = new Hyperlink(start, length, urlString);
        hyperlinks.add(hyperlink);
    }

    private void readText(ITextSpan span) {
        StyleRange textStyle = createTextStyle(getStyle(span));
        textStyleStack.push(textStyle);
        appendText(span.getTextContent(), textStyle);
        //readElementChildren(ele);
        textStyleStack.pop();
    }

    private void appendText(String text, StyleRange textStyle) {
        ensureLineStart();
        int length = text.length();
        totalText.append(text);
        currentLine.append(text);
        if (textStyle != null) {
            textStyle.start = totalOffset;
            textStyle.length = length;
            // Merge with last style range if possible
            if (!RichTextUtils.merge(textStyle, lastTextStyle)) {
                textStyles.add(textStyle);
                lastTextStyle = textStyle;
            }
        }
        totalOffset += length;
    }

    //    private void readText(Text text) {
    //        appendText(text.getTextContent(), newTextStyle());
    //    }

    private StyleRange createTextStyle(IStyle style) {
        StyleRange textStyle = newTextStyle();
        if (style == null)
            return textStyle;

        String name = style.getProperty(Styles.FontFamily);
        String availableFontName = FontUtils.getAAvailableFontNameFor(name);
        name = availableFontName != null ? availableFontName : name;

        if (Styles.SYSTEM.equals(name)) {
            name = JFaceResources.getDefaultFont().getFontData()[0].getName();
        }
        String height = style.getProperty(Styles.FontSize);
        String weight = style.getProperty(Styles.FontWeight);
        String fontStyle = style.getProperty(Styles.FontStyle);
        String foreground = style.getProperty(Styles.TextColor);
        String background = style.getProperty(Styles.BackgroundColor);
        String decoration = style.getProperty(Styles.TextDecoration);
        if (name == null && height == null && weight == null && fontStyle == null && foreground == null
                && background == null && decoration == null)
            return textStyle;

        if (name == null)
            name = RichTextUtils.DEFAULT_FONT_DATA.getName();
        int size = NumberUtils.safeParseInt(StyleUtils.trimNumber(height),
                RichTextUtils.DEFAULT_FONT_DATA.getHeight());
        boolean bold = weight != null && weight.contains(Styles.FONT_WEIGHT_BOLD);
        boolean italic = fontStyle != null && fontStyle.contains(Styles.FONT_STYLE_ITALIC);

        if (textStyle == null)
            textStyle = new StyleRange();
        textStyle.font = FontUtils.getFont(name, size, bold, italic);
        textStyle.foreground = ColorUtils.getColor(foreground);
        textStyle.background = ColorUtils.getColor(background);
        textStyle.underline = decoration != null && decoration.contains(Styles.TEXT_DECORATION_UNDERLINE);
        textStyle.strikeout = decoration != null && decoration.contains(Styles.TEXT_DECORATION_LINE_THROUGH);
        return textStyle;
    }

    private int toSWTAlignment(String value) {
        if (Styles.ALIGN_CENTER.equals(value))
            return SWT.CENTER;
        if (Styles.ALIGN_RIGHT.equals(value))
            return SWT.RIGHT;
        return SWT.LEFT;
    }

    private int calcIndentCount(StringBuilder line) {
        int indent = 0;
        for (int i = 0; i < line.length(); i++) {
            char c = line.charAt(i);
            if (c != '\t') {
                return indent;
            }
            indent++;
        }
        return indent;
    }

    private IStyle getStyle(IStyled styled) {
        String styleId = styled.getStyleId();
        if (styleId != null) {
            return adapter.getWorkbook().getStyleSheet().findStyle(styleId);
        }
        return null;
    }

    public IRichDocument getResult() {
        return result;
    }

}