All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.fixprotocol.md.antlr.MarkdownEventSource Maven / Gradle / Ivy

There is a newer version: 1.2.1
Show newest version
/*
 * Copyright 2020 FIX Protocol Ltd
 *
 * 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 io.fixprotocol.md.antlr;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import io.fixprotocol.md.antlr.MarkdownParser.BlockContext;
import io.fixprotocol.md.antlr.MarkdownParser.BlockquoteContext;
import io.fixprotocol.md.antlr.MarkdownParser.CellContext;
import io.fixprotocol.md.antlr.MarkdownParser.DocumentContext;
import io.fixprotocol.md.antlr.MarkdownParser.FencedcodeblockContext;
import io.fixprotocol.md.antlr.MarkdownParser.HeadingContext;
import io.fixprotocol.md.antlr.MarkdownParser.InfostringContext;
import io.fixprotocol.md.antlr.MarkdownParser.ListContext;
import io.fixprotocol.md.antlr.MarkdownParser.ListlineContext;
import io.fixprotocol.md.antlr.MarkdownParser.ParagraphContext;
import io.fixprotocol.md.antlr.MarkdownParser.ParagraphlineContext;
import io.fixprotocol.md.antlr.MarkdownParser.QuotelineContext;
import io.fixprotocol.md.antlr.MarkdownParser.TableContext;
import io.fixprotocol.md.antlr.MarkdownParser.TabledelimiterrowContext;
import io.fixprotocol.md.antlr.MarkdownParser.TableheadingContext;
import io.fixprotocol.md.antlr.MarkdownParser.TablerowContext;
import io.fixprotocol.md.event.Contextual;
import io.fixprotocol.md.event.MutableContext;
import io.fixprotocol.md.event.MutableContextual;
import io.fixprotocol.md.event.MutableDetailProperties;
import io.fixprotocol.md.event.mutable.ContextImpl;
import io.fixprotocol.md.event.mutable.DetailImpl;
import io.fixprotocol.md.event.mutable.DetailTableImpl;
import io.fixprotocol.md.event.mutable.DocumentationImpl;

/**
 * Generates events for document consumers
 *
 * @author Don Mendelson
 *
 */
public class MarkdownEventSource implements MarkdownListener {

  private static final String CELL_NONTEXT = " |\t";
  private static final String WHITESPACE_REGEX = "[ \t]";

  static String normalizeList(List textlines) {
    return textlines.stream().map(p -> p.LISTLINE().getText()).collect(Collectors.joining("\n"));
  }

  static String normalizeParagraph(List textlines) {
    return textlines.stream().map(p -> p.PARAGRAPHLINE().getText())
        .collect(Collectors.joining(" "));
  }

  static String normalizeQuote(List textlines) {
    return textlines.stream().map(p -> p.QUOTELINE().getText()).collect(Collectors.joining("\n"));
  }

  static String trimCell(String text) {
    int beginIndex = 0;
    int endIndex = text.length();
    for (; beginIndex < endIndex
        && (CELL_NONTEXT.indexOf(text.charAt(beginIndex)) != -1); beginIndex++);
    for (; endIndex > beginIndex
        && (CELL_NONTEXT.indexOf(text.charAt(endIndex - 1)) != -1); endIndex--);
    return text.substring(beginIndex, endIndex);
  }

  private final Consumer contextConsumer;
  private final Deque contexts = new ArrayDeque<>();
  private boolean inTableHeading = false;
  private final List lastBlocks = new ArrayList<>();
  private int lastColumnNo;
  private final List lastRowValues = new ArrayList<>();
  private final List lastTableHeadings = new ArrayList<>();
  private final Logger logger = LogManager.getLogger(getClass());

  public MarkdownEventSource(Consumer contextConsumer) {
    this.contextConsumer = contextConsumer;
  }

  @Override
  public void enterBlock(BlockContext ctx) {
    // no action

  }

  @Override
  public void enterBlockquote(BlockquoteContext ctx) {
    // no action

  }

  @Override
  public void enterCell(CellContext ctx) {
    // no action

  }

  @Override
  public void enterDocument(DocumentContext ctx) {
    // no action

  }

  @Override
  public void enterEveryRule(ParserRuleContext ctx) {
    // no action

  }

  @Override
  public void enterFencedcodeblock(FencedcodeblockContext ctx) {
    // TODO Auto-generated method stub

  }

  @Override
  public void enterHeading(HeadingContext ctx) {
    supplyLastDocumentation();
    lastBlocks.clear();
  }

  @Override
  public void enterInfostring(InfostringContext ctx) {
    // TODO Auto-generated method stub

  }

  @Override
  public void enterList(ListContext ctx) {
    // no action

  }

  @Override
  public void enterListline(ListlineContext ctx) {
    // no action

  }

  @Override
  public void enterParagraph(ParagraphContext ctx) {

  }

  @Override
  public void enterParagraphline(ParagraphlineContext ctx) {
    // no action

  }

  @Override
  public void enterQuoteline(QuotelineContext ctx) {
    // no action

  }

  @Override
  public void enterTable(TableContext ctx) {
    supplyLastDocumentation();
    lastBlocks.clear();
  }

  @Override
  public void enterTabledelimiterrow(TabledelimiterrowContext ctx) {
    // no action

  }

  @Override
  public void enterTableheading(TableheadingContext ctx) {
    lastTableHeadings.clear();
    inTableHeading = true;
  }

  @Override
  public void enterTablerow(TablerowContext ctx) {
    lastColumnNo = 0;
    lastRowValues.clear();
  }

  @Override
  public void exitBlock(BlockContext ctx) {
    // no action

  }

  @Override
  public void exitBlockquote(BlockquoteContext ctx) {
    final List textlines = ctx.quoteline();
    lastBlocks.add(normalizeQuote(textlines));
  }

  @Override
  public void exitCell(CellContext ctx) {
    final String cellText = trimCell(ctx.CELLTEXT().getText());
    if (inTableHeading) {
      lastTableHeadings.add(cellText);
    } else {
      lastRowValues.add(cellText);
    }
    lastColumnNo++;
  }

  @Override
  public void exitDocument(DocumentContext ctx) {
    supplyLastDocumentation();
  }

  @Override
  public void exitEveryRule(ParserRuleContext ctx) {
    // no action

  }

  @Override
  public void exitFencedcodeblock(FencedcodeblockContext ctx) {
    // TODO Auto-generated method stub

  }

  @Override
  public void exitHeading(HeadingContext ctx) {
    final String headingLine = ctx.HEADINGLINE().getText();
    // Only a new heading changes the context
    // Heading level is length of first word formed with '#'
    final int headingLevel = headingLine.indexOf(" ");
    final String[] headingWords = headingLine.substring(headingLevel + 1).split(WHITESPACE_REGEX);
    final ContextImpl context = new ContextImpl(headingWords, headingLevel);
    updateParentContext(context);

    contextConsumer.accept(context);
  }

  @Override
  public void exitInfostring(InfostringContext ctx) {
    // TODO Auto-generated method stub

  }

  @Override
  public void exitList(ListContext ctx) {
    final List textlines = ctx.listline();
    lastBlocks.add(normalizeList(textlines));
  }

  @Override
  public void exitListline(ListlineContext ctx) {
    // no action

  }

  @Override
  public void exitParagraph(ParagraphContext ctx) {
    final List textlines = ctx.paragraphline();
    lastBlocks.add(normalizeParagraph(textlines));
  }

  @Override
  public void exitParagraphline(ParagraphlineContext ctx) {
    // no action

  }

  @Override
  public void exitQuoteline(QuotelineContext ctx) {
    // no action

  }

  @Override
  public void exitTable(TableContext ctx) {
    if (!inTableHeading) {
      final DetailTableImpl detailTable = new DetailTableImpl();
      final List tablerows = ctx.tablerow();

      for (final TablerowContext tablerow : tablerows) {
        final MutableDetailProperties detail = detailTable.newRow();

        for (int i = 0; i < tablerow.cell().size() && i < lastTableHeadings.size(); i++) {
          final CellContext cell = tablerow.cell(i);
          if (cell != null) {
            detail.addProperty(lastTableHeadings.get(i), cell.getText());
          } else {
            logger.error("MarkdownEventSource table cell missing in column {}", i);
          }
        }
      }
      updateParentContext(detailTable);
      if (contextConsumer != null) {
        contextConsumer.accept(detailTable);
      }
    }
  }

  @Override
  public void exitTabledelimiterrow(TabledelimiterrowContext ctx) {
    // no action

  }

  @Override
  public void exitTableheading(TableheadingContext ctx) {
    inTableHeading = false;
  }

  @Override
  public void exitTablerow(TablerowContext ctx) {
    if (!inTableHeading) {
      final DetailImpl detail = new DetailImpl();
      for (int i = 0; i < lastColumnNo && i < lastTableHeadings.size(); i++) {
        final String value = lastRowValues.get(i);
        if (!value.isBlank()) {
          detail.addProperty(lastTableHeadings.get(i), value);
        }
      }
      updateParentContext(detail);
      if (contextConsumer != null) {
        contextConsumer.accept(detail);
      }
    }
  }

  @Override
  public void visitErrorNode(ErrorNode node) {
    // should error node be logged?

  }

  @Override
  public void visitTerminal(TerminalNode node) {
    // no action

  }

  void updateParentContext(final MutableContext context) {
    // Remove previous contexts at same or lower level
    contexts.removeIf(c -> context.getLevel() <= c.getLevel());
    final MutableContext lastContext = contexts.peekLast();

    // Add top level context or lower level than parent
    if (lastContext == null) {
      contexts.add(context);
    } else if (context.getLevel() > lastContext.getLevel()) {
      context.setParent(lastContext);
      contexts.add(context);
    }
  }

  void updateParentContext(final MutableContextual contextual) {
    final MutableContext lastContext = contexts.peekLast();
    contextual.setParent(lastContext);
  }

  private String normalizeBlocks() {
    return String.join("\n\n", lastBlocks);
  }

  private void supplyLastDocumentation() {
    if (!lastBlocks.isEmpty()) {
      final String paragraphs = normalizeBlocks();
      final DocumentationImpl documentation = new DocumentationImpl(paragraphs);
      updateParentContext(documentation);
      contextConsumer.accept(documentation);
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy