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.

  1. `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.

  2. `Inline#convert` just calls `converter.convert` with `self`, i.e. it's dispatched to converter's `inline_anchor` handler.

  3. 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

new(resolver_class: Resolver, **resolver_opts) click to toggle source

@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}).
Calls superclass method
# 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

process(document) click to toggle source

@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