Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add getImages() to Catalog
Browse files Browse the repository at this point in the history
Current implementation works correctly when using load+getContent.
But not when using convert which fails.
Same applies when trying to integrate with a custom converter, note
I see the same behauviour for Asciidoctor Ruby:
that is, load+content does not populate the catalog, only calling convert
abelsromero committed Apr 10, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 8853c8a commit 78efe30
Showing 10 changed files with 413 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -15,6 +15,14 @@ public interface Catalog {
*/
List<Footnote> getFootnotes();

/**
* Retrieves the images from the source document.
* Note that inline images are only available after `Document.getContent()` has been called.
*
* @return images occurring in document.
*/
List<ImageReference> getImages();

/**
* Refs is a map of asciidoctor ids to asciidoctor document elements.
*
@@ -29,4 +37,5 @@ public interface Catalog {
* @return a map of ids to elements that asciidoctor has collected from the document.
*/
Map<String, Object> getRefs();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.asciidoctor.ast;

public interface ImageReference {

String getTarget();

String getImagesdir();
}
Original file line number Diff line number Diff line change
@@ -2,17 +2,18 @@

import org.asciidoctor.ast.Catalog;
import org.asciidoctor.ast.Footnote;
import org.asciidoctor.ast.ImageReference;
import org.asciidoctor.jruby.internal.RubyHashMapDecorator;
import org.jruby.RubyArray;
import org.jruby.RubyHash;
import org.jruby.RubyStruct;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class CatalogImpl implements Catalog {
public class CatalogImpl implements Catalog {

private final Map<String, Object> catalog;

@@ -22,17 +23,23 @@ public CatalogImpl(Map<String, Object> catalog) {

@Override
public List<Footnote> getFootnotes() {
RubyArray<?> rubyFootnotes = (RubyArray<?>) catalog.get("footnotes");
List<Footnote> footnotes = new ArrayList<>();
for (Object f : rubyFootnotes) {
footnotes.add(FootnoteImpl.getInstance((RubyStruct) f));
}
return footnotes;
return (List<Footnote>) ((RubyArray) catalog.get("footnotes"))
.stream()
.map(o -> FootnoteImpl.getInstance((RubyStruct) o))
.collect(Collectors.toUnmodifiableList());
}

@Override
public List<ImageReference> getImages() {
return (List<ImageReference>) ((RubyArray) catalog.get("images"))
.stream()
.map(o -> ImageReferenceImpl.getInstance((RubyStruct) o))
.collect(Collectors.toUnmodifiableList());
}

@Override
public Map<String, Object> getRefs() {
Map <String,Object> refs = new RubyHashMapDecorator((RubyHash) catalog.get("refs"), String.class);
Map<String, Object> refs = new RubyHashMapDecorator((RubyHash) catalog.get("refs"), String.class);
return Collections.unmodifiableMap(refs);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package org.asciidoctor.jruby.ast.impl;

import org.jruby.RubyStruct;

import org.asciidoctor.ast.Footnote;
import org.jruby.RubyStruct;
import org.jruby.javasupport.JavaEmbedUtils;

public class FootnoteImpl implements Footnote {
@@ -15,11 +14,11 @@ public class FootnoteImpl implements Footnote {
private String id;
private String text;

private static Object aref(RubyStruct s, String key) {
return JavaEmbedUtils.rubyToJava(s.aref(s.getRuntime().newString(key)));
private static Object aref(RubyStruct s, String key) {
return JavaEmbedUtils.rubyToJava(s.aref(s.getRuntime().newString(key)));
}

public static Footnote getInstance(Long index, String id, String text) {
public static Footnote getInstance(Long index, String id, String text) {
FootnoteImpl footnote = new FootnoteImpl();
footnote.index = index;
footnote.id = id;
@@ -35,11 +34,17 @@ public static Footnote getInstance(RubyStruct rubyFootnote) {
}

@Override
public Long getIndex() { return index; }
public Long getIndex() {
return index;
}

@Override
public String getId() { return id; }
public String getId() {
return id;
}

@Override
public String getText() { return text; }
public String getText() {
return text;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.asciidoctor.jruby.ast.impl;

import org.asciidoctor.ast.ImageReference;
import org.jruby.RubyStruct;
import org.jruby.javasupport.JavaEmbedUtils;

public class ImageReferenceImpl implements ImageReference {

private static final String IMAGESDIR_KEY_NAME = "imagesdir";
private static final String TARGET_KEY_NAME = "target";

private final String target;
private final String imagesdir;

private ImageReferenceImpl(String target, String imagesdir) {
this.target = target;
this.imagesdir = imagesdir;
}

static ImageReference getInstance(RubyStruct rubyFootnote) {
final String target = (String) aref(rubyFootnote, TARGET_KEY_NAME);
final String imagesdir = (String) aref(rubyFootnote, IMAGESDIR_KEY_NAME);
return new ImageReferenceImpl(target, imagesdir);
}

private static Object aref(RubyStruct s, String key) {
return JavaEmbedUtils.rubyToJava(s.aref(s.getRuntime().newString(key)));
}

@Override
public String getTarget() {
return target;
}

@Override
public String getImagesdir() {
return imagesdir;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.asciidoctor.converter

import org.asciidoctor.Asciidoctor
import org.asciidoctor.OptionsBuilder
import org.asciidoctor.Options
import org.asciidoctor.ast.ContentNode
import org.asciidoctor.ast.Document
import org.asciidoctor.ast.Footnote
@@ -13,22 +13,23 @@ import org.junit.runner.RunWith
import spock.lang.Specification

import static org.hamcrest.Matchers.contains
import static org.hamcrest.Matchers.empty
import static org.hamcrest.Matchers.samePropertyValuesAs
import static org.junit.Assert.assertThat

/**
* Tests that footnotes can be accessed from converter.
*
* Note that it is a current limitation of asciidoctor that footnotes are not available until
* after they have been converted for the document.
* Note that it is a current limitation of asciidoctor that footnotes are not
* available until after they have been converted for the document.
*/
@RunWith(ArquillianSputnik)
class WhenFootnotesAreUsed extends Specification {

static final String CONVERTER_BACKEND = 'footnote'

@ArquillianResource
private Asciidoctor asciidoctor

private static List<Footnote> footnotesBeforeConvert
private static List<Footnote> footnotesAfterConvert

@@ -42,15 +43,15 @@ class WhenFootnotesAreUsed extends Specification {
* we simply want to force the conversion to verify that footnotes
* are populated.
*/

@Override
String convert(ContentNode node, String transform, Map<Object, Object> opts) {
if (node instanceof Document) {
def doc = (Document) node
footnotesBeforeConvert = doc.catalog.footnotes.collect()
footnotesBeforeConvert = doc.catalog.footnotes
doc.content
footnotesAfterConvert = doc.catalog.footnotes.collect()
}
else if (node instanceof StructuralNode) {
footnotesAfterConvert = doc.catalog.footnotes
} else if (node instanceof StructuralNode) {
((StructuralNode) node).content
}
}
@@ -63,7 +64,8 @@ class WhenFootnotesAreUsed extends Specification {
}

def convert(String document) {
asciidoctor.convert(document, OptionsBuilder.options().backend(CONVERTER_BACKEND))
def options = Options.builder().backend(CONVERTER_BACKEND).build()
asciidoctor.convert(document, options)
}

def footnote(Long index, String id, String text) {
@@ -78,8 +80,8 @@ class WhenFootnotesAreUsed extends Specification {
convert(document)

then:
assertThat(footnotesBeforeConvert, empty())
assertThat(footnotesAfterConvert, empty())
footnotesBeforeConvert.isEmpty()
footnotesAfterConvert.isEmpty()
}

def 'when a footnote is is in source doc, it should be accessible from converter'() {
@@ -90,9 +92,9 @@ class WhenFootnotesAreUsed extends Specification {
convert(document)

then:
assertThat(footnotesBeforeConvert, empty())
footnotesBeforeConvert.isEmpty()
assertThat(footnotesAfterConvert,
contains(samePropertyValuesAs(footnote(1,'fid','we shall find out!'))))
contains(samePropertyValuesAs(footnote(1, 'fid', 'we shall find out!'))))
}

def 'when footnotes are in source doc, they should be accessible from the converter'() {
@@ -108,9 +110,9 @@ An existing footnote can be referenced.footnote:myid1[]
convert(document)

then:
assertThat(footnotesBeforeConvert, empty())
footnotesBeforeConvert.isEmpty()
assertThat(footnotesAfterConvert,
contains(samePropertyValuesAs(footnote(1,'myid1','first footnote')),
samePropertyValuesAs(footnote(2, null, 'second footnote'))))
contains(samePropertyValuesAs(footnote(1, 'myid1', 'first footnote')),
samePropertyValuesAs(footnote(2, null, 'second footnote'))))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.asciidoctor.jruby.ast.impl;

import org.asciidoctor.ast.ImageReference;

public class TestImageReference implements ImageReference {

private final String target;
private final String imagesdir;

public TestImageReference(String target) {
this.target = target;
this.imagesdir = null;
}

public TestImageReference(String target, String imagesdir) {
this.target = target;
this.imagesdir = imagesdir;
}

@Override
public String getTarget() {
return target;
}

@Override
public String getImagesdir() {
return imagesdir;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package org.asciidoctor.jruby.internal;

import org.asciidoctor.Asciidoctor;
import org.asciidoctor.Attributes;
import org.asciidoctor.Options;
import org.asciidoctor.arquillian.api.Unshared;
import org.asciidoctor.ast.Document;
import org.asciidoctor.ast.ImageReference;
import org.asciidoctor.jruby.ast.impl.TestImageReference;
import org.asciidoctor.util.ClasspathResources;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.IOException;
import java.nio.file.Files;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(Arquillian.class)
public class WhenReadingImagesFromCatalogAsset {

@ArquillianResource
private ClasspathResources classpath;

@ArquillianResource(Unshared.class)
private Asciidoctor asciidoctor;

static final TestImageReference[] BLOCK_IMAGES = new TestImageReference[]{
new TestImageReference("images/block-image.jpg")
};

static final TestImageReference[] ALL_IMAGES = new TestImageReference[]{
new TestImageReference("images/block-image.jpg"),
new TestImageReference("images/inline-image.png")
};


@Test
public void shouldReturnEmptyWhenThereAreNoImages() {
final Options options = Options.builder()
.catalogAssets(true)
.build();

Document document = asciidoctor.load("= Hello", options);
List<ImageReference> images = document.getCatalog().getImages();

assertThat(images)
.isEmpty();
}

@Test
public void shouldReturnNullImagesDirWhenNotSet() {
final Options options = Options.builder()
.catalogAssets(true)
.build();
final String content = getAsciiDodWithImagesDocument();

Document document = asciidoctor.load(content, options);
List<ImageReference> images = document.getCatalog().getImages();

assertThat(images)
.usingRecursiveFieldByFieldElementComparator()
.containsExactly(BLOCK_IMAGES);
}

@Test
public void shouldReturnImagesDirWhenSet() {
final Options options = Options.builder()
.catalogAssets(true)
.attributes(Attributes.builder()
.imagesDir("some-path")
.build())
.build();
final String content = getAsciiDodWithImagesDocument();

Document document = asciidoctor.load(content, options);
List<ImageReference> images = document.getCatalog().getImages();

assertThat(images)
.usingRecursiveFieldByFieldElementComparator()
.containsExactly(new TestImageReference("images/block-image.jpg", "some-path"));
}

@Test
public void shouldNotCatalogInlineImagesWhenNotConverting() {
final Options options = Options.builder()
.catalogAssets(true)
.build();
final String content = getAsciiDodWithImagesDocument();

Document document = asciidoctor.load(content, options);

List<ImageReference> images = document.getCatalog().getImages();
assertThat(images)
.usingRecursiveFieldByFieldElementComparator()
.containsExactly(BLOCK_IMAGES);
}

@Test
public void shouldCatalogInlineImagesWhenProcessingContent() {
final Options options = Options.builder()
.catalogAssets(true)
.build();
final String content = getAsciiDodWithImagesDocument();

Document document = asciidoctor.load(content, options);
document.getContent();

List<ImageReference> images = document.getCatalog().getImages();
assertThat(images)
.usingRecursiveFieldByFieldElementComparator()
.containsExactlyInAnyOrder(ALL_IMAGES);
}

@Test
public void shouldNotCatalogInlineImagesWhenCatalogAssetsIsFalse() {
final Options options = Options.builder()
.catalogAssets(false)
.build();
final String content = getAsciiDodWithImagesDocument();

Document document = asciidoctor.load(content, options);
document.getContent();

List<ImageReference> images = document.getCatalog().getImages();
assertThat(images).isEmpty();
}

private String getAsciiDodWithImagesDocument() {
try {
return Files.readString(classpath.getResource("sample-with-images.adoc").toPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package org.asciidoctor.jruby.internal;

import org.asciidoctor.Asciidoctor;
import org.asciidoctor.Options;
import org.asciidoctor.arquillian.api.Unshared;
import org.asciidoctor.ast.*;
import org.asciidoctor.converter.StringConverter;
import org.asciidoctor.jruby.ast.impl.TestImageReference;
import org.asciidoctor.util.ClasspathResources;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.as;
import static org.assertj.core.api.Assertions.assertThat;

@RunWith(Arquillian.class)
public class WhenReadingImagesFromCatalogAssetFromConverter {

@ArquillianResource
private ClasspathResources classpath;

@ArquillianResource(Unshared.class)
private Asciidoctor asciidoctor;

static final String CONVERTER_BACKEND = "custom-backend";

static final TestImageReference[] ALL_IMAGES = new TestImageReference[]{
new TestImageReference("images/block-image.jpg"),
new TestImageReference("images/inline-image.png")
};

private static List<ImageReference> imagesBeforeConvert;
private static List<ImageReference> imagesAfterConvert;

@Before
public void beforeEach() {
final var javaConverterRegistry = asciidoctor.javaConverterRegistry();
javaConverterRegistry.converters().clear();
javaConverterRegistry.register(TestConverter.class, CONVERTER_BACKEND);

imagesBeforeConvert = List.of();
imagesAfterConvert = List.of();
}

@Test
public void shouldReturnEmptyWhenThereAreNoImages() {
final String content = "";

convert(content);

assertThat(imagesBeforeConvert).isEmpty();
assertThat(imagesAfterConvert).isEmpty();
}

@Test
public void shouldReturnEmptyWhenThereUsingLoad() {
final String content = getAsciiDodWithImagesDocument();

load(content);

assertThat(imagesBeforeConvert).isEmpty();
assertThat(imagesAfterConvert).isEmpty();
}

@Test
public void shouldReturnAllImages() {
final String content = getAsciiDodWithImagesDocument();

var options = Options.builder().backend(CONVERTER_BACKEND).build();
Document load = asciidoctor.load(content, options);
String content1 = (String) load.getContent();

Catalog catalog = load.getCatalog();
assertThat(imagesBeforeConvert).isEmpty();
assertThat(imagesAfterConvert)
.usingRecursiveFieldByFieldElementComparator()
.containsExactly(ALL_IMAGES);
}

private String convert(String document) {
var options = Options.builder().backend(CONVERTER_BACKEND).build();
return asciidoctor.convert(document, options);
}

private void load(String document) {
var options = Options.builder().backend(CONVERTER_BACKEND).build();
asciidoctor.load(document, options);
}

private String getAsciiDodWithImagesDocument() {
try {
return Files.readString(classpath.getResource("sample-with-images.adoc").toPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public static class TestConverter extends StringConverter {

public TestConverter(String backend, Map<String, Object> opts) {
super(backend, opts);
}

/*
* For this conversion test we do not care about the conversion result,
* we simply want to force the conversion to verify that footnotes
* are populated.
*/

@Override
public String convert(ContentNode node, String transform, Map<Object, Object> opts) {
String content = "";
if (node instanceof Document) {
var doc = (Document) node;
imagesBeforeConvert = doc.getCatalog().getImages();
content = (String) doc.getContent();
imagesAfterConvert = doc.getCatalog().getImages();
} else if (node instanceof StructuralNode) {
content = (String) ((StructuralNode) node).getContent();
}
return content;
}
}
}
9 changes: 9 additions & 0 deletions asciidoctorj-core/src/test/resources/sample-with-images.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
= This is a title

== A few images

A block image

image::images/block-image.jpg[]

An inlined image image:images/inline-image.png[] in the text.

0 comments on commit 78efe30

Please sign in to comment.