001/*
002 * Shredzone Commons
003 *
004 * Copyright (C) 2012 Richard "Shred" Körber
005 *   http://commons.shredzone.org
006 *
007 * This program is free software: you can redistribute it and/or modify
008 * it under the terms of the GNU Library General Public License as
009 * published by the Free Software Foundation, either version 3 of the
010 * License, or (at your option) any later version.
011 *
012 * This program is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
015 * GNU General Public License for more details.
016 *
017 * You should have received a copy of the GNU Library General Public License
018 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
019 */
020package org.shredzone.commons.text.filter;
021
022import java.io.Writer;
023
024import edu.umd.cs.findbugs.annotations.Nullable;
025import org.eclipse.mylyn.wikitext.parser.Attributes;
026import org.eclipse.mylyn.wikitext.parser.DocumentBuilder;
027import org.eclipse.mylyn.wikitext.parser.LinkAttributes;
028import org.eclipse.mylyn.wikitext.parser.MarkupParser;
029import org.eclipse.mylyn.wikitext.parser.builder.HtmlDocumentBuilder;
030import org.eclipse.mylyn.wikitext.textile.TextileLanguage;
031import org.shredzone.commons.text.LinkAnalyzer;
032import org.shredzone.commons.text.TextFilter;
033import org.shredzone.commons.text.utils.FastStringWriter;
034
035/**
036 * A filter that converts Textile markup to HTML.
037 * <p>
038 * Currently, Mylyn WikiText Textile (formerly known as Textile-J) is used for conversion.
039 * Future releases may come with an own, lightweight implementation.
040 *
041 * @see <a href="http://wiki.eclipse.org/Mylyn/Incubator/WikiText">Mylyn WikiText</a>
042 * @author Richard "Shred" Körber
043 */
044public class TextileFilter implements TextFilter {
045
046    private @Nullable LinkAnalyzer analyzer;
047
048    /**
049     * Sets a {@link LinkAnalyzer} to be used for converting links and image source URLs.
050     *
051     * @param analyzer
052     *            {@link LinkAnalyzer} to be used
053     */
054    public void setAnalyzer(@Nullable LinkAnalyzer analyzer) {
055        this.analyzer = analyzer;
056    }
057
058    /**
059     * Creates a Textile-j {@link DocumentBuilder} to be used for writing.
060     * <p>
061     * Note that this method is Textile-j specific and might be removed in future
062     * versions.
063     *
064     * @param writer
065     *            {@link Writer} to write the HTML output to
066     * @return {@link DocumentBuilder} to be used for the markup parser
067     */
068    protected DocumentBuilder createDocumentBuilder(Writer writer) {
069        if (analyzer != null) {
070            return new LinkAnalyzingHtmlDocumentBuilder(writer, analyzer);
071        } else {
072            HtmlDocumentBuilder builder = new HtmlDocumentBuilder(writer);
073            builder.setEmitAsDocument(false);
074            return builder;
075        }
076    }
077
078    @Override
079    public CharSequence apply(CharSequence text) {
080        FastStringWriter writer = new FastStringWriter(text.length() * 15 / 10);
081
082        MarkupParser parser = new MarkupParser(new TextileLanguage());
083        parser.setBuilder(createDocumentBuilder(writer));
084        parser.parse(text.toString());
085
086        return writer.toStringBuilder();
087    }
088
089    /**
090     * A {@link HtmlDocumentBuilder} that uses a {@link LinkAnalyzer}.
091     */
092    private static class LinkAnalyzingHtmlDocumentBuilder extends HtmlDocumentBuilder {
093        private final LinkAnalyzer analyzer;
094
095        public LinkAnalyzingHtmlDocumentBuilder(Writer writer, LinkAnalyzer analyzer) {
096            super(writer);
097            this.analyzer = analyzer;
098            setEmitAsDocument(false);
099        }
100
101        @Override
102        public void beginSpan(SpanType type, Attributes attributes) {
103            if (type.equals(SpanType.LINK) && attributes instanceof LinkAttributes) {
104                LinkAttributes la = (LinkAttributes) attributes;
105
106                la.setHref(analyzer.linkUrl(la.getHref()));
107                String linkType = analyzer.linkType(la.getHref());
108                if (linkType != null) {
109                    la.setCssClass(linkType);
110                }
111            }
112            super.beginSpan(type, attributes);
113        }
114
115        @Override
116        public void imageLink(Attributes linkAttributes, Attributes imageAttributes, String href, String imageUrl) {
117            String resolvedHref = analyzer.linkUrl(href);
118            String resolvedImageUrl = analyzer.imageUrl(imageUrl);
119            super.imageLink(linkAttributes, imageAttributes, resolvedHref, resolvedImageUrl);
120        }
121
122        @Override
123        public void image(Attributes attributes, String url) {
124            super.image(attributes, analyzer.imageUrl(url));
125        }
126    }
127
128}