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

com.vladsch.flexmark.ext.tables.internal.TableNodeFormatter Maven / Gradle / Ivy

Go to download

flexmark-java extension for tables using "|" pipes with optional column spans and table caption

The newest version!
package com.vladsch.flexmark.ext.tables.internal;

import static com.vladsch.flexmark.formatter.RenderPurpose.FORMAT;
import static com.vladsch.flexmark.util.format.TableManipulator.NULL;

import com.vladsch.flexmark.ast.Paragraph;
import com.vladsch.flexmark.ast.Text;
import com.vladsch.flexmark.ext.tables.TableBlock;
import com.vladsch.flexmark.ext.tables.TableBody;
import com.vladsch.flexmark.ext.tables.TableCaption;
import com.vladsch.flexmark.ext.tables.TableCell;
import com.vladsch.flexmark.ext.tables.TableHead;
import com.vladsch.flexmark.ext.tables.TableRow;
import com.vladsch.flexmark.ext.tables.TableSeparator;
import com.vladsch.flexmark.ext.tables.TablesExtension;
import com.vladsch.flexmark.formatter.MarkdownWriter;
import com.vladsch.flexmark.formatter.NodeFormatter;
import com.vladsch.flexmark.formatter.NodeFormatterContext;
import com.vladsch.flexmark.formatter.NodeFormatterFactory;
import com.vladsch.flexmark.formatter.NodeFormattingHandler;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.data.DataHolder;
import com.vladsch.flexmark.util.format.MarkdownTable;
import com.vladsch.flexmark.util.format.TableFormatOptions;
import com.vladsch.flexmark.util.format.TrackedOffset;
import com.vladsch.flexmark.util.format.TrackedOffsetList;
import com.vladsch.flexmark.util.html.CellAlignment;
import com.vladsch.flexmark.util.sequence.BasedSequence;
import com.vladsch.flexmark.util.sequence.LineAppendable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class TableNodeFormatter implements NodeFormatter {
  private final TableFormatOptions options;
  private final boolean parserTrimCellWhiteSpace;

  private MarkdownTable myTable;

  public TableNodeFormatter(DataHolder options) {
    this.options = new TableFormatOptions(options);
    parserTrimCellWhiteSpace = TablesExtension.TRIM_CELL_WHITESPACE.get(options);
  }

  @Nullable
  @Override
  public Set> getNodeClasses() {
    return null;
  }

  @Nullable
  @Override
  public Set> getNodeFormattingHandlers() {
    return new HashSet<>(
        Arrays.asList(
            new NodeFormattingHandler<>(TableBlock.class, TableNodeFormatter.this::render),
            new NodeFormattingHandler<>(TableHead.class, TableNodeFormatter.this::render),
            new NodeFormattingHandler<>(TableSeparator.class, TableNodeFormatter.this::render),
            new NodeFormattingHandler<>(TableBody.class, TableNodeFormatter.this::render),
            new NodeFormattingHandler<>(TableRow.class, TableNodeFormatter.this::render),
            new NodeFormattingHandler<>(TableCell.class, TableNodeFormatter.this::render),
            new NodeFormattingHandler<>(TableCaption.class, TableNodeFormatter.this::render),
            new NodeFormattingHandler<>(Text.class, TableNodeFormatter.this::render)));
  }

  private void render(TableBlock node, NodeFormatterContext context, MarkdownWriter markdown) {
    myTable = new MarkdownTable(node.getChars(), options);

    switch (context.getRenderPurpose()) {
      case TRANSLATION_SPANS:
      case TRANSLATED_SPANS:
      case TRANSLATED:
        markdown.blankLine();
        context.renderChildren(node);
        markdown.tailBlankLine();
        break;

      case FORMAT:
      default:
        context.renderChildren(node);

        TrackedOffsetList trackedOffsets = context.getTrackedOffsets();
        List tableTrackedOffsets =
            trackedOffsets.getTrackedOffsets(node.getStartOffset(), node.getEndOffset());

        if (!trackedOffsets.isEmpty()) {
          for (TrackedOffset trackedOffset : tableTrackedOffsets) {
            myTable.addTrackedOffset(trackedOffset);
          }
        }

        // allow table manipulation, mostly for testing
        if (options.tableManipulator != NULL) {
          myTable.normalize();
          options.tableManipulator.apply(myTable, node);
        }

        if (myTable.getMaxColumns() > 0) {
          // output table
          markdown.blankLine();

          CharSequence prefix = markdown.getPrefix();
          myTable.setFormatTableIndentPrefix(prefix);
          MarkdownWriter formattedTable = new MarkdownWriter(markdown.getOptions());
          myTable.appendTable(formattedTable);

          List tableOffsets = myTable.getTrackedOffsets();
          int startOffset = markdown.offsetWithPending();
          if (!tableTrackedOffsets.isEmpty()) {
            // get the indent used for new lines so that index can be adjusted by added indent
            for (TrackedOffset trackedOffset : tableTrackedOffsets) {
              if (trackedOffset.isResolved()) {
                trackedOffset.setIndex(trackedOffset.getIndex() + startOffset);
              }
            }
          }

          markdown
              .pushPrefix()
              .setPrefix("", false)
              .pushOptions()
              .removeOptions(LineAppendable.F_WHITESPACE_REMOVAL)
              .append(formattedTable)
              .popOptions()
              .popPrefix(false);

          markdown.tailBlankLine();

          if (myTable.getMaxColumns() > 0 && !tableTrackedOffsets.isEmpty()) {
            if (options.dumpIntellijOffsets) {
              markdown.append("\nTracked Offsets").line(); // simulate flex example ast dump
              String sep = "  ";
              int i = 0;
              for (TrackedOffset trackedOffset : tableOffsets) {
                i++;
                markdown
                    .append(sep)
                    .append(
                        String.format(
                            Locale.US,
                            "%d:[%d,%d] was:[%d,%d]",
                            i,
                            trackedOffset.getIndex(),
                            trackedOffset.getIndex() + 1,
                            trackedOffset.getOffset(),
                            trackedOffset.getOffset() + 1));
                sep = " ";
              }
              markdown.append("\n");
            }
          }
        }
    }

    myTable = null;
  }

  private void render(TableHead node, NodeFormatterContext context, MarkdownWriter markdown) {
    myTable.setSeparator(false);
    myTable.setHeader(true);
    context.renderChildren(node);
  }

  private void render(TableSeparator node, NodeFormatterContext context, MarkdownWriter markdown) {
    myTable.setSeparator(true);
    context.renderChildren(node);
  }

  private void render(TableBody node, NodeFormatterContext context, MarkdownWriter markdown) {
    myTable.setSeparator(false);
    myTable.setHeader(false);
    context.renderChildren(node);
  }

  private void render(TableRow node, NodeFormatterContext context, MarkdownWriter markdown) {
    context.renderChildren(node);
    if (context.getRenderPurpose() == FORMAT) {
      if (!myTable.isSeparator()) myTable.nextRow();
    } else {
      markdown.line();
    }
  }

  private void render(TableCaption node, NodeFormatterContext context, MarkdownWriter markdown) {
    if (context.getRenderPurpose() == FORMAT) {
      myTable.setCaptionWithMarkers(
          node, node.getOpeningMarker(), node.getText(), node.getClosingMarker());
    } else {
      // HACK: to reuse the table formatting logic of MarkdownTable
      String dummyCaption = node.hasChildren() ? "dummy" : "";
      String formattedCaption =
          MarkdownTable.formattedCaption(
              BasedSequence.of(dummyCaption).subSequence(0, dummyCaption.length()), options);

      if (formattedCaption != null) {
        markdown.line().append(node.getOpeningMarker());
        context.renderChildren(node);
        markdown.append(node.getClosingMarker()).line();
      }
    }
  }

  private void render(TableCell node, NodeFormatterContext context, MarkdownWriter markdown) {
    if (context.getRenderPurpose() == FORMAT) {
      BasedSequence text = node.getText();
      if (options.trimCellWhitespace) {
        if (text.isBlank() && !text.isEmpty()) {
          text = text.subSequence(0, 1);
        } else {
          text = text.trim();
        }
      }
      myTable.addCell(
          new com.vladsch.flexmark.util.format.TableCell(
              node,
              node.getOpeningMarker(),
              text,
              node.getClosingMarker(),
              1,
              node.getSpan(),
              node.getAlignment() == null
                  ? CellAlignment.NONE
                  : node.getAlignment().cellAlignment()));
    } else {
      if (node.getPrevious() == null) {
        if (options.leadTrailPipes && node.getOpeningMarker().isEmpty()) markdown.append('|');
        else markdown.append(node.getOpeningMarker());
      } else {
        markdown.append(node.getOpeningMarker());
      }

      if (!myTable.isSeparator()
          && options.spaceAroundPipes
          && (!node.getText().startsWith(" ") || parserTrimCellWhiteSpace)) markdown.append(' ');

      String[] childText = new String[] {""};

      context.translatingSpan(
          (context1, writer) -> {
            context1.renderChildren(node);
            childText[0] = writer.toString(-1, -1);
          });

      if (!myTable.isSeparator()
          && options.spaceAroundPipes
          && (!childText[0].endsWith(" ") || parserTrimCellWhiteSpace)) markdown.append(' ');
      if (node.getNext() == null) {
        if (options.leadTrailPipes && node.getClosingMarker().isEmpty()) markdown.append('|');
        else markdown.append(node.getClosingMarker());
      } else {
        markdown.append(node.getClosingMarker());
      }
    }
  }

  private void render(Text node, NodeFormatterContext context, MarkdownWriter markdown) {
    // if (TABLE_HEADER_SEPARATOR.matcher(node.getChars()).matches()) {
    if (myTable != null && myTable.isSeparator()) {
      Node parent = node.getAncestorOfType(Paragraph.class);
      if (parent instanceof Paragraph && ((Paragraph) parent).hasTableSeparator()) {
        markdown.pushPrefix().addPrefix(" ").append(node.getChars()).popPrefix();
      } else {
        markdown.append(node.getChars());
      }
    } else {
      markdown.append(node.getChars());
    }
  }

  public static class Factory implements NodeFormatterFactory {
    @NotNull
    @Override
    public NodeFormatter create(@NotNull DataHolder options) {
      return new TableNodeFormatter(options);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy