Dave Jarvis' Repositories

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

Adds bracketed span extension

AuthorDaveJarvis <email>
Date2025-08-27 13:58:57 GMT-0700
Commitabd63933fee3523f9047789c80b2ca72855dc501
Parent7eeb127
Delta284 lines added, 0 lines removed, 284-line increase
src/main/java/com/keenwrite/processors/markdown/extensions/spans/BracketedSpanExtension.java
+/* Copyright 2025 White Magic Software, Ltd. -- All rights reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+package com.keenwrite.processors.markdown.extensions.spans;
+
+import com.keenwrite.processors.markdown.extensions.common.MarkdownRendererExtension;
+import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
+import com.vladsch.flexmark.parser.Parser.Builder;
+
+/**
+ * Extension for parsing and rendering bracketed span syntax:
+ * [text]{.class key="val" #id}
+ */
+public final class BracketedSpanExtension extends MarkdownRendererExtension {
+ private BracketedSpanExtension() {
+ }
+
+ public static BracketedSpanExtension create() {
+ return new BracketedSpanExtension();
+ }
+
+ @Override
+ public void extend( final Builder builder ) {
+ builder.customInlineParserExtensionFactory(
+ new BracketedSpanParserFactory()
+ );
+ }
+
+ @Override
+ protected NodeRendererFactory createNodeRendererFactory() {
+ return BracketedSpanRenderer.factory();
+ }
+}
src/main/java/com/keenwrite/processors/markdown/extensions/spans/BracketedSpanNode.java
+/* Copyright 2025 White Magic Software, Ltd. -- All rights reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+package com.keenwrite.processors.markdown.extensions.spans;
+
+import com.vladsch.flexmark.html.HtmlWriter;
+import com.vladsch.flexmark.html.renderer.NodeRendererContext;
+import com.vladsch.flexmark.util.ast.Node;
+import com.vladsch.flexmark.util.sequence.BasedSequence;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+
+public class BracketedSpanNode extends Node {
+ private final String mClassName;
+ private final String mId;
+ private final Map<String, String> mAttributes;
+ private final BasedSequence mText;
+
+ public BracketedSpanNode(
+ final String className,
+ final String id,
+ final Map<String, String> attributes,
+ final BasedSequence text
+ ) {
+ this.mClassName = className;
+ this.mId = id;
+ this.mAttributes = attributes;
+ this.mText = text;
+ }
+
+ public void render(
+ final NodeRendererContext context,
+ final HtmlWriter html
+ ) {
+ html.raw( "<span" );
+
+ if( mClassName != null ) {
+ html.raw( " class=\"" ).raw( mClassName ).raw( "\"" );
+ }
+
+ if( mId != null ) {
+ html.raw( " id=\"" ).raw( mId ).raw( "\"" );
+ }
+
+ for( var entry : mAttributes.entrySet() ) {
+ html
+ .raw( " " ).raw( entry.getKey() )
+ .raw( "=\"" ).raw( entry.getValue() )
+ .raw( "\"" );
+ }
+
+ html.raw( ">" );
+
+ if( hasChildren() ) {
+ for( var child : getChildren() ) {
+ context.render( child );
+ }
+ }
+ else {
+ html.raw( mText.toString() );
+ }
+
+ html.raw( "</span>" );
+ }
+
+ @Override
+ public @NotNull BasedSequence[] getSegments() {
+ return new BasedSequence[ 0 ];
+ }
+}
src/main/java/com/keenwrite/processors/markdown/extensions/spans/BracketedSpanParser.java
+/* Copyright 2025 White Magic Software, Ltd. -- All rights reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+package com.keenwrite.processors.markdown.extensions.spans;
+
+import com.vladsch.flexmark.parser.InlineParser;
+import com.vladsch.flexmark.parser.InlineParserExtension;
+import com.vladsch.flexmark.parser.LightInlineParser;
+import com.vladsch.flexmark.util.sequence.BasedSequence;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.regex.Pattern;
+
+public class BracketedSpanParser implements InlineParserExtension {
+ private static final Pattern ATTR_PATTERN = Pattern.compile(
+ "(?:\\.([\\w-]+))|" +
+ "(?:#([\\w-]+))|" +
+ "(\\w+)=\"((?:\\\\\"|[^\"])*)\""
+ );
+
+ public BracketedSpanParser( final @NotNull LightInlineParser ignoredParser ) {
+ }
+
+ @Override
+ public boolean parse( final @NotNull LightInlineParser parser ) {
+ var result = false;
+
+ final var input = parser.getInput();
+ final var index = parser.getIndex();
+
+ if( input.charAt( index ) == '[' ) {
+ final var closingBracket = input.indexOf( ']', index );
+ final var braceStart = input.indexOf( '{', closingBracket );
+ final var braceEnd = input.indexOf( '}', braceStart );
+
+ if( closingBracket != -1 && braceStart != -1 && braceEnd != -1 ) {
+ final var content = input.subSequence( index + 1, closingBracket );
+ final var span = getBracketedSpan(
+ input, braceStart, braceEnd,
+ content
+ );
+
+ span.setChars( input.subSequence( index, braceEnd + 1 ) );
+ parser.flushTextNode();
+ parser.getBlock().appendChild( span );
+ parser.setIndex( braceEnd + 1 );
+
+ result = true;
+ }
+ }
+
+ return result;
+ }
+
+ private static @NotNull BracketedSpanNode getBracketedSpan(
+ final BasedSequence input,
+ final int braceStart,
+ final int braceEnd,
+ final BasedSequence content
+ ) {
+ final var attrText =
+ input.subSequence( braceStart + 1, braceEnd ).toString();
+ final var attrs = new HashMap<String, String>();
+ final var classes = new ArrayList<String>();
+ String id = null;
+
+ final var matcher = ATTR_PATTERN.matcher( attrText );
+ while( matcher.find() ) {
+ if( matcher.group( 1 ) != null ) {
+ classes.add( matcher.group( 1 ) );
+ }
+ else if( matcher.group( 2 ) != null ) {
+ id = matcher.group( 2 );
+ }
+ else if( matcher.group( 3 ) != null && matcher.group( 4 ) != null ) {
+ final var key = matcher.group( 3 );
+ final var rawValue = matcher.group( 4 ).replace( "\\\"", "\"" );
+ attrs.put( key, rawValue );
+ }
+ }
+
+ final var className = classes.isEmpty() ? null : String.join(
+ " ",
+ classes
+ );
+ return new BracketedSpanNode( className, id, attrs, content );
+ }
+
+ @Override
+ public void finalizeDocument( final @NotNull InlineParser parser ) {
+ }
+
+ @Override
+ public void finalizeBlock( final @NotNull InlineParser parser ) {
+ }
+}
src/main/java/com/keenwrite/processors/markdown/extensions/spans/BracketedSpanParserFactory.java
+/* Copyright 2025 White Magic Software, Ltd. -- All rights reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+package com.keenwrite.processors.markdown.extensions.spans;
+
+import com.vladsch.flexmark.parser.InlineParserExtension;
+import com.vladsch.flexmark.parser.InlineParserExtensionFactory;
+import com.vladsch.flexmark.parser.LightInlineParser;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Set;
+
+/**
+ * Factory for creating the bracketed span inline parser.
+ */
+public final class BracketedSpanParserFactory implements InlineParserExtensionFactory {
+ public BracketedSpanParserFactory() {
+ }
+
+ /**
+ * Characters that trigger this parser. In this case, '[' starts a
+ * bracketed span.
+ */
+ @Override
+ public @NotNull CharSequence getCharacters() {
+ return "[";
+ }
+
+ /**
+ * Creates a new instance of the parser.
+ */
+ @Override
+ public @NotNull InlineParserExtension apply( @NotNull LightInlineParser parser ) {
+ return new BracketedSpanParser( parser );
+ }
+
+ @Override
+ public @NotNull Set<Class<?>> getAfterDependents() {
+ return Set.of();
+ }
+
+ @Override
+ public @NotNull Set<Class<?>> getBeforeDependents() {
+ return Set.of();
+ }
+
+ @Override
+ public boolean affectsGlobalScope() {
+ return false;
+ }
+}
src/main/java/com/keenwrite/processors/markdown/extensions/spans/BracketedSpanRenderer.java
+/* Copyright 2025 White Magic Software, Ltd. -- All rights reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+package com.keenwrite.processors.markdown.extensions.spans;
+
+import com.vladsch.flexmark.html.renderer.NodeRenderer;
+import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
+import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class BracketedSpanRenderer implements NodeRenderer {
+ @Override
+ public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
+ return Collections.singleton(
+ new NodeRenderingHandler<>(
+ BracketedSpanNode.class,
+ BracketedSpanNode::render
+ )
+ );
+ }
+
+ public static NodeRendererFactory factory() {
+ return _ -> new BracketedSpanRenderer();
+ }
+}