I'm trying to write a small extension to flexmark. I want to create a
custom AttributeProvider and am trying to work through the example
shown here.
My issue is translating two classes into Clojure.
I've separated the two relevant example classes into separate Java source files and translated the demonstration program into a clojure test.
The SampleAttributeProvider in Java:
package cwiki.util;
import com.vladsch.flexmark.ast.Link;
import com.vladsch.flexmark.ast.Node;
import com.vladsch.flexmark.html.AttributeProvider;
import com.vladsch.flexmark.html.AttributeProviderFactory;
import com.vladsch.flexmark.html.IndependentAttributeProviderFactory;
import com.vladsch.flexmark.html.renderer.AttributablePart;
import com.vladsch.flexmark.html.renderer.LinkResolverContext;
import com.vladsch.flexmark.util.html.Attributes;
class SampleAttributeProvider implements AttributeProvider {
@Override
public void setAttributes(final Node node, final AttributablePart part, final Attributes attributes) {
if (node instanceof Link && part == AttributablePart.LINK) {
Link link = (Link) node;
if (link.getText().equals("...")) {
attributes.replaceValue("target", "_top");
}
}
}
static AttributeProviderFactory Factory() {
return new IndependentAttributeProviderFactory() {
@Override
public AttributeProvider create(LinkResolverContext context) {
//noinspection ReturnOfInnerClass
return new SampleAttributeProvider();
}
};
}
}
The SampleExtension in Java.
package cwiki.util;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.util.options.MutableDataHolder;
public class SampleExtension implements HtmlRenderer.HtmlRendererExtension {
@Override
public void rendererOptions(final MutableDataHolder options) {
// add any configuration settings to options you want to apply to everything, here
}
@Override
public void extend(final HtmlRenderer.Builder rendererBuilder, final String rendererType) {
rendererBuilder.attributeProviderFactory(cwiki.util.SampleAttributeProvider.Factory());
}
public static SampleExtension create() {
return new SampleExtension();
}
}
Translating the demonstraton program in the example is straightforward. The test file in Clojure:
(ns cwiki.test.util.CWikiAttributeProviderTest
(:require [clojure.test :refer :all])
(:import (com.vladsch.flexmark.util.options MutableDataSet)
(com.vladsch.flexmark.parser Parser Parser$Builder)
(com.vladsch.flexmark.html HtmlRenderer HtmlRenderer$Builder)
(cwiki.util SampleExtension)
(java.util ArrayList)))
(defn commonmark
[markdown]
(let [options (-> (MutableDataSet.)
(.set Parser/EXTENSIONS
(ArrayList. [(SampleExtension/create)]))
(.set HtmlRenderer/SOFT_BREAK "<br/>"))
parser (.build ^Parser$Builder (Parser/builder options))
document (.parse ^Parser parser ^String markdown)
renderer (.build ^HtmlRenderer$Builder (HtmlRenderer/builder options))]
(.render renderer document)))
(deftest cwiki-attribute-provider-test
(testing "The ability of the CWikiAttributeProvider to place the correct attributes in wikilinks."
(let [test-output (commonmark "[...](http://github.com/vsch/flexmark-java)")]
(is (= test-output
"<p><a href=\"http://github.com/vsch/flexmark-java\" target=\"_top\">...</a></p>\n")))))
The test runs and passes using the two Java classes above.
I've been able to translate the SampleAttributeProvider to Clojure. It compiles and produces class files. I haven't been able to actually run and test it because...
I'm having problems translating the extension class from the example. Here is what I've come up with so far. (Class names have been changed to avoid collisions.):
(ns cwiki.util.CWikiAttributeProviderExtension
(:gen-class
:implements [com.vladsch.flexmark.html.HtmlRenderer$HtmlRendererExtension]
:methods [^:static [create [] cwiki.util.CWikiAttributeProviderExtension]])
(:import (com.vladsch.flexmark.html HtmlRenderer$Builder)
(com.vladsch.flexmark.util.options MutableDataHolder)
(cwiki.util CWikiAttributeProvider CWikiAttributeProviderExtension)))
(defn -rendererOptions
[this ^MutableDataHolder options]
; Add any configuration option that you want to apply to everything here.
)
(defn -extend
[this ^HtmlRenderer$Builder rendererBuilder ^String rendererType]
(.attributeProviderFactory rendererBuilder (CWikiAttributeProvider.)))
(defn ^CWikiAttributeProviderExtension -create [this]
(CWikiAttributeProviderExtension.))
Attempting to compile this with
$ lein compile CWikiAttributeProviderExtension, it produces an error message starting with:
Compiling cwiki.util.CWikiAttributeProviderExtension
java.lang.ClassNotFoundException: cwiki.util.CWikiAttributeProviderExtension, compiling:(cwiki/util/CWikiAttributeProviderExtension.clj:1:1)
Exception in thread "main" java.lang.ClassNotFoundException: cwiki.util.CWikiAttributeProviderExtension, compiling:(cwiki/util/CWikiAttributeProviderExtension.clj:1:1)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:7010)
at clojure.lang.Compiler.analyze(Compiler.java:6773)
at clojure.lang.Compiler.analyze(Compiler.java:6729)
at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:6100)
at clojure.lang.Compiler$TryExpr$Parser.parse(Compiler.java:2307)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:7003)
...
The IDE (Cursive on IntelliJ) shows a warning message about not being able to resolve the create method in the :gen-class section, but I can't see anything wrong with the declaration or implementation. This translation is similar to what I have used previously in other programs to build a Java class that adds a method to an object extending a Java interface. It is similar to what I used to create the other relevant class in the example. But clearly something is wrong.
Attempting to just use the translated CWikiAttributeProvider without translating the extension class causes the tool chain to get confused about first compiling an object in Clojure, then the Java parts, then the rest of the Clojure program. So it seems I must translate both classes at once or not at all.
Just for completeness, here's the translated attribute provider class in Clojure.
(ns cwiki.util.CWikiAttributeProvider
(:gen-class
:implements [com.vladsch.flexmark.html.AttributeProvider]
:methods [^:static [Factory [] com.vladsch.flexmark.html.AttributeProviderFactory]])
(:import (com.vladsch.flexmark.ast Node Link)
(com.vladsch.flexmark.html AttributeProviderFactory
IndependentAttributeProviderFactory)
(com.vladsch.flexmark.html.renderer AttributablePart
LinkResolverContext)
(com.vladsch.flexmark.util.html Attributes)
(cwiki.util CWikiAttributeProvider)))
(defn -setAttributes
[this ^Node node ^AttributablePart part ^Attributes attributes]
(when (and (= (type node) Link) (= part AttributablePart/LINK))
(let [title (.getText ^Link node)]
(println "node: " node)
(println "title: " title)
(when (= title "...")
(.replaceValue attributes "target" "_top")))))
(defn ^AttributeProviderFactory -Factory [this]
(proxy [IndependentAttributeProviderFactory] []
(create [^LinkResolverContext context]
;; noinspection ReturnOfInnerClass
(CWikiAttributeProvider.))))
You need a forward declaration for the CWikiAttributeProvider as a return value in gen-class. Declare the class in two steps at the top of your file: 1) with constructors only, and 2) with a full declaration including (static) methods and state.
(gen-class
:name cwiki.util.CWikiAttributeProviderExtension
:implements [com.vladsch.flexmark.html.HtmlRenderer$HtmlRendererExtension])
(gen-class
:name cwiki.util.CWikiAttributeProviderExtension
:implements [com.vladsch.flexmark.html.HtmlRenderer$HtmlRendererExtension]
:methods (^:static [create [] cwiki.util.CWikiAttributeProviderExtension]))
I was able to compile your example, using these gen-class statements, and following modifications:
cwiki.util.CWikiAttributeProvider) in method implementations and type hints, andthis reference from the static method (to match the gen-class declaration for the method).Note also that you need to compile the class ahead-of-time (AOT) when using it in a project.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With