Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git

Fix processing of inline R statements within TeX code

AuthorDaveJarvis <email>
Date2021-01-01 10:23:39 GMT-0800
Commit84ac84ab0d55aab9a0eac2412be5882af1347760
Parente02499e
Delta124 lines added, 206 lines removed, 82-line decrease
src/test/java/com/keenwrite/processors/markdown/ImageLinkExtensionTest.java
import com.keenwrite.preferences.Workspace;
+import com.keenwrite.processors.markdown.extensions.ImageLinkExtension;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
src/main/java/com/keenwrite/ui/dialogs/LinkDialog.java
/**
- * Dialog to enter a markdown link.
+ * Dialog to enter a Markdown link.
*/
public class LinkDialog extends AbstractDialog<String> {
src/main/java/com/keenwrite/processors/markdown/tex/TexNodeRenderer.java
-/* Copyright 2020 White Magic Software, Ltd. -- All rights reserved. */
-package com.keenwrite.processors.markdown.tex;
-
-import com.keenwrite.ExportFormat;
-import com.keenwrite.preview.SvgRasterizer;
-import com.vladsch.flexmark.html.HtmlWriter;
-import com.vladsch.flexmark.html.renderer.NodeRenderer;
-import com.vladsch.flexmark.html.renderer.NodeRendererContext;
-import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
-import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
-import com.vladsch.flexmark.util.ast.Node;
-import com.vladsch.flexmark.util.data.DataHolder;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.Set;
-
-import static com.keenwrite.preview.MathRenderer.MATH_RENDERER;
-import static com.keenwrite.processors.markdown.tex.TexNode.*;
-
-public class TexNodeRenderer {
-
- public static class Factory implements NodeRendererFactory {
- private final ExportFormat mExportFormat;
-
- public Factory( final ExportFormat exportFormat ) {
- mExportFormat = exportFormat;
- }
-
- @NotNull
- @Override
- public NodeRenderer apply( @NotNull DataHolder options ) {
- return switch( mExportFormat ) {
- case HTML_TEX_SVG -> new TexSvgNodeRenderer();
- case HTML_TEX_DELIMITED, MARKDOWN_PLAIN -> new TexDelimNodeRenderer();
- case NONE -> new TexElementNodeRenderer();
- };
- }
- }
-
- private static abstract class AbstractTexNodeRenderer
- implements NodeRenderer {
-
- @Override
- public @Nullable Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
- final var h = new NodeRenderingHandler<>( TexNode.class, this::render );
- return Set.of( h );
- }
-
- /**
- * Subclasses implement this method to render the content of {@link TexNode}
- * instances as per their associated {@link ExportFormat}.
- *
- * @param node {@link Node} containing text content of a math formula.
- * @param context Configuration information (unused).
- * @param html Where to write the rendered output.
- */
- abstract void render( final TexNode node,
- final NodeRendererContext context,
- final HtmlWriter html );
- }
-
- /**
- * Responsible for rendering a TeX node as an HTML {@code <tex>}
- * element. This is the default behaviour.
- */
- private static class TexElementNodeRenderer extends AbstractTexNodeRenderer {
- void render( final TexNode node,
- final NodeRendererContext context,
- final HtmlWriter html ) {
- html.tag( HTML_TEX );
- html.raw( node.getText() );
- html.closeTag( HTML_TEX );
- }
- }
-
- /**
- * Responsible for rendering a TeX node as an HTML {@code <svg>}
- * element.
- */
- private static class TexSvgNodeRenderer extends AbstractTexNodeRenderer {
- void render( final TexNode node,
- final NodeRendererContext context,
- final HtmlWriter html ) {
- final var tex = node.getText().toStringOrNull();
- final var doc = MATH_RENDERER.render( tex == null ? "" : tex );
- final var svg = SvgRasterizer.toSvg( doc.getDocumentElement() );
- html.raw( svg );
- }
- }
-
- /**
- * Responsible for rendering a TeX node as text bracketed by $ tokens.
- */
- private static class TexDelimNodeRenderer extends AbstractTexNodeRenderer {
- void render( final TexNode node,
- final NodeRendererContext context,
- final HtmlWriter html ) {
- html.raw( TOKEN_OPEN );
- html.raw( node.getText() );
- html.raw( TOKEN_CLOSE );
- }
- }
-}
src/main/java/com/keenwrite/processors/markdown/extensions/tex/TexNodeRenderer.java
+/* Copyright 2020 White Magic Software, Ltd. -- All rights reserved. */
+package com.keenwrite.processors.markdown.extensions.tex;
+
+import com.keenwrite.ExportFormat;
+import com.keenwrite.preview.SvgRasterizer;
+import com.vladsch.flexmark.html.HtmlWriter;
+import com.vladsch.flexmark.html.renderer.NodeRenderer;
+import com.vladsch.flexmark.html.renderer.NodeRendererContext;
+import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
+import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
+import com.vladsch.flexmark.util.ast.Node;
+import com.vladsch.flexmark.util.data.DataHolder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Set;
+
+import static com.keenwrite.preview.MathRenderer.MATH_RENDERER;
+import static com.keenwrite.processors.markdown.extensions.tex.TexNode.*;
+
+public class TexNodeRenderer {
+
+ public static class Factory implements NodeRendererFactory {
+ private final ExportFormat mExportFormat;
+
+ public Factory( final ExportFormat exportFormat ) {
+ mExportFormat = exportFormat;
+ }
+
+ @NotNull
+ @Override
+ public NodeRenderer apply( @NotNull DataHolder options ) {
+ return switch( mExportFormat ) {
+ case HTML_TEX_SVG -> new TexSvgNodeRenderer();
+ case HTML_TEX_DELIMITED, MARKDOWN_PLAIN -> new TexDelimNodeRenderer();
+ case NONE -> new TexElementNodeRenderer();
+ };
+ }
+ }
+
+ private static abstract class AbstractTexNodeRenderer
+ implements NodeRenderer {
+
+ @Override
+ public @Nullable Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
+ final var h = new NodeRenderingHandler<>( TexNode.class, this::render );
+ return Set.of( h );
+ }
+
+ /**
+ * Subclasses implement this method to render the content of {@link TexNode}
+ * instances as per their associated {@link ExportFormat}.
+ *
+ * @param node {@link Node} containing text content of a math formula.
+ * @param context Configuration information (unused).
+ * @param html Where to write the rendered output.
+ */
+ abstract void render( final TexNode node,
+ final NodeRendererContext context,
+ final HtmlWriter html );
+ }
+
+ /**
+ * Responsible for rendering a TeX node as an HTML {@code <tex>}
+ * element. This is the default behaviour.
+ */
+ private static class TexElementNodeRenderer extends AbstractTexNodeRenderer {
+ void render( final TexNode node,
+ final NodeRendererContext context,
+ final HtmlWriter html ) {
+ html.tag( HTML_TEX );
+ html.raw( node.getText() );
+ html.closeTag( HTML_TEX );
+ }
+ }
+
+ /**
+ * Responsible for rendering a TeX node as an HTML {@code <svg>}
+ * element.
+ */
+ private static class TexSvgNodeRenderer extends AbstractTexNodeRenderer {
+ void render( final TexNode node,
+ final NodeRendererContext context,
+ final HtmlWriter html ) {
+ final var tex = node.getText().toStringOrNull();
+ final var doc = MATH_RENDERER.render( tex == null ? "" : tex );
+ final var svg = SvgRasterizer.toSvg( doc.getDocumentElement() );
+ html.raw( svg );
+ }
+ }
+
+ /**
+ * Responsible for rendering a TeX node as text bracketed by $ tokens.
+ */
+ private static class TexDelimNodeRenderer extends AbstractTexNodeRenderer {
+ void render( final TexNode node,
+ final NodeRendererContext context,
+ final HtmlWriter html ) {
+ html.raw( TOKEN_OPEN );
+ html.raw( node.getText() );
+ html.raw( TOKEN_CLOSE );
+ }
+ }
+}
src/main/java/com/keenwrite/processors/markdown/MarkdownProcessor.java
import com.keenwrite.io.MediaType;
import com.keenwrite.processors.ExecutorProcessor;
-import com.keenwrite.processors.IdentityProcessor;
import com.keenwrite.processors.Processor;
import com.keenwrite.processors.ProcessorContext;
-import com.keenwrite.processors.markdown.r.RExtension;
+import com.keenwrite.processors.markdown.extensions.caret.CaretExtension;
+import com.keenwrite.processors.markdown.extensions.FencedBlockExtension;
+import com.keenwrite.processors.markdown.extensions.ImageLinkExtension;
+import com.keenwrite.processors.markdown.extensions.tex.TeXExtension;
+import com.keenwrite.processors.markdown.extensions.r.RExtension;
import com.vladsch.flexmark.ext.definition.DefinitionExtension;
import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughSubscriptExtension;
import static com.keenwrite.io.MediaType.TEXT_R_MARKDOWN;
import static com.keenwrite.io.MediaType.TEXT_R_XML;
+import static com.keenwrite.processors.IdentityProcessor.IDENTITY;
/**
public static MarkdownProcessor create( final ProcessorContext context ) {
- return create( IdentityProcessor.INSTANCE, context );
+ return create( IDENTITY, context );
}
final ProcessorContext context ) {
final var extensions = createDefaultExtensions();
- final var format = context.getExportFormat();
- final var workspace = context.getWorkspace();
final var editorFile = context.getPath();
- final var linkDir = context.getBasePath();
final var mediaType = MediaType.valueFrom( editorFile );
if( mediaType == TEXT_R_MARKDOWN || mediaType == TEXT_R_XML ) {
- extensions.add( RExtension.create() );
+ extensions.add( RExtension.create( context ) );
}
- extensions.add( ImageLinkExtension.create( linkDir, workspace ) );
- extensions.add( TeXExtension.create( format ) );
+ extensions.add( ImageLinkExtension.create( context ) );
+ extensions.add( TeXExtension.create( context ) );
extensions.add( FencedBlockExtension.create( context ) );
- extensions.add( CaretExtension.create( context.getCaret() ) );
+ extensions.add( CaretExtension.create( context ) );
return extensions;
@Override
public String apply( final String markdown ) {
- return toHtml( markdown );
+ return toHtml( parse( markdown ) );
}
/**
- * Returns the AST in the form of a node for the given markdown document. This
+ * Returns the AST in the form of a node for the given Markdown document. This
* can be used, for example, to determine if a hyperlink exists inside of a
* paragraph.
*
- * @param markdown The markdown to convert into an AST.
- * @return The markdown AST for the given text (usually a paragraph).
+ * @param markdown The Markdown to convert into an AST.
+ * @return The Markdown AST for the given text (usually a paragraph).
*/
public Node toNode( final String markdown ) {
/**
- * Helper method to create an AST given some markdown.
+ * Helper method to create an AST given some Markdown.
*
- * @param markdown The markdown to parse.
- * @return The root node of the markdown tree.
+ * @param markdown The Markdown to parse.
+ * @return The root node of the Markdown tree.
*/
private Node parse( final String markdown ) {
return getParser().parse( markdown );
- }
-
- /**
- * Converts a string of markdown into HTML.
- *
- * @param markdown The markdown text to convert to HTML, must not be null.
- * @return The markdown rendered as an HTML document.
- */
- private String toHtml( final String markdown ) {
- return toHtml( parse( markdown ) );
}
src/main/java/com/keenwrite/processors/markdown/TeXExtension.java
-/* Copyright 2020 White Magic Software, Ltd. -- All rights reserved. */
-package com.keenwrite.processors.markdown;
-
-import com.keenwrite.ExportFormat;
-import com.keenwrite.processors.markdown.tex.TeXInlineDelimiterProcessor;
-import com.keenwrite.processors.markdown.tex.TexNodeRenderer.Factory;
-import com.vladsch.flexmark.html.HtmlRenderer;
-import com.vladsch.flexmark.parser.Parser;
-import com.vladsch.flexmark.util.data.MutableDataHolder;
-import com.vladsch.flexmark.util.misc.Extension;
-import org.jetbrains.annotations.NotNull;
-
-import static com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension;
-import static com.vladsch.flexmark.parser.Parser.ParserExtension;
-
-/**
- * Responsible for wrapping delimited TeX code in Markdown into an XML element
- * that the HTML renderer can handle. For example, {@code $E=mc^2$} becomes
- * {@code <tex>E=mc^2</tex>} when passed to HTML renderer. The HTML renderer
- * is responsible for converting the TeX code for display. This avoids inserting
- * SVG code into the Markdown document, which the parser would then have to
- * iterate---a <em>very</em> wasteful operation that impacts front-end
- * performance.
- */
-public class TeXExtension implements ParserExtension, HtmlRendererExtension {
- /**
- * Controls how the node renderer produces TeX code within HTML output.
- */
- private final ExportFormat mExportFormat;
-
- /**
- * Creates an extension capable of handling delimited TeX code in Markdown.
- *
- * @return The new {@link TeXExtension}, never {@code null}.
- */
- public static TeXExtension create( final ExportFormat format ) {
- return new TeXExtension( format );
- }
-
- /**
- * Force using the {@link #create(ExportFormat)} method for consistency with
- * the other {@link Extension} creation invocations.
- */
- private TeXExtension( final ExportFormat exportFormat ) {
- mExportFormat = exportFormat;
- }
-
- /**
- * Adds the TeX extension for HTML document export types.
- *
- * @param builder The document builder.
- * @param rendererType Indicates the document type to be built.
- */
- @Override
- public void extend( @NotNull final HtmlRenderer.Builder builder,
- @NotNull final String rendererType ) {
- if( "HTML".equalsIgnoreCase( rendererType ) ) {
- builder.nodeRendererFactory( new Factory( mExportFormat ) );
- }
- }
-
- @Override
- public void extend( final Parser.Builder builder ) {
- builder.customDelimiterProcessor( new TeXInlineDelimiterProcessor() );
- }
-
- @Override
- public void rendererOptions( @NotNull final MutableDataHolder options ) {
- }
-
- @Override
- public void parserOptions( final MutableDataHolder options ) {
- }
-}