001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.metaschema.core.datatype.markup.flexmark;
028
029import com.vladsch.flexmark.ast.AutoLink;
030import com.vladsch.flexmark.ast.BlockQuote;
031import com.vladsch.flexmark.ast.BulletList;
032import com.vladsch.flexmark.ast.Code;
033import com.vladsch.flexmark.ast.CodeBlock;
034import com.vladsch.flexmark.ast.Emphasis;
035import com.vladsch.flexmark.ast.FencedCodeBlock;
036import com.vladsch.flexmark.ast.HardLineBreak;
037import com.vladsch.flexmark.ast.Heading;
038import com.vladsch.flexmark.ast.HtmlBlock;
039import com.vladsch.flexmark.ast.HtmlCommentBlock;
040import com.vladsch.flexmark.ast.HtmlEntity;
041import com.vladsch.flexmark.ast.HtmlInline;
042import com.vladsch.flexmark.ast.Image;
043import com.vladsch.flexmark.ast.IndentedCodeBlock;
044import com.vladsch.flexmark.ast.Link;
045import com.vladsch.flexmark.ast.LinkRef;
046import com.vladsch.flexmark.ast.ListItem;
047import com.vladsch.flexmark.ast.MailLink;
048import com.vladsch.flexmark.ast.OrderedList;
049import com.vladsch.flexmark.ast.Paragraph;
050import com.vladsch.flexmark.ast.Reference;
051import com.vladsch.flexmark.ast.SoftLineBreak;
052import com.vladsch.flexmark.ast.StrongEmphasis;
053import com.vladsch.flexmark.ast.Text;
054import com.vladsch.flexmark.ast.TextBase;
055import com.vladsch.flexmark.ast.ThematicBreak;
056import com.vladsch.flexmark.ext.gfm.strikethrough.Subscript;
057import com.vladsch.flexmark.ext.superscript.Superscript;
058import com.vladsch.flexmark.ext.tables.TableBlock;
059import com.vladsch.flexmark.ext.typographic.TypographicQuotes;
060import com.vladsch.flexmark.ext.typographic.TypographicSmarts;
061import com.vladsch.flexmark.util.ast.Block;
062import com.vladsch.flexmark.util.ast.Document;
063import com.vladsch.flexmark.util.ast.Node;
064
065import gov.nist.secauto.metaschema.core.datatype.markup.flexmark.InsertAnchorExtension.InsertAnchorNode;
066import gov.nist.secauto.metaschema.core.util.ObjectUtils;
067
068import edu.umd.cs.findbugs.annotations.NonNull;
069import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
070
071/**
072 *
073 * This implementation is stateless.
074 *
075 * @param <T>
076 *          the type of stream to write to
077 * @param <E>
078 *          the type of exception that can be thrown when a writing error occurs
079 */
080@SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_THROWABLE")
081public class MarkupVisitor<T, E extends Throwable> implements IMarkupVisitor<T, E> {
082  private final boolean handleBlockElements;
083
084  public MarkupVisitor(boolean handleBlockElements) {
085    this.handleBlockElements = handleBlockElements;
086  }
087
088  protected boolean isHandleBlockElements() {
089    return handleBlockElements;
090  }
091
092  @Override
093  public void visitDocument(Document document, IMarkupWriter<T, E> writer) throws E {
094    visitChildren(document, writer);
095  }
096
097  protected void visitChildren(@NonNull Node parentNode, @NonNull IMarkupWriter<T, E> writer) throws E {
098    for (Node node : parentNode.getChildren()) {
099      assert node != null;
100      visit(node, writer);
101    }
102  }
103
104  protected void visit(@NonNull Node node, @NonNull IMarkupWriter<T, E> writer) throws E {
105    boolean handled = processInlineElements(node, writer);
106    if (!handled && node instanceof Block) {
107      if (isHandleBlockElements()) {
108        handled = processBlockElements(node, writer);
109      } else {
110        visitChildren(node, writer);
111        handled = true;
112      }
113    }
114
115    if (!handled) {
116      throw new UnsupportedOperationException(
117          String.format("Node '%s' not handled. AST: %s", node.getNodeName(), node.toAstString(true)));
118    }
119  }
120
121  protected boolean processInlineElements( // NOPMD dispatch method
122      @NonNull Node node,
123      @NonNull IMarkupWriter<T, E> writer) throws E { // NOPMD - acceptable
124    boolean retval = true;
125    if (node instanceof Text) {
126      writer.writeText((Text) node);
127    } else if (node instanceof TextBase) {
128      writer.writeText((TextBase) node);
129    } else if (node instanceof HtmlEntity) {
130      writer.writeHtmlEntity((HtmlEntity) node);
131    } else if (node instanceof TypographicSmarts) {
132      writer.writeHtmlEntity((TypographicSmarts) node);
133    } else if (node instanceof TypographicQuotes) {
134      writer.writeTypographicQuotes((TypographicQuotes) node, this::visit);
135    } else if (node instanceof Code) {
136      writer.writeCode((Code) node, this::visit);
137    } else if (node instanceof StrongEmphasis) {
138      writer.writeElement("strong", node, this::visit);
139    } else if (node instanceof Emphasis) {
140      writer.writeElement("em", node, this::visit);
141    } else if (node instanceof ListItem) {
142      writer.writeElement("li", node, this::visit);
143    } else if (node instanceof Link) {
144      writer.writeLink((Link) node, this::visit);
145    } else if (node instanceof AutoLink) {
146      writer.writeLink((AutoLink) node);
147    } else if (node instanceof MailLink) {
148      writer.writeLink((MailLink) node);
149    } else if (node instanceof Subscript) {
150      writer.writeElement("sub", node, this::visit);
151    } else if (node instanceof Superscript) {
152      writer.writeElement("sup", node, this::visit);
153    } else if (node instanceof Image) {
154      writer.writeImage((Image) node);
155    } else if (node instanceof InsertAnchorNode) {
156      writer.writeInsertAnchor((InsertAnchorNode) node);
157    } else if (node instanceof SoftLineBreak) {
158      writer.writeText("\n");
159    } else if (node instanceof HardLineBreak) {
160      writer.writeBreak((HardLineBreak) node);
161    } else if (node instanceof HtmlInline) {
162      writer.writeInlineHtml((HtmlInline) node);
163    } else if (node instanceof LinkRef || node instanceof Reference) {
164      throw new UnsupportedOperationException(
165          String.format(
166              "Link references are not supported by Metaschema."
167                  + " Perhaps you have an unescaped bracket in the following string? %s",
168              ObjectUtils.notNull(node.getParent()).getChars()));
169    } else {
170      retval = false;
171    }
172    return retval;
173  }
174
175  protected boolean processBlockElements( // NOPMD dispatch method
176      @NonNull Node node,
177      @NonNull IMarkupWriter<T, E> writer) throws E {
178    boolean retval = true;
179    if (node instanceof Paragraph) {
180      writer.writeParagraph((Paragraph) node, this::visit);
181    } else if (node instanceof Heading) {
182      writer.writeHeading((Heading) node, this::visit);
183    } else if (node instanceof OrderedList) {
184      writer.writeList("ol", (OrderedList) node, this::visit);
185    } else if (node instanceof BulletList) {
186      writer.writeList("ul", (BulletList) node, this::visit);
187    } else if (node instanceof TableBlock) {
188      writer.writeTable((TableBlock) node, this::visit);
189    } else if (node instanceof HtmlBlock) {
190      writer.writeBlockHtml((HtmlBlock) node);
191    } else if (node instanceof HtmlCommentBlock) {
192      writer.writeComment((HtmlCommentBlock) node);
193    } else if (node instanceof IndentedCodeBlock) {
194      writer.writeCodeBlock((IndentedCodeBlock) node, this::visit);
195    } else if (node instanceof FencedCodeBlock) {
196      writer.writeCodeBlock((FencedCodeBlock) node, this::visit);
197    } else if (node instanceof CodeBlock) {
198      writer.writeCodeBlock((CodeBlock) node, this::visit);
199    } else if (node instanceof BlockQuote) {
200      writer.writeBlockQuote((BlockQuote) node, this::visit);
201    } else if (node instanceof ThematicBreak) {
202      writer.writeBreak((ThematicBreak) node);
203    } else {
204      retval = false;
205    }
206
207    // if (retval && node.getNextAny(Block.class) != null) {
208    // writer.writeText("\n");
209    // }
210    return retval;
211  }
212}