Skip to content

Commit

Permalink
Merge pull request #504 from robertpanzer/issue-501
Browse files Browse the repository at this point in the history
Fixes 501: Added method to get and remove substitutions
  • Loading branch information
robertpanzer authored Aug 28, 2016
2 parents 43e6fc8 + 3a472ad commit d169243
Show file tree
Hide file tree
Showing 3 changed files with 322 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,42 @@

public interface StructuralNode extends ContentNode {

/**
* Constant for special character replacement substitution like {@code <} to {@code &amp;lt;}.
* @see <a href="http://asciidoctor.org/docs/user-manual/#special-characters">Asciidoctor User Manual</a>
*/
public static final String SUBSTITUTION_SPECIAL_CHARACTERS = "specialcharacters";

/**
* Constant for quote replacements like {@code *bold*} to <b>{@code bold}</b>.
* @see <a href="http://asciidoctor.org/docs/user-manual/#quotes">Asciidoctor User Manual</a>
*/
public static final String SUBSTITUTION_QUOTES = "quotes";

/**
* Constant for attribute replacements like {@code {foo}}.
* @see <a href="http://asciidoctor.org/docs/user-manual/#attributes-2">Asciidoctor User Manual</a>
*/
public static final String SUBSTITUTION_ATTRIBUTES = "attributes";

/**
* Constant for replacements like {@code (C)} to {@code &#169;}.
* @see <a href="http://asciidoctor.org/docs/user-manual/#replacements">Asciidoctor User Manual</a>
*/
public static final String SUBSTITUTION_REPLACEMENTS = "replacements";

/**
* Constant for macro replacements like {@code mymacro:target[]}.
* @see <a href="http://asciidoctor.org/docs/user-manual/#subs-mac">Asciidoctor User Manual</a>
*/
public static final String SUBSTITUTION_MACROS = "macros";

/**
* Constant for post replacements like creating line breaks from a trailing {@code +} in a line.
* @see <a href="http://asciidoctor.org/docs/user-manual/#post-replacements">Asciidoctor User Manual</a>
*/
public static final String SUBSTITUTION_POST_REPLACEMENTS = "post_replacements";

/**
* @deprecated Please use {@linkplain #getTitle()} instead
*/
Expand Down Expand Up @@ -50,4 +86,46 @@ public interface StructuralNode extends ContentNode {
*/
Cursor getSourceLocation();

/**
* Returns the list of enabled substitutions.
* @return A list of substitutions enabled for this node, e.g. <code>["specialcharacters", "quotes", "attributes", "replacements", "macros", "post_replacements"]</code> for paragraphs.
* @see <a href="http://asciidoctor.org/docs/user-manual/#subs">Asciidoctor User Manual</a>
*/
List<String> getSubstitutions();

/**
* @param substitution the name of a substitution, e.g. {@link #SUBSTITUTION_POST_REPLACEMENTS}
* @return <code>true</code> if the name of the given substitution is enabled.
* @see <a href="http://asciidoctor.org/docs/user-manual/#subs">Asciidoctor User Manual</a>
*/
boolean isSubstitutionEnabled(String substitution);

/**
* Removes the given substitution from this node.
* @param substitution the name of a substitution, e.g. {@link #SUBSTITUTION_QUOTES}
* @see <a href="http://asciidoctor.org/docs/user-manual/#subs">Asciidoctor User Manual</a>
*/
void removeSubstitution(String substitution);

/**
* Adds the given substitution to this node at the end of the substitution list.
* @param substitution the name of a substitution, e.g. {@link #SUBSTITUTION_MACROS}
* @see <a href="http://asciidoctor.org/docs/user-manual/#subs">Asciidoctor User Manual</a>
*/
void addSubstitution(String substitution);

/**
* Adds the given substitution to this node at the beginning of the substitution list.
* @param substitution the name of a substitution, e.g. {@link #SUBSTITUTION_ATTRIBUTES}
* @see <a href="http://asciidoctor.org/docs/user-manual/#subs">Asciidoctor User Manual</a>
*/
void prependSubstitution(String substitution);

/**
* Sets the given substitutions on this node overwriting all other substitutions.
* @param substitution the name of a substitution, e.g. {@link #SUBSTITUTION_SPECIAL_CHARACTERS}
* @see <a href="http://asciidoctor.org/docs/user-manual/#subs">Asciidoctor User Manual</a>
*/
void setSubstitutions(String... substitution);

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.asciidoctor.ast.StructuralNode;
import org.asciidoctor.internal.RubyBlockListDecorator;
import org.asciidoctor.internal.RubyHashUtil;
import org.asciidoctor.internal.RubyUtils;
import org.jruby.RubyArray;
import org.jruby.runtime.builtin.IRubyObject;

Expand Down Expand Up @@ -90,6 +91,44 @@ public Cursor getSourceLocation() {
return new CursorImpl(object);
}

@Override
public List<String> getSubstitutions() {
return getList("subs", String.class);
}

@Override
public boolean isSubstitutionEnabled(String substitution) {
return getBoolean("sub?", RubyUtils.toSymbol(getRuntime(), substitution));
}

@Override
public void removeSubstitution(String substitution) {
getRubyProperty("remove_sub", RubyUtils.toSymbol(getRuntime(), substitution));
}

@Override
public void addSubstitution(String substitution) {
RubyArray subs = (RubyArray) getRubyProperty("@subs");
subs.add(RubyUtils.toSymbol(getRuntime(), substitution));
}

@Override
public void prependSubstitution(String substitution) {
RubyArray subs = (RubyArray) getRubyProperty("@subs");
subs.insert(getRuntime().newFixnum(0), RubyUtils.toSymbol(getRuntime(), substitution));
}

@Override
public void setSubstitutions(String... substitutions) {
RubyArray subs = (RubyArray) getRubyProperty("@subs");
subs.clear();
if (substitutions != null) {
for (String substitution : substitutions) {
subs.add(RubyUtils.toSymbol(getRuntime(), substitution));
}
}
}

@Override
public List<StructuralNode> findBy(Map<Object, Object> selector) {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package org.asciidoctor

import org.asciidoctor.ast.Block
import org.asciidoctor.ast.Document
import org.asciidoctor.extension.Treeprocessor
import org.jboss.arquillian.spock.ArquillianSputnik
import org.jboss.arquillian.test.api.ArquillianResource
import org.junit.runner.RunWith
import spock.lang.Specification
import static org.asciidoctor.ast.StructuralNode.SUBSTITUTION_SPECIAL_CHARACTERS
import static org.asciidoctor.ast.StructuralNode.SUBSTITUTION_QUOTES
import static org.asciidoctor.ast.StructuralNode.SUBSTITUTION_ATTRIBUTES
import static org.asciidoctor.ast.StructuralNode.SUBSTITUTION_REPLACEMENTS
import static org.asciidoctor.ast.StructuralNode.SUBSTITUTION_MACROS
import static org.asciidoctor.ast.StructuralNode.SUBSTITUTION_POST_REPLACEMENTS


@RunWith(ArquillianSputnik)
class WhenSubstitutionsAreUsed extends Specification {

@ArquillianResource
private Asciidoctor asciidoctor

def 'a node should return its substitutions'() {

given:
String document = '''
= Test document
== Test section
Test paragraph
[source,java]
----
System.out.println("Hello World");
----
'''

when:
Document doc = asciidoctor.load(document, OptionsBuilder.options().asMap())
Block paragraph = doc.blocks()[0].blocks[0]
Block source = doc.blocks()[0].blocks[1]

then:
paragraph.substitutions == [SUBSTITUTION_SPECIAL_CHARACTERS, SUBSTITUTION_QUOTES, SUBSTITUTION_ATTRIBUTES, SUBSTITUTION_REPLACEMENTS, SUBSTITUTION_MACROS, SUBSTITUTION_POST_REPLACEMENTS]
source.substitutions == [SUBSTITUTION_SPECIAL_CHARACTERS]
}

def 'it should be possible to remove a substitution'() {
given:
String document = '''
= Test document
:foo: bar
== Test section
First test paragraph {foo}.
Second test paragraph {foo}
'''

when:
asciidoctor.javaExtensionRegistry().treeprocessor(TestTreeprocessor)
Document doc = asciidoctor.load(document, OptionsBuilder.options().asMap())
Block firstparagraph = doc.blocks()[0].blocks[0]
Block secondparagraph = doc.blocks()[0].blocks[1]

String html = asciidoctor.convert(document, OptionsBuilder.options().asMap())

then:
firstparagraph.substitutions == [SUBSTITUTION_SPECIAL_CHARACTERS, SUBSTITUTION_QUOTES, SUBSTITUTION_ATTRIBUTES, SUBSTITUTION_REPLACEMENTS, SUBSTITUTION_MACROS, SUBSTITUTION_POST_REPLACEMENTS]
firstparagraph.isSubstitutionEnabled(SUBSTITUTION_ATTRIBUTES)
secondparagraph.substitutions == [SUBSTITUTION_SPECIAL_CHARACTERS, SUBSTITUTION_QUOTES, SUBSTITUTION_REPLACEMENTS, SUBSTITUTION_MACROS, SUBSTITUTION_POST_REPLACEMENTS]
!secondparagraph.isSubstitutionEnabled(SUBSTITUTION_ATTRIBUTES)

html.contains('First test paragraph bar.')
html.contains('Second test paragraph {foo}')
}

def 'it should be possible to add a substitution'() {
given:
String document = '''
= Test document
:foo: bar
== Test section
First test paragraph {foo}
[source,java]
----
System.out.println("{foo}");
----
'''

when:
asciidoctor.javaExtensionRegistry().treeprocessor(TestTreeprocessor)
Document doc = asciidoctor.load(document, OptionsBuilder.options().asMap())
Block firstparagraph = doc.blocks()[0].blocks[0]
Block secondparagraph = doc.blocks()[0].blocks[1]

String html = asciidoctor.convert(document, OptionsBuilder.options().asMap())

then:
firstparagraph.substitutions == [SUBSTITUTION_SPECIAL_CHARACTERS, SUBSTITUTION_QUOTES, SUBSTITUTION_ATTRIBUTES, SUBSTITUTION_REPLACEMENTS, SUBSTITUTION_MACROS, SUBSTITUTION_POST_REPLACEMENTS]
firstparagraph.isSubstitutionEnabled(SUBSTITUTION_ATTRIBUTES)
secondparagraph.substitutions == [SUBSTITUTION_SPECIAL_CHARACTERS, SUBSTITUTION_ATTRIBUTES]
secondparagraph.isSubstitutionEnabled(SUBSTITUTION_ATTRIBUTES)

html.contains('First test paragraph bar')
html.contains('System.out.println("bar");')
}

static class TestTreeprocessor extends Treeprocessor {
@Override
Document process(Document document) {
if (document.blocks()[0].blocks[1].isSubstitutionEnabled(SUBSTITUTION_ATTRIBUTES)) {
document.blocks()[0].blocks[1].removeSubstitution(SUBSTITUTION_ATTRIBUTES)
} else {
document.blocks()[0].blocks[1].addSubstitution(SUBSTITUTION_ATTRIBUTES)
}
document
}
}


def 'it should be possible to prepend a substitution'() {
given:
// Usually *{foo} should not be rendered as bold text as attributes are substituted after quotes.
// But by prepending the attributes substitution we can accomplish that
String document = '''
= Test document
:foo: bar*
== Test section
First test paragraph *{foo}
'''

when:
asciidoctor.javaExtensionRegistry().treeprocessor(PrependSubstitutionTestTreeprocessor)
Document doc = asciidoctor.load(document, OptionsBuilder.options().asMap())
Block firstparagraph = doc.blocks()[0].blocks[0]

String html = asciidoctor.convert(document, OptionsBuilder.options().asMap())

then:
firstparagraph.substitutions == [SUBSTITUTION_ATTRIBUTES, SUBSTITUTION_SPECIAL_CHARACTERS, SUBSTITUTION_QUOTES, SUBSTITUTION_ATTRIBUTES, SUBSTITUTION_REPLACEMENTS, SUBSTITUTION_MACROS, SUBSTITUTION_POST_REPLACEMENTS]
firstparagraph.isSubstitutionEnabled(SUBSTITUTION_ATTRIBUTES)

html.contains('First test paragraph <strong>bar</strong>')
}

static class PrependSubstitutionTestTreeprocessor extends Treeprocessor {
@Override
Document process(Document document) {
document.blocks()[0].blocks[0].prependSubstitution(SUBSTITUTION_ATTRIBUTES)
document
}
}

def 'it should be possible to set a substitution'() {
given:
// By default in the listing < and > should be substituted by &lt; and &gt; due to the special characters substitution.
// We set only the attributes substitution instead and expect literally 'bar <=> bar' in the output.
String document = '''
= Test document
:foo: bar
== Test section
----
{foo} <=> bar
----
'''

when:
asciidoctor.javaExtensionRegistry().treeprocessor(SetSubstitutionTestTreeprocessor)
Document doc = asciidoctor.load(document, OptionsBuilder.options().asMap())
Block firstparagraph = doc.blocks()[0].blocks[0]

String html = asciidoctor.convert(document, OptionsBuilder.options().asMap())

then:
firstparagraph.substitutions == [SUBSTITUTION_ATTRIBUTES]
firstparagraph.isSubstitutionEnabled(SUBSTITUTION_ATTRIBUTES)

html.contains('bar <=> bar')
}

static class SetSubstitutionTestTreeprocessor extends Treeprocessor {
@Override
Document process(Document document) {
document.blocks()[0].blocks[0].setSubstitutions(SUBSTITUTION_ATTRIBUTES)
document
}
}

}

0 comments on commit d169243

Please sign in to comment.