de.micromata.genome.gwiki.utils.html.Html2WikiFilter.java Source code

Java tutorial

Introduction

Here is the source code for de.micromata.genome.gwiki.utils.html.Html2WikiFilter.java

Source

//
// Copyright (C) 2010-2016 Roger Rene Kommer & Micromata GmbH
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package de.micromata.genome.gwiki.utils.html;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections4.ArrayStack;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.xerces.xni.Augmentations;
import org.apache.xerces.xni.QName;
import org.apache.xerces.xni.XMLAttributes;
import org.apache.xerces.xni.XMLString;
import org.apache.xerces.xni.XNIException;
import org.apache.xerces.xni.parser.XMLDocumentFilter;
import org.apache.xerces.xni.parser.XMLInputSource;
import org.apache.xerces.xni.parser.XMLParserConfiguration;
import org.cyberneko.html.HTMLConfiguration;
import org.cyberneko.html.filters.DefaultFilter;

import de.micromata.genome.gwiki.model.GWikiElementInfo;
import de.micromata.genome.gwiki.page.GWikiContext;
import de.micromata.genome.gwiki.page.impl.wiki.GWikiBodyEvalMacro;
import de.micromata.genome.gwiki.page.impl.wiki.GWikiBodyMacro;
import de.micromata.genome.gwiki.page.impl.wiki.GWikiMacroClassFactory;
import de.micromata.genome.gwiki.page.impl.wiki.GWikiMacroFactory;
import de.micromata.genome.gwiki.page.impl.wiki.GWikiMacroFragment;
import de.micromata.genome.gwiki.page.impl.wiki.GWikiMacroRenderFlags;
import de.micromata.genome.gwiki.page.impl.wiki.GWikiMacroRte;
import de.micromata.genome.gwiki.page.impl.wiki.MacroAttributes;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragment;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragmentBr;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragmentBrInLine;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragmentChildContainer;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragmentFixedFont;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragmentHeading;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragmentHr;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragmentImage;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragmentLi;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragmentLink;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragmentList;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragmentP;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragmentTable;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragmentText;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragmentTextDeco;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiFragmentVisitor;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiNestableFragment;
import de.micromata.genome.gwiki.page.impl.wiki.fragment.GWikiSimpleFragmentVisitor;
import de.micromata.genome.gwiki.page.impl.wiki.macros.GWikiHtmlBodyPTagMacro;
import de.micromata.genome.gwiki.page.impl.wiki.macros.GWikiHtmlBodyTagMacro;
import de.micromata.genome.gwiki.page.impl.wiki.macros.GWikiHtmlTagMacro;
import de.micromata.genome.gwiki.page.impl.wiki.macros.GWikiTextFormatMacro;
import de.micromata.genome.gwiki.page.impl.wiki.parser.GWikiWikiParserContext;
import de.micromata.genome.gwiki.utils.StringUtils;

/**
 * Filter to transform HTML to Wiki syntax.
 * 
 * @author Roger Rene Kommer (r.kommer@micromata.de)
 * 
 */
public class Html2WikiFilter extends DefaultFilter {

    protected GWikiWikiParserContext parseContext = new GWikiWikiParserContext();

    private Set<String> supportedHtmlTags = new HashSet<String>();

    /**
     * Tags, which may not be closed in HTML code.
     */
    private String[] autoCloseTags = new String[] { "hr", "br" };

    private static final String DEFAULT_SPECIAL_CHARACTERS = "*-_~^+{}[]!#|\\";

    /**
     * Character, which has to be escaped.
     */
    protected String specialCharacters = DEFAULT_SPECIAL_CHARACTERS;

    private boolean ignoreWsNl = true;

    private ArrayStack<GWikiFragment> autoCloseTagStack = new ArrayStack<GWikiFragment>();

    protected ArrayStack<String> liStack = new ArrayStack<String>();

    public static Map<String, String> DefaultSimpleTextDecoMap = new HashMap<String, String>();

    public static Map<String, String> DefaultWiki2HtmlTextDecoMap = new HashMap<String, String>();

    public static Map<String, GWikiMacroFactory> TextDecoMacroFactories = new HashMap<String, GWikiMacroFactory>();
    static {

        DefaultSimpleTextDecoMap.put("b", "*");
        DefaultSimpleTextDecoMap.put("strong", "*");
        DefaultSimpleTextDecoMap.put("em", "_");
        DefaultSimpleTextDecoMap.put("i", "_");
        DefaultSimpleTextDecoMap.put("del", "-");
        DefaultSimpleTextDecoMap.put("strike", "-");
        DefaultSimpleTextDecoMap.put("sub", "~");
        DefaultSimpleTextDecoMap.put("sup", "^");
        DefaultSimpleTextDecoMap.put("u", "+");
        for (Map.Entry<String, String> me : DefaultSimpleTextDecoMap.entrySet()) {
            DefaultWiki2HtmlTextDecoMap.put(me.getValue(), me.getKey());
            TextDecoMacroFactories.put(me.getValue(), new GWikiMacroClassFactory(GWikiTextFormatMacro.class));
        }

    }

    private Map<String, String> simpleTextDecoMap = DefaultSimpleTextDecoMap;

    private List<Html2WikiTransformer> macroTransformer = new ArrayList<Html2WikiTransformer>();

    /**
     * because character will be intercepted if an entity like &auml; is in the character text, this will be used to
     * collect all characters before parsing it.
     */
    protected StringBuilder collectedText = new StringBuilder();

    // protected boolean in
    public static String html2Wiki(String text) {
        Set<String> s = Collections.emptySet();
        return html2Wiki(text, s);
    }

    public static String html2Wiki(String text, Set<String> htmlMacroTags) {
        Html2WikiFilter nf = new Html2WikiFilter();
        nf.getSupportedHtmlTags().addAll(htmlMacroTags);
        return nf.transform(text);
    }

    public String transform(String text) {
        parseContext.pushFragList();
        XMLParserConfiguration parser = new HTMLConfiguration();
        parser.setProperty("http://cyberneko.org/html/properties/filters", new XMLDocumentFilter[] { this });
        text = StringUtils.defaultString(text);
        XMLInputSource source = new XMLInputSource(null, null, null, new StringReader(text), "UTF-8");
        try {
            parser.parse(source);
            GWikiFragmentChildContainer cont = new GWikiFragmentChildContainer(parseContext.popFragList());
            GWikiFragmentVisitor visitor = createVisitor();
            cont.iterate(visitor, null);
            return cont.getSource();
            // return nf.resultText.toString();
        } catch (RuntimeException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    protected GWikiFragmentVisitor createVisitor() {
        return new Html2WikiFragmentVisitor();
    }

    protected boolean isSimpleWordDeco(String el, XMLAttributes attributes) {
        return simpleTextDecoMap.containsKey(el);
    }

    @SuppressWarnings("unchecked")
    protected <T> T findFragInStack(Class<T> cls) {
        for (int i = 0; i < parseContext.stackSize(); ++i) {
            List<GWikiFragment> fl = parseContext.peek(i);
            if (fl.size() > 0) {
                GWikiFragment lr = fl.get(fl.size() - 1);
                if (cls.isAssignableFrom(lr.getClass()) == true) {
                    return (T) lr;
                }
            }
        }
        return null;
    }

    protected GWikiFragment findFragsInStack(Class<? extends GWikiFragment>... classes) {
        for (Class<? extends GWikiFragment> cls : classes) {
            GWikiFragment f = findFragInStack(cls);
            if (f != null) {
                return f;
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    protected boolean needSoftNl() {
        return findFragsInStack(GWikiFragmentLi.class, GWikiFragmentTable.class) != null;
    }

    protected GWikiFragment getNlFragement(GWikiFragment defaultFrag) {
        if (needSoftNl() == true) {
            return new GWikiFragmentBrInLine();
        }
        return defaultFrag;
    }

    protected String getListTag(String en, XMLAttributes attributes) {
        String tag;
        if (en.equals("ol") == true) {
            tag = "#";
        } else if (StringUtils.equals(attributes.getValue("type"), "square") == true) {
            tag = "-";
        } else {
            tag = "*";
        }
        if (liStack.isEmpty() == true) {
            return tag;
        }
        for (int i = 0; i < parseContext.stackSize(); ++i) {
            List<GWikiFragment> fl = parseContext.peek(i);
            if (fl.size() > 0) {
                GWikiFragment lr = fl.get(fl.size() - 1);
                if (lr instanceof GWikiFragmentList) {
                    GWikiFragmentList lf = (GWikiFragmentList) lr;
                    tag += lf.getListTag();
                    break;
                }
            }
        }
        return tag;
    }

    protected MacroAttributes convertToMaAttributes(QName element, XMLAttributes attributes) {
        String en = element.rawname.toLowerCase();
        MacroAttributes ma = new MacroAttributes(en);
        for (int i = 0; i < attributes.getLength(); ++i) {
            String k = attributes.getLocalName(i);
            String v = attributes.getValue(i);
            ma.getArgs().setStringValue(k, v);
        }
        return ma;
    }

    protected GWikiFragment convertToBodyMacro(QName element, XMLAttributes attributes, int macroRenderModes) {
        MacroAttributes ma = convertToMaAttributes(element, attributes);
        return new GWikiMacroFragment(new GWikiHtmlBodyTagMacro(macroRenderModes), ma);
    }

    protected GWikiFragment convertToEmptyMacro(QName element, XMLAttributes attributes) {
        MacroAttributes ma = convertToMaAttributes(element, attributes);
        return new GWikiMacroFragment(new GWikiHtmlTagMacro(), ma);
    }

    protected String getAttribute(XMLAttributes attributes, String key) {
        return getAttribute(attributes, key, key);
    }

    protected String getAttribute(XMLAttributes attributes, String nativeKey, String dataKey) {
        String ret = attributes.getValue("data-wiki-" + dataKey);
        if (ret != null) {
            return ret;
        }
        return attributes.getValue(nativeKey);
    }

    protected void parseLink(XMLAttributes attributes) {
        // if (StringUtils.isNotEmpty(attributes.getValue("wikitarget")) == true) {
        // parseContext.addFragment(new GWikiFragementLink(attributes.getValue("wikitarget")));
        // return;
        // }
        String href = getAttribute(attributes, "href", "url");
        GWikiContext wikiContext = GWikiContext.getCurrent();
        String tat = getAttribute(attributes, "title");
        String title = tat;
        String id = href;
        if (href != null && wikiContext != null) {
            String ctxpath = wikiContext.getRequest().getContextPath();
            if (href.startsWith(ctxpath) == true) {
                if (ctxpath.length() > 0) {
                    id = href.substring(ctxpath.length() + 1);
                }
                if (id.startsWith("/") == true) {
                    id = id.substring(1);
                }
                GWikiElementInfo ei = wikiContext.getWikiWeb().findElementInfo(id);
                if (ei == null) {
                    id = href;
                } else {
                    String origtitle = ei.getTitle();
                    if (StringUtils.equals(origtitle, title) == true) {
                        title = null;
                    }
                }
            }
        }
        if (id == null) {
            id = "";
        }
        GWikiFragmentLink link = new GWikiFragmentLink(id);

        if (StringUtils.isNotBlank(title) == true) {
            link.setTitle(title);
        }
        tat = getAttribute(attributes, "target", "windowTarget");
        if (StringUtils.isNotBlank(tat) == true) {
            link.setWindowTarget(tat);
        }
        tat = getAttribute(attributes, "class");
        if (StringUtils.isNotBlank(tat) == true) {
            link.setLinkClass(tat);
        }
        parseContext.addFragment(link);
        return;
    }

    protected void finalizeLink() {
        List<GWikiFragment> frags = parseContext.popFragList();
        GWikiFragmentLink lf = (GWikiFragmentLink) parseContext.lastFragment();
        // if (wasForeignLink == true) {
        lf.addChilds(frags);
        // }
    }

    protected void parseImage(XMLAttributes attributes) {
        String source = attributes.getValue("src");
        if (source == null) {
            return;
        }
        GWikiContext wikiContext = GWikiContext.getCurrent();
        if (wikiContext != null) {
            String ctxpath = wikiContext.getRequest().getContextPath();
            if (StringUtils.isNotEmpty(ctxpath) && source.startsWith(ctxpath) == true) {
                source = source.substring(ctxpath.length() + 1);
            }
        }
        GWikiFragmentImage image = new GWikiFragmentImage(source);
        String tat = getAttribute(attributes, "alt");
        if (StringUtils.isNotEmpty(tat) == true) {
            image.setAlt(tat);
        }
        tat = getAttribute(attributes, "width");
        if (StringUtils.isNotEmpty(attributes.getValue("width")) == true) {
            image.setWidth(tat);
        }
        tat = getAttribute(attributes, "height");
        if (StringUtils.isNotEmpty(tat) == true) {
            image.setHeight(tat);
        }
        tat = getAttribute(attributes, "border");
        if (StringUtils.isNotEmpty(tat) == true) {
            image.setBorder(tat);
        }
        tat = getAttribute(attributes, "hspace");
        if (StringUtils.isNotEmpty(tat) == true) {
            image.setHspace(tat);
        }
        tat = getAttribute(attributes, "vspace");
        if (StringUtils.isNotEmpty(tat) == true) {
            image.setVspace(tat);
        }
        tat = getAttribute(attributes, "class", "styleClass");
        if (StringUtils.isNotEmpty(tat) == true) {
            image.setStyleClass(tat);
        }
        tat = getAttribute(attributes, "style");
        if (StringUtils.isNotEmpty(tat) == true) {
            image.setStyle(tat);
        }
        parseContext.addFragment(image);
    }

    protected void createTable(QName element, XMLAttributes attributes) {
        GWikiFragment frag;
        if (attributes.getLength() == 0 || (attributes.getLength() == 1
                && StringUtils.equals(attributes.getValue("class"), "gwikiTable") == true)) {
            frag = new GWikiFragmentTable();
        } else {
            frag = convertToBodyMacro(element, attributes,
                    GWikiMacroRenderFlags.combine(GWikiMacroRenderFlags.NewLineAfterStart,
                            GWikiMacroRenderFlags.NewLineBeforeEnd, GWikiMacroRenderFlags.TrimTextContent));
        }
        parseContext.addFragment(frag);
        parseContext.pushFragList();
    }

    protected void endTable() {
        List<GWikiFragment> frags = parseContext.popFragList();
        GWikiFragment top = parseContext.lastDefinedFragment();
        if (top instanceof GWikiMacroFragment) {
            GWikiMacroFragment bm = (GWikiMacroFragment) top;
            bm.addChilds(frags);
        } else {
            // nothing
        }
    }

    protected void copyAttributes(MacroAttributes target, XMLAttributes attributes) {
        for (int i = 0; i < attributes.getLength(); ++i) {
            target.getArgs().setStringValue(attributes.getQName(i), attributes.getValue(i));
        }
    }

    protected void createTr(QName element, XMLAttributes attributes) {
        GWikiFragment top = parseContext.lastDefinedFragment();

        if (top instanceof GWikiFragmentTable) {
            GWikiFragmentTable table = (GWikiFragmentTable) top;
            GWikiFragmentTable.Row row = new GWikiFragmentTable.Row();
            copyAttributes(row.getAttributes(), attributes);
            table.addRow(row);
            parseContext.pushFragList();
            return;
        }
        GWikiFragment frag = convertToBodyMacro(element, attributes,
                GWikiMacroRenderFlags.combine(GWikiMacroRenderFlags.NewLineAfterStart,
                        GWikiMacroRenderFlags.NewLineBeforeEnd, GWikiMacroRenderFlags.TrimTextContent));
        parseContext.addFragment(frag);
        parseContext.pushFragList();
    }

    protected void endTr() {
        List<GWikiFragment> frags = parseContext.popFragList();
        GWikiFragment top = parseContext.lastDefinedFragment();
        if (top instanceof GWikiMacroFragment) {
            GWikiMacroFragment bm = (GWikiMacroFragment) top;
            bm.addChilds(frags);
        } else {
            // nothing
        }
    }

    protected void createThTd(QName element, XMLAttributes attributes) {

        String en = element.rawname.toLowerCase();
        GWikiFragment lfrag = parseContext.lastDefinedFragment();
        if (lfrag instanceof GWikiFragmentTable) {
            GWikiFragmentTable table = (GWikiFragmentTable) lfrag;
            GWikiFragmentTable.Cell cell = table.addCell(en);
            copyAttributes(cell.getAttributes(), attributes);
            parseContext.pushFragList();
            return;

        }
        GWikiFragment frag = convertToBodyMacro(element, attributes, 0);
        parseContext.addFragment(frag);
        parseContext.pushFragList();
    }

    protected void endTdTh() {
        List<GWikiFragment> frags = parseContext.popFragList();
        GWikiFragment lf = parseContext.lastDefinedFragment();
        if (lf instanceof GWikiFragmentTable) {
            GWikiFragmentTable table = (GWikiFragmentTable) lf;
            table.addCellContent(frags);
        } else if (lf instanceof GWikiMacroFragment) {
            GWikiMacroFragment mf = (GWikiMacroFragment) lf;
            mf.addChilds(frags);
        }
    }

    protected void createCode(QName element, XMLAttributes attributes) {
        parseContext.pushFragList();
    }

    protected void endCode() {
        List<GWikiFragment> childs = parseContext.popFragList();
        GWikiFragmentFixedFont ff = new GWikiFragmentFixedFont(childs);
        parseContext.addFragment(ff);
    }

    protected boolean hasPreviousBr() {
        // last character field has br
        if (parseContext.lastFragment() instanceof GWikiFragmentBr) {
            return true;
        }
        return false;
    }

    private boolean isAutoCloseTag(String tagName) {
        if (ArrayUtils.contains(autoCloseTags, tagName) == true) {
            return true;
        }
        return false;
    }

    protected boolean handleMacroTransformerBegin(String tagName, XMLAttributes attributes, boolean withBody) {
        for (Html2WikiTransformer ma : macroTransformer) {
            if (ma.match(tagName, attributes, withBody) == true) {
                GWikiFragment frag = ma.handleMacroTransformer(tagName, attributes, withBody);
                if (frag != null) {
                    if (isAutoCloseTag(tagName) == true) {
                        autoCloseTagStack.push(frag);
                    }
                    parseContext.addFragment(frag);
                    parseContext.pushFragList();
                }

                return true;
            }
        }
        return false;
    }

    protected boolean handleMacroTransformerEnd(QName element, Augmentations augs) {
        String en = element.rawname.toLowerCase();
        GWikiFragment lpf = parseContext.lastParentFrag();
        if (lpf instanceof GWikiMacroFragment) {
            GWikiMacroFragment lpfm = (GWikiMacroFragment) lpf;

            if (lpfm.getMacro() instanceof GWikiMacroRte) {
                GWikiMacroRte mr = (GWikiMacroRte) lpfm.getMacro();
                if (mr.getTransformInfo() != null) {
                    String t = collectedText.toString();
                    if (mr.getTransformInfo() != null
                            && StringUtils.equals(mr.getTransformInfo().getTagName(), en) == true) {
                        List<GWikiFragment> children = parseContext.popFragList();
                        mr.getTransformInfo().handleMacroEnd(en, lpfm, children, t);
                        collectedText.setLength(0);
                        return true;
                    }
                }

            }
        }
        return false;
    }

    protected boolean handleAutoCloseTag(String tagName) {
        if (autoCloseTagStack.isEmpty() == true) {
            return false;
        }
        if (autoCloseTagStack.peek() != parseContext.lastParentFrag()) {
            return false;
        }
        GWikiFragment pfrag = autoCloseTagStack.pop();
        List<GWikiFragment> cfrags = parseContext.popFragList();
        if (pfrag instanceof GWikiNestableFragment) {
            ((GWikiNestableFragment) pfrag).addChilds(cfrags);
        }
        return true;
    }

    @Override
    public void emptyElement(QName element, XMLAttributes attributes, Augmentations augs) throws XNIException {
        flushText();
        String en = element.rawname.toLowerCase();
        if (en.equals("br") == true) {
            parseContext.addFragment(getNlFragement(new GWikiFragmentBr()));
        } else if (en.equals("p") == true) {
            parseContext.addFragment(getNlFragement(new GWikiFragmentP()));
        } else if (en.equals("hr") == true) {
            parseContext.addFragment(new GWikiFragmentHr());
        } else if (en.equals("img") == true) {
            parseImage(attributes);
        } else if (en.equals("a") == true) {
            // TODO gwiki anchor
        } else if (supportedHtmlTags.contains(en) == true) {
            parseContext.addFragment(convertToEmptyMacro(element, attributes));
        } else {
            // hmm
        }
        super.emptyElement(element, attributes, augs);
    }

    protected boolean handleSpanStart(QName element, XMLAttributes attributes) {
        String value = attributes.getValue("style");
        if (StringUtils.equals(value, "font-family: courier new,courier,monospace;") == true
                || StringUtils.equals(value, "font-family: courier new,courier;") == true) {
            parseContext.addFragment(new GWikiFragmentFixedFont(new ArrayList<GWikiFragment>()));
            parseContext.pushFragList();
            return true;
        }
        return false;
    }

    protected boolean handleSpanEnd() {
        if (parseContext.getFrags().size() >= 2) {
            List<GWikiFragment> fl = parseContext.getFrags().get(parseContext.getFrags().size() - 2);
            if (fl.size() > 0 && fl.get(fl.size() - 1) instanceof GWikiFragmentFixedFont) {
                GWikiFragmentFixedFont ff = (GWikiFragmentFixedFont) fl.get(fl.size() - 1);
                ff.addChilds(parseContext.popFragList());
                return true;
            }
        }
        return false;
    }

    @Override
    public void startElement(QName element, XMLAttributes attributes, Augmentations augs) throws XNIException {
        flushText();
        // todo <span style="font-family: monospace;">sadf</span> {{}}
        String en = element.rawname.toLowerCase();

        Html2WikiElement el = null;
        if (handleMacroTransformerBegin(en, attributes, true) == true) {
            ; // nothing more
        } else if (en.equals("p") == true) {
            String styleClass = attributes.getValue("class");
            String style = attributes.getValue("style");
            if (StringUtils.isNotBlank(styleClass) == true || StringUtils.isNotBlank(style) == true) {
                MacroAttributes ma = new MacroAttributes();
                ma.setCmd("p");
                if (StringUtils.isNotBlank(styleClass) == true) {
                    ma.getArgs().setStringValue("class", styleClass);
                }
                if (StringUtils.isNotBlank(style) == true) {
                    ma.getArgs().setStringValue("style", style);
                }
                GWikiMacroFragment mf = new GWikiMacroFragment(new GWikiHtmlBodyPTagMacro(), ma);
                parseContext.pushFragStack(mf);
                parseContext.pushFragList();
            }
        } else if (en.length() == 2 && en.charAt(0) == 'h' && Character.isDigit(en.charAt(1)) == true) {
            parseContext.addFragment(new GWikiFragmentHeading(Integer.parseInt("" + en.charAt(1))));
            parseContext.pushFragList();
        } else if (en.equals("ul") == true || en.equals("ol") == true) {
            parseContext.addFragment(new GWikiFragmentList(getListTag(en, attributes)));
            liStack.push(en);
            parseContext.pushFragList();
        } else if (en.equals("li") == true) {
            parseContext.addFragment(new GWikiFragmentLi(findFragInStack(GWikiFragmentList.class)));
            parseContext.pushFragList();
        } else if (isSimpleWordDeco(en, attributes) == true) {
            parseContext.pushFragList();
        } else if (en.equals("a") == true) {
            parseLink(attributes);
            parseContext.pushFragList();
        } else if (en.equals("table") == true) {
            createTable(element, attributes);
        } else if (en.equals("tr") == true) {
            createTr(element, attributes);
        } else if (en.equals("th") == true) {
            createThTd(element, attributes);
        } else if (en.equals("td") == true) {
            createThTd(element, attributes);
        } else if (en.equals("span") == true && handleSpanStart(element, attributes) == true) {
            // nothing
        } else if (en.equals("code") == true) {
            createCode(element, attributes);
        } else {
            if (supportedHtmlTags.contains(en) == true) {
                parseContext.addFragment(convertToBodyMacro(element, attributes, 0));
                parseContext.pushFragList();
            }
        }

    }

    @SuppressWarnings("deprecation")
    private boolean requireTextDecoMacroSyntax(final GWikiFragmentTextDeco fragDeco) {
        fragDeco.iterate(new GWikiSimpleFragmentVisitor() {

            @Override
            public void begin(GWikiFragment fragment) {
                if (fragDeco.isRequireMacroSyntax() == true) {
                    return;
                }
                if (fragment instanceof GWikiFragmentP || fragment instanceof GWikiFragmentBr) {
                    fragDeco.setRequireMacroSyntax(true);
                }
            }
        }, null);
        if (fragDeco.isRequireMacroSyntax() == true) {
            return true;
        }
        GWikiFragment lf = parseContext.lastFrag();
        if (lf == null) {
            return false;
        }
        if ((lf instanceof GWikiFragmentText) == false) {
            return false;
        }
        GWikiFragmentText tl = (GWikiFragmentText) lf;
        String source = tl.getSource();
        if (StringUtils.isEmpty(source) == true) {
            return false;
        }
        char lc = source.charAt(source.length() - 1);
        if (Character.isSpace(lc) == false) {
            return true;
        }
        return false;
    }

    @Override
    public void endElement(QName element, Augmentations augs) throws XNIException {
        flushText();
        String en = element.rawname.toLowerCase();
        if (handleMacroTransformerEnd(element, augs) == true) {
            super.endElement(element, augs);
            return;
        }
        List<GWikiFragment> frags;

        if (handleAutoCloseTag(en) == true) {
            ; // nothing
        } else if (en.length() == 2 && en.charAt(0) == 'h' && Character.isDigit(en.charAt(1)) == true) {
            frags = parseContext.popFragList();
            GWikiFragmentHeading lfh = (GWikiFragmentHeading) parseContext.lastFragment();
            lfh.addChilds(frags);
        } else if (en.equals("p") == true) {
            GWikiFragment top = parseContext.peekFragStack(); // p with attributes
            if (top instanceof GWikiMacroFragment
                    && ((GWikiMacroFragment) top).getMacro() instanceof GWikiHtmlBodyPTagMacro) {
                parseContext.popFragStack();
                frags = parseContext.popFragList();
                ((GWikiMacroFragment) top).getAttrs().setChildFragment(new GWikiFragmentChildContainer(frags));
                parseContext.addFragment(top);
            } else {
                if (hasPreviousBr() == false) {
                    parseContext.addFragment(getNlFragement(new GWikiFragmentP()));
                } else {
                    parseContext.addFragment(getNlFragement(new GWikiFragmentBr()));
                }
            }
        } else if (en.equals("ul") == true || en.equals("ol") == true) {
            if (liStack.isEmpty() == false && liStack.peek().equals(en) == true) {
                liStack.pop();
            }
            frags = parseContext.popFragList();
            GWikiFragmentList lf = (GWikiFragmentList) parseContext.lastFragment();
            lf.addChilds(frags);
        } else if (en.equals("li") == true) {
            frags = parseContext.popFragList();
            GWikiFragmentLi li = (GWikiFragmentLi) parseContext.lastFragment();
            li.addChilds(frags);
        } else if (isSimpleWordDeco(en, null) == true) {
            frags = parseContext.popFragList();
            GWikiFragmentTextDeco fragDeco = new GWikiFragmentTextDeco(simpleTextDecoMap.get(en).charAt(0),
                    "<" + en + ">", "</" + en + ">", frags);
            fragDeco.setRequireMacroSyntax(requireTextDecoMacroSyntax(fragDeco));
            parseContext.addFragment(fragDeco);
        } else if (en.equals("img") == true) {

        } else if (en.equals("a") == true) {
            finalizeLink();

        } else if (en.equals("table") == true) {
            endTable();
        } else if (en.equals("tr") == true) {
            endTr();
        } else if (en.equals("th") == true) {
            endTdTh();
        } else if (en.equals("td") == true) {
            endTdTh();
        } else if (en.equals("code") == true) {
            endCode();
        } else if (en.equals("span") == true && handleSpanEnd() == true) {
            // nothing

        } else if (supportedHtmlTags.contains(en) == true) {
            frags = parseContext.popFragList();
            GWikiMacroFragment maf = (GWikiMacroFragment) parseContext.lastFragment();
            if (maf != null) {
                maf.getAttrs().setChildFragment(new GWikiFragmentChildContainer(frags));
            } else {
                throw new RuntimeException("No fragment set on end tag: " + en);
            }
        } else {

            // GWikiLog.note("Unhandled end tag: " + en);
        }
        super.endElement(element, augs);
    }

    /**
     * Take a string and return escaped wiki text.
     * 
     * @param t string
     * @return escaped version of string. if t is null, returns null.
     */
    public static String escapeWiki(String t) {

        return escapeWiki(t, DEFAULT_SPECIAL_CHARACTERS);
    }

    public static String escapeWiki(String t, String specialCharacters) {
        if (t == null) {
            return t;
        }
        StringBuilder sb = null;
        boolean insideMacro = false;
        for (int i = 0; i < t.length(); ++i) {
            char c = t.charAt(i);
            if (insideMacro == true && c == '}') {
                insideMacro = false;
                if (sb != null) {
                    sb.append(c);
                }
                continue;
            }
            if (insideMacro == false && c == '{') {
                insideMacro = true;
            }
            if (insideMacro == false && specialCharacters.indexOf(c) != -1) {

                if (sb == null) {
                    sb = new StringBuilder();
                    if (i > 0) {
                        sb.append(t.substring(0, i));
                    }
                }
                sb.append("\\").append(c);
            } else {
                if (sb != null) {
                    sb.append(c);
                }
            }
        }
        if (sb != null) {
            return sb.toString();
        }
        return t;
    }

    protected String escapeText(String t) {
        String ret = escapeWiki(t, specialCharacters);
        return ret;
    }

    /**
     * Takes wiki text and returns a escaped String
     * 
     * @param wiki
     * @return null if result is empty
     */
    public static String unescapeWiki(String wiki) {
        return unescapeWiki(wiki, DEFAULT_SPECIAL_CHARACTERS);
    }

    /**
     * Takes wiki text and returns a escaped String
     * 
     * @param wiki
     * @return null if result is empty
     */
    public static String unescapeWiki(String wiki, String speacialCharacters) {
        StringBuilder result = new StringBuilder();

        for (int i = 0; i < (wiki.length() - 1); i++) {
            char curr = wiki.charAt(i);
            char lookAhead = wiki.charAt(i + 1);

            if (curr == '\\' && speacialCharacters.indexOf(lookAhead) != -1) {
                // ignore '\'
                continue;
            } else {
                result.append(curr);
            }
        }

        // append last
        if (wiki.charAt(wiki.length() - 1) != '\\') {
            result.append(wiki.charAt(wiki.length() - 1));
        }

        if (result.length() > 1) {
            return result.toString();
        }
        return null;
    }

    protected void flushText() {
        if (collectedText.length() == 0) {
            return;
        }
        String t = collectedText.toString();
        GWikiFragment lpf = parseContext.lastParentFrag();
        GWikiFragment lf = parseContext.lastFrag();

        if (lpf != null && lpf instanceof GWikiMacroFragment) {
            GWikiMacroFragment gwmf = (GWikiMacroFragment) lpf;
            if (gwmf.getMacro() instanceof GWikiBodyMacro
                    && (gwmf.getMacro() instanceof GWikiBodyEvalMacro) == false) {
                // GWikiBodyMacro bm = (GWikiBodyMacro)gwmf.getMacro();
                // gwmf.getAttrs().setBody(t);
                // just continue
                return;
            }
        }
        collectedText.setLength(0);
        if (t.length() > 0 && Character.isWhitespace(t.charAt(0)) == false) {
            if (lf instanceof GWikiFragmentTextDeco) {
                ((GWikiFragmentTextDeco) lf).setRequireMacroSyntax(true);
            }
        }
        if (ignoreWsNl == true) {
            String s = StringUtils.trim(t);
            if (StringUtils.isBlank(s) || StringUtils.isNewLine(s)) {
                return;
            }
        }
        // int cp = Character.codePointAt(t.toCharArray(), 0);
        if (t.startsWith("<!--") == true) {
            return;
        }
        if (StringUtils.isNewLine(t) == false) {
            parseContext.addTextFragement(escapeText(t));
        }
    }

    @Override
    public void characters(XMLString text, Augmentations augs) throws XNIException {

        String t = text.toString();
        if (t.startsWith("<!--") == true) {
            super.characters(text, augs);
            return;
        }

        collectedText.append(t);

        super.characters(text, augs);
    }

    public Set<String> getSupportedHtmlTags() {
        return supportedHtmlTags;
    }

    public void setSupportedHtmlTags(Set<String> supportedHtmlTags) {
        this.supportedHtmlTags = supportedHtmlTags;
    }

    public List<Html2WikiTransformer> getMacroTransformer() {
        return macroTransformer;
    }

    public void setMacroTransformer(List<Html2WikiTransformer> macroTransformer) {
        this.macroTransformer = macroTransformer;
    }

    public Map<String, String> getSimpleTextDecoMap() {
        return simpleTextDecoMap;
    }

    public void setSimpleTextDecoMap(Map<String, String> simpleTextDecoMap) {
        this.simpleTextDecoMap = simpleTextDecoMap;
    }

    public String[] getAutoCloseTags() {
        return autoCloseTags;
    }

    public void setAutoCloseTags(String[] autoCloseTags) {
        this.autoCloseTags = autoCloseTags;
    }

    public ArrayStack<GWikiFragment> getAutoCloseTagStack() {
        return autoCloseTagStack;
    }

    public void setAutoCloseTagStack(ArrayStack<GWikiFragment> autoCloseTagStack) {
        this.autoCloseTagStack = autoCloseTagStack;
    }

    public String getSpecialCharacters() {
        return specialCharacters;
    }

    public void setSpecialCharacters(String specialCharacters) {
        this.specialCharacters = specialCharacters;
    }

}