class Asciidoctor::InterdocReftext::Processor
Asciidoctor
processor that adds support for automatic cross-reference text for inter-document cross references.
### Implementation Considerations
Asciidoctor
does not allow to cleanly change the way of resolving xreftext for `xref:path#[]` macro with path and without explicit xreflabel; it always uses path as the default xreflabel.
-
`xref:[]` macros are parsed and even converted in `Asciidoctor::Substitutors#sub_inline_xrefs` - a single, huge and nasty method that accepts a text (e.g. whole paragraph) and returns the text with converted `xref:[]` macros. The conversion is delegated to `Asciidoctor::Inline#convert` - for each macro a new instance of `Inline` node is created and then `#convert` is called.
-
`Inline#convert` just calls `converter.convert` with `self`, i.e. it's dispatched to converter's `inline_anchor` handler.
-
The built-in so called HTML5 converter looks into the catalog of references (`document.catalog`) for reflabel for the xref's refid, but only if xref node does not define attribute path or text (explicit reflabel). If text is not set and path is set, i.e. it's an inter-document reference without explicit reflabel, catalog of references is bypassed and path is used as a reflabel.
Eh, this is really nasty… The least evil way how to achieve the goal seems to be monkey-patching of the `Asciidoctor::Inline` class. This is done via {InlineNodeMixin} which is prepended* into the `Inline` class on initialization of this processor.
The actual logic that resolves reflabel for the given refid is implemented in class {Resolver}. The {Processor} is responsible for creating an instance of {Resolver} for the processed document and injecting it into instance variable {RESOLVER_VAR_NAME} in the document, so {InlineNodeMixin} can access it.
Prepending* {InlineNodeMixin} into the `Asciidoctor::Inline` class has (obviously) a global effect. However, if {RESOLVER_VAR_NAME} is not injected in the document object (e.g. extension is not active), `Inline` behaves the same as without {InlineNodeMixin}.
_* If running under Opal (JavaScript), {InlineNodeMixin} is not prepended into the `Asciidoctor::Inline`, because Opal does not support that. Thus it's included and the `#text` method is overriden using poor alias method chain approach.
NOTE: We use reftext and reflabel as interchangeable terms in this gem.
Constants
- RESOLVER_VAR_NAME
Name of instance variable that is dynamically defined in a document object; it contains an instance of the
Resolver
for the document.
Public Class Methods
@param resolver_class [#new] the {Resolver} class to use. @param resolver_opts [Hash<Symbol, Object>] options to be passed into
the resolver_class's initializer (see {Resolver#initialize}).
# File lib/asciidoctor/interdoc_reftext/processor.rb, line 66 def initialize(resolver_class: Resolver, **resolver_opts) super @resolver_class = resolver_class @resolver_opts = resolver_opts # Monkey-patch Asciidoctor::Inline unless already patched. unless ::Asciidoctor::Inline.include? InlineNodeMixin if RUBY_PLATFORM == 'opal' # Opal does not support `Module#prepend`, so we have to fallback to # `include` with poor alias method chain approach. ::Asciidoctor::Inline.send(:include, InlineNodeMixin) else ::Asciidoctor::Inline.send(:prepend, InlineNodeMixin) end end end
Public Instance Methods
@param document [Asciidoctor::Document] the document to process.
# File lib/asciidoctor/interdoc_reftext/processor.rb, line 84 def process(document) resolver = @resolver_class.new(document, **@resolver_opts) document.instance_variable_set(RESOLVER_VAR_NAME, resolver) nil end