MarkupVisitor.java

/*
 * Portions of this software was developed by employees of the National Institute
 * of Standards and Technology (NIST), an agency of the Federal Government and is
 * being made available as a public service. Pursuant to title 17 United States
 * Code Section 105, works of NIST employees are not subject to copyright
 * protection in the United States. This software may be subject to foreign
 * copyright. Permission in the United States and in foreign countries, to the
 * extent that NIST may hold copyright, to use, copy, modify, create derivative
 * works, and distribute this software and its documentation without fee is hereby
 * granted on a non-exclusive basis, provided that this notice and disclaimer
 * of warranty appears in all copies.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
 */

package gov.nist.secauto.metaschema.core.datatype.markup.flexmark;

import com.vladsch.flexmark.ast.AutoLink;
import com.vladsch.flexmark.ast.BlockQuote;
import com.vladsch.flexmark.ast.BulletList;
import com.vladsch.flexmark.ast.Code;
import com.vladsch.flexmark.ast.CodeBlock;
import com.vladsch.flexmark.ast.Emphasis;
import com.vladsch.flexmark.ast.FencedCodeBlock;
import com.vladsch.flexmark.ast.HardLineBreak;
import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.ast.HtmlBlock;
import com.vladsch.flexmark.ast.HtmlCommentBlock;
import com.vladsch.flexmark.ast.HtmlEntity;
import com.vladsch.flexmark.ast.HtmlInline;
import com.vladsch.flexmark.ast.Image;
import com.vladsch.flexmark.ast.IndentedCodeBlock;
import com.vladsch.flexmark.ast.Link;
import com.vladsch.flexmark.ast.LinkRef;
import com.vladsch.flexmark.ast.ListItem;
import com.vladsch.flexmark.ast.MailLink;
import com.vladsch.flexmark.ast.OrderedList;
import com.vladsch.flexmark.ast.Paragraph;
import com.vladsch.flexmark.ast.Reference;
import com.vladsch.flexmark.ast.SoftLineBreak;
import com.vladsch.flexmark.ast.StrongEmphasis;
import com.vladsch.flexmark.ast.Text;
import com.vladsch.flexmark.ast.TextBase;
import com.vladsch.flexmark.ast.ThematicBreak;
import com.vladsch.flexmark.ext.gfm.strikethrough.Subscript;
import com.vladsch.flexmark.ext.superscript.Superscript;
import com.vladsch.flexmark.ext.tables.TableBlock;
import com.vladsch.flexmark.ext.typographic.TypographicQuotes;
import com.vladsch.flexmark.ext.typographic.TypographicSmarts;
import com.vladsch.flexmark.util.ast.Block;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.ast.Node;

import gov.nist.secauto.metaschema.core.datatype.markup.flexmark.InsertAnchorExtension.InsertAnchorNode;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 *
 * This implementation is stateless.
 *
 * @param <T>
 *          the type of stream to write to
 * @param <E>
 *          the type of exception that can be thrown when a writing error occurs
 */
@SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_THROWABLE")
public class MarkupVisitor<T, E extends Throwable> implements IMarkupVisitor<T, E> {
  private final boolean handleBlockElements;

  public MarkupVisitor(boolean handleBlockElements) {
    this.handleBlockElements = handleBlockElements;
  }

  protected boolean isHandleBlockElements() {
    return handleBlockElements;
  }

  @Override
  public void visitDocument(Document document, IMarkupWriter<T, E> writer) throws E {
    visitChildren(document, writer);
  }

  protected void visitChildren(@NonNull Node parentNode, @NonNull IMarkupWriter<T, E> writer) throws E {
    for (Node node : parentNode.getChildren()) {
      assert node != null;
      visit(node, writer);
    }
  }

  protected void visit(@NonNull Node node, @NonNull IMarkupWriter<T, E> writer) throws E {
    boolean handled = processInlineElements(node, writer);
    if (!handled && node instanceof Block) {
      if (isHandleBlockElements()) {
        handled = processBlockElements(node, writer);
      } else {
        visitChildren(node, writer);
        handled = true;
      }
    }

    if (!handled) {
      throw new UnsupportedOperationException(
          String.format("Node '%s' not handled. AST: %s", node.getNodeName(), node.toAstString(true)));
    }
  }

  protected boolean processInlineElements( // NOPMD dispatch method
      @NonNull Node node,
      @NonNull IMarkupWriter<T, E> writer) throws E { // NOPMD - acceptable
    boolean retval = true;
    if (node instanceof Text) {
      writer.writeText((Text) node);
    } else if (node instanceof TextBase) {
      writer.writeText((TextBase) node);
    } else if (node instanceof HtmlEntity) {
      writer.writeHtmlEntity((HtmlEntity) node);
    } else if (node instanceof TypographicSmarts) {
      writer.writeHtmlEntity((TypographicSmarts) node);
    } else if (node instanceof TypographicQuotes) {
      writer.writeTypographicQuotes((TypographicQuotes) node, this::visit);
    } else if (node instanceof Code) {
      writer.writeCode((Code) node, this::visit);
    } else if (node instanceof StrongEmphasis) {
      writer.writeElement("strong", node, this::visit);
    } else if (node instanceof Emphasis) {
      writer.writeElement("em", node, this::visit);
    } else if (node instanceof ListItem) {
      writer.writeElement("li", node, this::visit);
    } else if (node instanceof Link) {
      writer.writeLink((Link) node, this::visit);
    } else if (node instanceof AutoLink) {
      writer.writeLink((AutoLink) node);
    } else if (node instanceof MailLink) {
      writer.writeLink((MailLink) node);
    } else if (node instanceof Subscript) {
      writer.writeElement("sub", node, this::visit);
    } else if (node instanceof Superscript) {
      writer.writeElement("sup", node, this::visit);
    } else if (node instanceof Image) {
      writer.writeImage((Image) node);
    } else if (node instanceof InsertAnchorNode) {
      writer.writeInsertAnchor((InsertAnchorNode) node);
    } else if (node instanceof SoftLineBreak) {
      writer.writeText("\n");
    } else if (node instanceof HardLineBreak) {
      writer.writeBreak((HardLineBreak) node);
    } else if (node instanceof HtmlInline) {
      writer.writeInlineHtml((HtmlInline) node);
    } else if (node instanceof LinkRef || node instanceof Reference) {
      throw new UnsupportedOperationException(
          String.format(
              "Link references are not supported by Metaschema."
                  + " Perhaps you have an unescaped bracket in the following string? %s",
              ObjectUtils.notNull(node.getParent()).getChars()));
    } else {
      retval = false;
    }
    return retval;
  }

  protected boolean processBlockElements( // NOPMD dispatch method
      @NonNull Node node,
      @NonNull IMarkupWriter<T, E> writer) throws E {
    boolean retval = true;
    if (node instanceof Paragraph) {
      writer.writeParagraph((Paragraph) node, this::visit);
    } else if (node instanceof Heading) {
      writer.writeHeading((Heading) node, this::visit);
    } else if (node instanceof OrderedList) {
      writer.writeList("ol", (OrderedList) node, this::visit);
    } else if (node instanceof BulletList) {
      writer.writeList("ul", (BulletList) node, this::visit);
    } else if (node instanceof TableBlock) {
      writer.writeTable((TableBlock) node, this::visit);
    } else if (node instanceof HtmlBlock) {
      writer.writeBlockHtml((HtmlBlock) node);
    } else if (node instanceof HtmlCommentBlock) {
      writer.writeComment((HtmlCommentBlock) node);
    } else if (node instanceof IndentedCodeBlock) {
      writer.writeCodeBlock((IndentedCodeBlock) node, this::visit);
    } else if (node instanceof FencedCodeBlock) {
      writer.writeCodeBlock((FencedCodeBlock) node, this::visit);
    } else if (node instanceof CodeBlock) {
      writer.writeCodeBlock((CodeBlock) node, this::visit);
    } else if (node instanceof BlockQuote) {
      writer.writeBlockQuote((BlockQuote) node, this::visit);
    } else if (node instanceof ThematicBreak) {
      writer.writeBreak((ThematicBreak) node);
    } else {
      retval = false;
    }

    // if (retval && node.getNextAny(Block.class) != null) {
    // writer.writeText("\n");
    // }
    return retval;
  }
}