Dave Jarvis' Repositories

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

Adds unit test, isolates xref parsing

AuthorDaveJarvis <email>
Date2023-11-20 23:13:29 GMT-0800
Commitc334bfecbc671ec3443ad04b318e9b8c4f315617
Parentb7c3eb9
Delta291 lines added, 158 lines removed, 133-line increase
src/test/java/com/keenwrite/processors/markdown/extensions/references/CrossReferencesExtensionTest.java
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import java.util.List;
+import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
+@SuppressWarnings( "SpellCheckingInspection" )
public class CrossReferencesExtensionTest {
- private Parser mParser;
- private HtmlRenderer mRenderer;
-
- @BeforeEach
- public void setup() {
+ @ParameterizedTest
+ @MethodSource( "testDocuments" )
+ public void test_References_Documents_Html(
+ final String input,
+ final String expected ) {
final var extension = new CrossReferenceExtension();
final var pBuilder = Parser.builder();
final var hBuilder = HtmlRenderer.builder();
final var extensions = List.of( extension );
pBuilder.extensions( extensions );
hBuilder.extensions( extensions );
-
- mParser = pBuilder.build();
- mRenderer = hBuilder.build();
- }
- @Test
- public void test_References_SingularReferences_ReferencesAndPointers() {
- final var document = mParser.parse(
- """
- {#fig:cats} [@fig:cats]
- {#table:dogs} [@table:dogs]
- {#life:dolphins} [@life:dolphins]
- """
- );
+ final var parser = pBuilder.build();
+ final var renderer = hBuilder.build();
- final String expected =
- """
- <p><a data-type="fig" name="cats" /> <a data-type="fig" href="#cats" />
- <a data-type="table" name="dogs" /> <a data-type="table" href="#dogs" />
- <a data-type="life" name="dolphins" /> <a data-type="life" href="#dolphins" /></p>
- """;
- final var actual = mRenderer.render( document );
+ final var document = parser.parse( input );
+ final var actual = renderer.render( document );
assertEquals( expected, actual );
}
- @Test
- @SuppressWarnings( "SpellCheckingInspection" )
- public void test_References_DocumentReferences_ReferencesAndPointers() {
- final var document = mParser.parse(
- """
- Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- {#fig:cats} Sed do eiusmod tempor incididunt ut
- labore et dolore magna aliqua. Ut enim ad minim veniam,
- quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat. [@fig:cats]
+ private static Stream<Arguments> testDocuments() {
+ return Stream.of(
+ Arguments.of(
+ """
+ {#fig:cats} [@fig:cats]
+ {#table:dogs} [@table:dogs]
+ {#life:dolphins} [@life:dolphins]
+ """,
+ """
+ <p><a data-type="fig" name="cats" /> <a data-type="fig" href="#cats" />
+ <a data-type="table" name="dogs" /> <a data-type="table" href="#dogs" />
+ <a data-type="life" name="dolphins" /> <a data-type="life" href="#dolphins" /></p>
+ """ ),
+ Arguments.of(
+ """
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ {#fig:cats} Sed do eiusmod tempor incididunt ut
+ labore et dolore magna aliqua. Ut enim ad minim veniam,
+ quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. [@fig:cats]
+ """,
+ """
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ <a data-type="fig" name="cats" /> Sed do eiusmod tempor incididunt ut
+ labore et dolore magna aliqua. Ut enim ad minim veniam,
+ quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. <a data-type="fig" href="#cats" /></p>
+ """
+ ),
+ Arguments.of(
+ """
+ {#日本:w0mbatß}
+ [@日本:w0mbatß]
+ [@日本:w0mbatß]
+ [@日本:w0mbatß]
+ """,
+ """
+ <p><a data-type="日本" name="w0mbatß" />
+ <a data-type="日本" href="#w0mbatß" />
+ <a data-type="日本" href="#w0mbatß" />
+ <a data-type="日本" href="#w0mbatß" /></p>
+ """
+ ),
+ Arguments.of(
+ """
+ {#note:advancement}
+
+ Advancement isn't measured by the ingenuity of inventions, but
+ by humanity's ability to anticipate and forfend dire aftermaths
+ *before* using them.
+
+ [@note:advancement]
+
+ To what end?
+ """,
"""
+ <p><a data-type="note" name="advancement" /></p>
+ <p>Advancement isn't measured by the ingenuity of inventions, but
+ by humanity's ability to anticipate and forfend dire aftermaths
+ <em>before</em> using them.</p>
+ <p><a data-type="note" href="#advancement" /></p>
+ <p>To what end?</p>
+ """
+ )
);
-
- final var expected =
- """
- <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- <a data-type="fig" name="cats" /> Sed do eiusmod tempor incididunt ut
- labore et dolore magna aliqua. Ut enim ad minim veniam,
- quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat. <a data-type="fig" href="#cats" /></p>
- """;
-
- final var actual = mRenderer.render( document );
-
- assertEquals( expected, actual );
}
}
src/main/java/com/keenwrite/processors/markdown/extensions/references/AnchorNameNode.java
import com.vladsch.flexmark.ast.DelimitedNodeImpl;
-import com.vladsch.flexmark.parser.core.delimiter.Delimiter;
import com.vladsch.flexmark.util.sequence.BasedSequence;
import org.jetbrains.annotations.NotNull;
-
-import java.util.regex.Pattern;
-
-import static java.util.regex.Pattern.compile;
/**
* Responsible for writing HTML anchor names in the form
* {@code <a data-type="..." name="name" />}, where {@code name} can be
* referred to by a cross-reference.
*
* @see AnchorXrefNode
*/
class AnchorNameNode extends DelimitedNodeImpl implements CrossReferenceNode {
- private static final String REGEX_ANCHOR = "#(\\w+):(\\w+)";
- private static final Pattern PATTERN_ANCHOR = compile( REGEX_ANCHOR );
private BasedSequence mOpeningMarker = BasedSequence.EMPTY;
private BasedSequence mClosingMarker = BasedSequence.EMPTY;
- private String mTypeName = "";
- private String mIdName = "";
+ private BasedSequenceNameParser mParser;
- public AnchorNameNode( final Delimiter opener, final Delimiter closer ) {}
+ public AnchorNameNode() {}
@Override
public String getTypeName() {
- return mTypeName;
+ return mParser.getTypeName();
}
@Override
public String getIdName() {
- return mIdName;
+ return mParser.getIdName();
}
@Override
public void setText( final BasedSequence text ) {
- final var matcher = PATTERN_ANCHOR.matcher( text.toString() );
-
- if( matcher.find() ) {
- mTypeName = matcher.group( 1 );
- mIdName = matcher.group( 2 );
- }
+ mParser = BasedSequenceNameParser.parse( text );
}
src/main/java/com/keenwrite/processors/markdown/extensions/references/AnchorXrefInlineParser.java
-/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
- *
- * SPDX-License-Identifier: MIT
- */
-package com.keenwrite.processors.markdown.extensions.references;
-
-import com.vladsch.flexmark.parser.InlineParserExtensionFactory;
-import com.vladsch.flexmark.parser.delimiter.DelimiterProcessor;
-import com.vladsch.flexmark.parser.internal.InlineParserImpl;
-import com.vladsch.flexmark.parser.internal.LinkRefProcessorData;
-import com.vladsch.flexmark.util.data.DataHolder;
-
-import java.util.BitSet;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-import static java.util.regex.Pattern.compile;
-
-final class AnchorXrefInlineParser extends InlineParserImpl {
-
- private static final String REGEX_REFERENCE = "@(\\w+):(\\w+)";
- private static final Pattern PATTERN_REFERENCE = compile( REGEX_REFERENCE );
-
- private boolean mFoundReference;
-
- AnchorXrefInlineParser(
- final DataHolder options,
- final BitSet specialCharacters,
- final BitSet delimiterCharacters,
- final Map<Character, DelimiterProcessor> delimiterProcessors,
- final LinkRefProcessorData referenceLinkProcessors,
- final List<InlineParserExtensionFactory> inlineParserExtensions ) {
- super(
- options,
- specialCharacters,
- delimiterCharacters,
- delimiterProcessors,
- referenceLinkProcessors,
- inlineParserExtensions
- );
- }
-
- @Override
- public boolean parseOpenBracket() {
- mFoundReference = peek( 1 ) == '@';
-
- return super.parseOpenBracket();
- }
-
- @Override
- public boolean parseCloseBracket() {
- final boolean foundBracket = super.parseCloseBracket();
-
- if( mFoundReference ) {
- final var blockNode = getBlock();
- final var linkRef = blockNode.getLastChild();
-
- if( linkRef != null ) {
- final var text = linkRef.getChildChars();
- final var matcher = PATTERN_REFERENCE.matcher( text );
-
- if( matcher.find() ) {
- final var typeName = matcher.group( 1 );
- final var idName = matcher.group( 2 );
- final var xref = new AnchorXrefNode( typeName, idName );
-
- linkRef.unlink();
- blockNode.appendChild( xref );
- }
- }
- }
-
- return foundBracket;
- }
-}
src/main/java/com/keenwrite/processors/markdown/extensions/references/AnchorXrefProcessorFactory.java
+/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+package com.keenwrite.processors.markdown.extensions.references;
+
+import com.vladsch.flexmark.parser.LinkRefProcessor;
+import com.vladsch.flexmark.parser.LinkRefProcessorFactory;
+import com.vladsch.flexmark.util.ast.Document;
+import com.vladsch.flexmark.util.ast.Node;
+import com.vladsch.flexmark.util.data.DataHolder;
+import com.vladsch.flexmark.util.sequence.BasedSequence;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Responsible for processing {@code [@type:id]} anchors.
+ */
+class AnchorXrefProcessorFactory implements LinkRefProcessorFactory {
+ private final LinkRefProcessor mProcessor = new AnchorLinkRefProcessor();
+
+ @Override
+ public boolean getWantExclamationPrefix( @NotNull final DataHolder options ) {
+ return false;
+ }
+
+ @Override
+ public int getBracketNestingLevel( @NotNull final DataHolder options ) {
+ return 0;
+ }
+
+ @NotNull
+ @Override
+ public LinkRefProcessor apply( @NotNull final Document document ) {
+ return mProcessor;
+ }
+
+ private static class AnchorLinkRefProcessor implements LinkRefProcessor {
+
+ @Override
+ public boolean getWantExclamationPrefix() {
+ return false;
+ }
+
+ @Override
+ public int getBracketNestingLevel() {
+ return 0;
+ }
+
+ @Override
+ public boolean isMatch( @NotNull final BasedSequence nodeChars ) {
+ return nodeChars.indexOf( '@' ) == 1;
+ }
+
+ @NotNull
+ @Override
+ public Node createNode( @NotNull final BasedSequence nodeChars ) {
+ return BasedSequenceXrefParser.parse( nodeChars ).toNode();
+ }
+
+ @NotNull
+ @Override
+ public BasedSequence adjustInlineText(
+ @NotNull final Document document,
+ @NotNull final Node node ) {
+ return BasedSequence.EMPTY;
+ }
+
+ @Override
+ public boolean allowDelimiters(
+ @NotNull final BasedSequence chars,
+ @NotNull final Document document,
+ @NotNull final Node node ) {
+ return false;
+ }
+
+ @Override
+ public void updateNodeElements(
+ @NotNull final Document document,
+ @NotNull final Node node ) {}
+ }
+}
src/main/java/com/keenwrite/processors/markdown/extensions/references/BasedSequenceNameParser.java
+/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+package com.keenwrite.processors.markdown.extensions.references;
+
+import com.vladsch.flexmark.util.sequence.BasedSequence;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.util.regex.Pattern.UNICODE_CHARACTER_CLASS;
+import static java.util.regex.Pattern.compile;
+
+class BasedSequenceNameParser extends BasedSequenceParser {
+ private static final String REGEX = STR. "#\{ REGEX_INNER }" ;
+ private static final Pattern PATTERN = compile(
+ REGEX, UNICODE_CHARACTER_CLASS
+ );
+
+ private BasedSequenceNameParser( final String text ) {
+ super( text );
+ }
+
+ @Override
+ Matcher createMatcher( final String text ) {
+ return PATTERN.matcher( text );
+ }
+
+ static BasedSequenceNameParser parse( final BasedSequence chars ) {
+ return new BasedSequenceNameParser( chars.toString() );
+ }
+}
src/main/java/com/keenwrite/processors/markdown/extensions/references/BasedSequenceParser.java
+/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+package com.keenwrite.processors.markdown.extensions.references;
+
+import java.util.regex.Matcher;
+
+abstract class BasedSequenceParser {
+ /**
+ * Shared syntax between subclasses.
+ */
+ static final String REGEX_INNER = "(\\p{Alnum}+):(\\p{Alnum}+)";
+
+ private final String mTypeName;
+ private final String mIdName;
+
+ BasedSequenceParser( final String text ) {
+ final var matcher = createMatcher( text );
+
+ if( matcher.find() ) {
+ mTypeName = matcher.group( 1 );
+ mIdName = matcher.group( 2 );
+ }
+ else {
+ mTypeName = null;
+ mIdName = null;
+ }
+ }
+
+ abstract Matcher createMatcher( final String text );
+
+ String getTypeName() {
+ return mTypeName;
+ }
+
+ String getIdName() {
+ return mIdName;
+ }
+}
src/main/java/com/keenwrite/processors/markdown/extensions/references/BasedSequenceXrefParser.java
+/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+package com.keenwrite.processors.markdown.extensions.references;
+
+import com.vladsch.flexmark.util.ast.Node;
+import com.vladsch.flexmark.util.sequence.BasedSequence;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static com.keenwrite.processors.markdown.extensions.EmptyNode.EMPTY_NODE;
+import static java.util.regex.Pattern.UNICODE_CHARACTER_CLASS;
+import static java.util.regex.Pattern.compile;
+
+class BasedSequenceXrefParser extends BasedSequenceParser {
+ private static final String REGEX = STR. "\\[@\{ REGEX_INNER }]" ;
+ private static final Pattern PATTERN = compile(
+ REGEX, UNICODE_CHARACTER_CLASS
+ );
+
+ BasedSequenceXrefParser( final String text ) {
+ super( text );
+ }
+
+ @Override
+ Matcher createMatcher( final String text ) {
+ return PATTERN.matcher( text );
+ }
+
+ static BasedSequenceXrefParser parse( final BasedSequence chars ) {
+ return new BasedSequenceXrefParser( chars.toString() );
+ }
+
+ Node toNode() {
+ final var typeName = getTypeName();
+ final var idName = getIdName();
+
+ return typeName == null || idName == null
+ ? EMPTY_NODE
+ : new AnchorXrefNode( typeName, idName );
+ }
+}
src/main/java/com/keenwrite/processors/markdown/extensions/references/CrossReferenceExtension.java
@Override
public void extend( final Parser.Builder builder ) {
- builder.customInlineParserFactory( AnchorXrefInlineParser::new );
+ builder.linkRefProcessorFactory( new AnchorXrefProcessorFactory() );
builder.customDelimiterProcessor( new AnchorNameDelimiterProcessor() );
}
src/main/java/com/keenwrite/processors/markdown/extensions/references/CrossReferenceNode.java
String getRefAttrName();
- default String toHtml() {
- final String typeName = getTypeName();
- final String idName = getIdName();
-
- return toHtml( typeName, idName );
- }
-
- default String toHtml( final String type, final String id ) {
- return STR.
- "<a data-type=\"\{ type }\" \{ getRefAttrName() }=\"\{ id }\" />" ;
- }
-
+ /**
+ * Writes the HTML representation for this cross-reference node.
+ *
+ * @param html The HTML tag is written to the {@link HtmlWriter}.
+ */
default void write( final HtmlWriter html ) {
- html.raw( toHtml() );
+ final var type = getTypeName();
+ final var id = getIdName();
+ final var attr = getRefAttrName();
+
+ html.raw( STR. "<a data-type=\"\{ type }\" \{ attr }=\"\{ id }\" />" );
}
}