coradoc 2.0.28 → 2.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/lib/coradoc/cli.rb +4 -1
  3. data/lib/coradoc/coradoc.rb +3 -0
  4. data/lib/coradoc/errors.rb +19 -0
  5. data/lib/coradoc/format_module.rb +10 -0
  6. data/lib/coradoc/pipeline.rb +60 -1
  7. data/lib/coradoc/reference/address/anchor.rb +48 -0
  8. data/lib/coradoc/reference/address/doi.rb +40 -0
  9. data/lib/coradoc/reference/address/isbn.rb +39 -0
  10. data/lib/coradoc/reference/address/path.rb +66 -0
  11. data/lib/coradoc/reference/address/scoped_path.rb +47 -0
  12. data/lib/coradoc/reference/address/url.rb +41 -0
  13. data/lib/coradoc/reference/address.rb +153 -0
  14. data/lib/coradoc/reference/catalog/composite.rb +59 -0
  15. data/lib/coradoc/reference/catalog/local.rb +97 -0
  16. data/lib/coradoc/reference/catalog/memory_index.rb +59 -0
  17. data/lib/coradoc/reference/catalog.rb +33 -0
  18. data/lib/coradoc/reference/edge/citation_options.rb +16 -0
  19. data/lib/coradoc/reference/edge/footnote_ref_options.rb +13 -0
  20. data/lib/coradoc/reference/edge/image_ref_options.rb +15 -0
  21. data/lib/coradoc/reference/edge/include_options.rb +15 -0
  22. data/lib/coradoc/reference/edge/kind.rb +72 -0
  23. data/lib/coradoc/reference/edge/link_options.rb +12 -0
  24. data/lib/coradoc/reference/edge/navigation_options.rb +12 -0
  25. data/lib/coradoc/reference/edge/options.rb +13 -0
  26. data/lib/coradoc/reference/edge.rb +80 -0
  27. data/lib/coradoc/reference/edge_search.rb +128 -0
  28. data/lib/coradoc/reference/materializer/base.rb +44 -0
  29. data/lib/coradoc/reference/materializer/passthrough.rb +31 -0
  30. data/lib/coradoc/reference/materializer/registry.rb +116 -0
  31. data/lib/coradoc/reference/materializer.rb +14 -0
  32. data/lib/coradoc/reference/presentation/base.rb +47 -0
  33. data/lib/coradoc/reference/presentation/custom_hierarchy.rb +104 -0
  34. data/lib/coradoc/reference/presentation/page.rb +20 -0
  35. data/lib/coradoc/reference/presentation/single_document.rb +35 -0
  36. data/lib/coradoc/reference/presentation/split_pages.rb +118 -0
  37. data/lib/coradoc/reference/presentation.rb +16 -0
  38. data/lib/coradoc/reference/resolution.rb +184 -0
  39. data/lib/coradoc/reference/resolver/base.rb +15 -0
  40. data/lib/coradoc/reference/resolver/caching.rb +37 -0
  41. data/lib/coradoc/reference/resolver/catalog.rb +69 -0
  42. data/lib/coradoc/reference/resolver/chain.rb +33 -0
  43. data/lib/coradoc/reference/resolver.rb +20 -0
  44. data/lib/coradoc/reference/result/ambiguous.rb +23 -0
  45. data/lib/coradoc/reference/result/base.rb +46 -0
  46. data/lib/coradoc/reference/result/missing.rb +14 -0
  47. data/lib/coradoc/reference/result/resolved.rb +22 -0
  48. data/lib/coradoc/reference/result.rb +20 -0
  49. data/lib/coradoc/reference.rb +65 -0
  50. data/lib/coradoc/validation.rb +40 -0
  51. data/lib/coradoc/version.rb +1 -1
  52. metadata +44 -1
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Reference
5
+ module Catalog
6
+ # Indexes one CoreModel document. Walks the tree once at
7
+ # construction; indexes every node carrying an +id+ by its anchor
8
+ # Address, plus the document itself by an optional path Address.
9
+ #
10
+ # catalog = Catalog::Local.from_doc(doc, path: "ELF-5005-1")
11
+ # catalog.lookup(Address.parse("ELF-5005-1")) # => doc
12
+ # catalog.lookup(Address.parse("sec-3")) # => section
13
+ class Local
14
+ include Catalog::Protocol
15
+
16
+ attr_reader :document, :document_path
17
+
18
+ def initialize(document:, document_path: nil, index: nil)
19
+ @document = document
20
+ @document_path = document_path
21
+ @index = index || MemoryIndex.new
22
+ populate! unless index
23
+ end
24
+
25
+ class << self
26
+ def from_doc(document, path: nil)
27
+ new(document: document, document_path: path)
28
+ end
29
+ end
30
+
31
+ def lookup(address)
32
+ @index.lookup(address)
33
+ end
34
+
35
+ def ambiguous?(address)
36
+ @index.ambiguous?(address)
37
+ end
38
+
39
+ def each_pair(&)
40
+ @index.each_pair(&)
41
+ end
42
+
43
+ def recognizes_scheme?(scheme)
44
+ return true if scheme.to_sym == :anchor
45
+ return true if scheme.to_sym == :path && document_path
46
+ return true if scheme.to_sym == :scoped_path && document_path&.include?(':')
47
+
48
+ @index.recognizes_scheme?(scheme)
49
+ end
50
+
51
+ private
52
+
53
+ def populate!
54
+ index_document_root!
55
+ walk(document)
56
+ end
57
+
58
+ def index_document_root!
59
+ return unless document_path
60
+
61
+ address = Coradoc::Reference::Address.parse(document_path, hint: path_hint)
62
+ @index.add(address, document)
63
+ end
64
+
65
+ def path_hint
66
+ document_path.include?(':') ? :scoped_path : :path
67
+ end
68
+
69
+ def walk(node)
70
+ return unless node.is_a?(Coradoc::CoreModel::Base)
71
+
72
+ index_node!(node)
73
+ walk_children(node)
74
+ end
75
+
76
+ def index_node!(node)
77
+ return unless node.id
78
+
79
+ address = Coradoc::Reference::Address.new(
80
+ scheme: 'anchor',
81
+ target: node.id
82
+ )
83
+ @index.add(address, node)
84
+ end
85
+
86
+ def walk_children(node)
87
+ return unless node.is_a?(Coradoc::CoreModel::HasChildren)
88
+
89
+ children = node.children
90
+ return if children.nil? || children.empty?
91
+
92
+ children.each { |child| walk(child) }
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Reference
5
+ module Catalog
6
+ # Shared in-memory index. Built-in catalogs compose this rather
7
+ # than reimplementing the storage layer (DRY). The index is
8
+ # immutable from the outside — entries are added by the catalog
9
+ # during construction, then read-only.
10
+ class MemoryIndex
11
+ include Enumerable
12
+
13
+ def initialize
14
+ @by_address = {}
15
+ @schemes = Set.new
16
+ end
17
+
18
+ def add(address, content)
19
+ raise ArgumentError, 'address required' unless address.is_a?(Coradoc::Reference::Address)
20
+ raise ArgumentError, 'content required' unless content
21
+
22
+ @by_address[address] = Array(@by_address[address]) << content
23
+ @schemes << address.scheme.to_sym
24
+ self
25
+ end
26
+
27
+ def lookup(address)
28
+ entries = @by_address[address]
29
+ return nil unless entries
30
+ return entries.first if entries.size == 1
31
+
32
+ # Copy: callers must not be able to corrupt the index.
33
+ entries.dup
34
+ end
35
+
36
+ def ambiguous?(address)
37
+ entries = @by_address[address]
38
+ entries && entries.size > 1
39
+ end
40
+
41
+ def each_pair
42
+ return to_enum(:each_pair) unless block_given?
43
+
44
+ @by_address.each do |address, contents|
45
+ contents.each { |c| yield address, c }
46
+ end
47
+ end
48
+
49
+ def recognizes_scheme?(scheme)
50
+ @schemes.include?(scheme.to_sym)
51
+ end
52
+
53
+ def size
54
+ @by_address.size
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Reference
5
+ # Externally-built index from Address → Content. coradoc queries
6
+ # catalogs; it never owns the collection truth.
7
+ #
8
+ # A Catalog is any object responding to +lookup+, +each_pair+, and
9
+ # +recognizes_scheme?+. Built-in catalogs share an in-memory index
10
+ # (MemoryIndex) for implementation. Catalogs compose via CompositeCatalog.
11
+ module Catalog
12
+ autoload :MemoryIndex, "#{__dir__}/catalog/memory_index"
13
+ autoload :Local, "#{__dir__}/catalog/local"
14
+ autoload :Composite, "#{__dir__}/catalog/composite"
15
+
16
+ # Protocol methods every Catalog must answer. Implementations may
17
+ # include this module for documentation only; the protocol is
18
+ # duck-typed but explicit at the type-check sites via these method
19
+ # names.
20
+ module Protocol
21
+ # Resolve +address+ to a Content node (CoreModel::Base) or nil.
22
+ def lookup(address) = raise(NotImplementedError)
23
+
24
+ # Enumerate every (Address, Content) pair this catalog knows.
25
+ def each_pair(&) = raise(NotImplementedError)
26
+
27
+ # Does this catalog index addresses of the given scheme? Used by
28
+ # composite catalogs to skip irrelevant children.
29
+ def recognizes_scheme?(scheme) = raise(NotImplementedError)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Reference
5
+ class Edge < Lutaml::Model::Serializable
6
+ # Options for citation edges (bib references). Style is the
7
+ # citation style name (e.g. "ieee", "apa", "chicago"); locality
8
+ # is the optional page/section reference carried separately on
9
+ # Address.fragment, not here.
10
+ class CitationOptions < Edge::Options
11
+ attribute :style, :string
12
+ attribute :suppress_author, :boolean, default: -> { false }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Reference
5
+ class Edge < Lutaml::Model::Serializable
6
+ # Options for footnote references. The footnote body lives in
7
+ # the catalog as a Content node; the Edge points at it.
8
+ class FootnoteRefOptions < Edge::Options
9
+ attribute :footnote_id, :string
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Reference
5
+ class Edge < Lutaml::Model::Serializable
6
+ # Options for image references.
7
+ class ImageRefOptions < Edge::Options
8
+ attribute :alt_text, :string
9
+ attribute :width, :string
10
+ attribute :height, :string
11
+ attribute :role, :string
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Reference
5
+ class Edge < Lutaml::Model::Serializable
6
+ # Options for include edges: embeds the canonical
7
+ # CoreModel::IncludeOptions — the include directive's typed
8
+ # selectors (tags/wildcards, lines, typed leveloffset, indent,
9
+ # encoding). One typed form, never a re-flattened mirror.
10
+ class IncludeOptions < Edge::Options
11
+ attribute :include_options, Coradoc::CoreModel::IncludeOptions
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Reference
5
+ class Edge < Lutaml::Model::Serializable
6
+ # Registry mapping kind symbol → Entry(name, options_class).
7
+ # Built-in kinds are registered lazily on first access. External
8
+ # gems add kinds via +Edge.register_kind+ (OCP).
9
+ #
10
+ # +register+ always forces builtin registration first, so an
11
+ # external registration made before first use is never clobbered
12
+ # by the lazy builtin pass.
13
+ module Kind
14
+ @entries = {}
15
+ @builtins_registered = false
16
+ @registering_builtins = false
17
+
18
+ MUTEX = Mutex.new
19
+ private_constant :MUTEX
20
+
21
+ Entry = Struct.new(:name, :options_class)
22
+ private_constant :Entry
23
+
24
+ class << self
25
+ def register(name, options_class: nil)
26
+ ensure_builtins_registered! unless @registering_builtins
27
+ @entries[name.to_sym] = Entry.new(name.to_sym, options_class)
28
+ end
29
+
30
+ def names
31
+ ensure_builtins_registered!
32
+ @entries.keys
33
+ end
34
+
35
+ def options_class_for(name)
36
+ ensure_builtins_registered!
37
+ @entries[name.to_sym]&.options_class
38
+ end
39
+
40
+ def entry_for(name)
41
+ ensure_builtins_registered!
42
+ @entries[name.to_sym]
43
+ end
44
+
45
+ def reset!
46
+ @entries.clear
47
+ @builtins_registered = false
48
+ end
49
+
50
+ def ensure_builtins_registered!
51
+ MUTEX.synchronize do
52
+ return if @builtins_registered
53
+
54
+ @registering_builtins = true
55
+ begin
56
+ register(:navigation, options_class: Edge::NavigationOptions)
57
+ register(:citation, options_class: Edge::CitationOptions)
58
+ register(:link, options_class: Edge::LinkOptions)
59
+ register(:include, options_class: Edge::IncludeOptions)
60
+ register(:image_ref, options_class: Edge::ImageRefOptions)
61
+ register(:footnote_ref, options_class: Edge::FootnoteRefOptions)
62
+ @builtins_registered = true
63
+ ensure
64
+ @registering_builtins = false
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Reference
5
+ class Edge < Lutaml::Model::Serializable
6
+ # Options for hyperlink edges.
7
+ class LinkOptions < Edge::Options
8
+ attribute :role, :string
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Reference
5
+ class Edge < Lutaml::Model::Serializable
6
+ # Options specific to navigation edges (xref, anchor).
7
+ class NavigationOptions < Edge::Options
8
+ attribute :tooltip, :string
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Reference
5
+ class Edge < Lutaml::Model::Serializable
6
+ # Options base class. Subclasses are typed value objects per kind —
7
+ # never +:hash+. Value equality (==/eql?/hash) comes from
8
+ # Lutaml::Model: class-aware, all attributes compared.
9
+ class Options < Lutaml::Model::Serializable
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+
5
+ module Coradoc
6
+ module Reference
7
+ # Directed edge: a Content node references another Content via an Address.
8
+ #
9
+ # Every reference in coradoc — navigation, citation, hyperlink,
10
+ # include, image, footnote — is one Edge with a different +kind+.
11
+ # The kind is a label for materialization; resolution is kind-agnostic.
12
+ #
13
+ # Edge.new(
14
+ # kind: :navigation,
15
+ # address: Address.parse("ELF-5005-1#sec-3"),
16
+ # source_id: "para-42",
17
+ # label: "Section 3"
18
+ # )
19
+ class Edge < Lutaml::Model::Serializable
20
+ autoload :Kind, "#{__dir__}/edge/kind"
21
+ autoload :Options, "#{__dir__}/edge/options"
22
+ autoload :NavigationOptions,
23
+ "#{__dir__}/edge/navigation_options"
24
+ autoload :CitationOptions,
25
+ "#{__dir__}/edge/citation_options"
26
+ autoload :LinkOptions, "#{__dir__}/edge/link_options"
27
+ autoload :IncludeOptions, "#{__dir__}/edge/include_options"
28
+ autoload :ImageRefOptions, "#{__dir__}/edge/image_ref_options"
29
+ autoload :FootnoteRefOptions,
30
+ "#{__dir__}/edge/footnote_ref_options"
31
+
32
+ attribute :kind, :string
33
+ attribute :address, Coradoc::Reference::Address
34
+ attribute :source_id, :string
35
+ attribute :label, :string
36
+ attribute :options, Coradoc::Reference::Edge::Options
37
+
38
+ class << self
39
+ # Build an Edge with the given kind. Options are coerced to the
40
+ # kind's options class (if any) via the Kind registry — never
41
+ # hand-rolled. Raises UnknownKindError for kinds that were never
42
+ # registered — external kinds register via +register_kind+ (OCP).
43
+ def build(kind:, address:, source_id: nil, label: nil, options: nil)
44
+ entry = Kind.entry_for(kind)
45
+ unless entry
46
+ raise Coradoc::Reference::UnknownKindError,
47
+ "Unknown reference kind #{kind.inspect} — " \
48
+ 'register it via Edge.register_kind'
49
+ end
50
+
51
+ new(
52
+ kind: kind.to_s,
53
+ address: address,
54
+ source_id: source_id,
55
+ label: label,
56
+ options: coerce_options(options, entry.options_class)
57
+ )
58
+ end
59
+
60
+ def register_kind(name, options_class: nil)
61
+ Kind.register(name, options_class: options_class)
62
+ end
63
+
64
+ def kinds
65
+ Kind.names
66
+ end
67
+
68
+ private
69
+
70
+ def coerce_options(value, options_class)
71
+ klass = options_class || Options
72
+ return klass.new if value.nil?
73
+ return value if value.is_a?(klass)
74
+
75
+ klass.new(value)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Reference
5
+ # Walks a CoreModel tree and yields every Edge found in it. For
6
+ # Phase 1, Edges are wrapped inside existing node types —
7
+ # +CrossReferenceElement+, +LinkElement+, +Image+, +Include+,
8
+ # +FootnoteElement+. The Phase 3 migration will move Edges to
9
+ # first-class attributes; this module abstracts the extraction so
10
+ # the rest of the resolver doesn't change.
11
+ #
12
+ # The walker is read-only: it never mutates the input tree.
13
+ module EdgeSearch
14
+ module_function
15
+
16
+ # Yields (parent_node, edge) for every Edge found in the tree.
17
+ # The parent_node is the node that owns the edge — the
18
+ # materializer replaces the edge in-place inside parent_node's
19
+ # children list.
20
+ def each_edge(root)
21
+ return to_enum(:each_edge, root) unless block_given?
22
+
23
+ walk(root) { |node| edges_for(node).each { |edge| yield node, edge } }
24
+ end
25
+
26
+ # Walk tree, yield each Base node. Stops at inline leaves.
27
+ def walk(node, &block)
28
+ return unless node.is_a?(Coradoc::CoreModel::Base)
29
+ return unless block
30
+
31
+ yield(node)
32
+ return unless node.is_a?(Coradoc::CoreModel::HasChildren)
33
+
34
+ children = node.children
35
+ return unless children
36
+
37
+ children.each { |child| walk(child, &block) }
38
+ end
39
+
40
+ # Extract every Edge the given node owns. Pure function of node.
41
+ def edges_for(node)
42
+ edge_extractor = EDGE_EXTRACTORS[node.class]
43
+ return [] unless edge_extractor
44
+
45
+ edge = edge_extractor.call(node)
46
+ edge ? [edge] : []
47
+ end
48
+
49
+ def edge_from_cross_reference(node)
50
+ Edge.build(
51
+ kind: :navigation,
52
+ address: parse_address(node.target, hint: xref_hint(node.target)),
53
+ source_id: node.id,
54
+ label: node.content
55
+ )
56
+ end
57
+
58
+ def edge_from_link(node)
59
+ Edge.build(
60
+ kind: :link,
61
+ address: parse_address(node.target),
62
+ source_id: node.id,
63
+ label: node.content
64
+ )
65
+ end
66
+
67
+ def edge_from_include(node)
68
+ Edge.build(
69
+ kind: :include,
70
+ address: parse_address(node.target, hint: path_or_url_hint(node.target)),
71
+ source_id: node.id,
72
+ options: { include_options: node.options }
73
+ )
74
+ end
75
+
76
+ def edge_from_image(node)
77
+ Edge.build(
78
+ kind: :image_ref,
79
+ address: parse_address(node.src, hint: path_or_url_hint(node.src)),
80
+ source_id: node.id,
81
+ label: node.alt,
82
+ options: { alt_text: node.alt }
83
+ )
84
+ end
85
+
86
+ def edge_from_footnote(node)
87
+ Edge.build(
88
+ kind: :footnote_ref,
89
+ address: parse_address(node.target || node.id, hint: :anchor),
90
+ source_id: node.id,
91
+ options: { footnote_id: node.id }
92
+ )
93
+ end
94
+
95
+ # Xrefs are document-internal by definition (AsciiDoc): bare
96
+ # targets are anchors, even uppercase document-ID-shaped ones
97
+ # ("SEC-2"). Only an explicit "document#fragment" shape or a URL
98
+ # points outside the current document.
99
+ def xref_hint(target)
100
+ raw = target.to_s
101
+ return :url if Coradoc::Reference::Address::Url.matches?(raw)
102
+ return :path if raw.include?('#') && !raw.start_with?('#')
103
+
104
+ :anchor
105
+ end
106
+
107
+ # File-like targets (include, image): URLs stay urls, everything
108
+ # else is a path — including bare filenames ("foo.png") that the
109
+ # anchor bareword heuristic would otherwise claim.
110
+ def path_or_url_hint(target)
111
+ Coradoc::Reference::Address::Url.matches?(target.to_s) ? :url : :path
112
+ end
113
+
114
+ def parse_address(target, hint: nil)
115
+ Coradoc::Reference::Address.parse(target.to_s, hint: hint)
116
+ end
117
+
118
+ EDGE_EXTRACTORS = {
119
+ Coradoc::CoreModel::CrossReferenceElement => method(:edge_from_cross_reference),
120
+ Coradoc::CoreModel::LinkElement => method(:edge_from_link),
121
+ Coradoc::CoreModel::Include => method(:edge_from_include),
122
+ Coradoc::CoreModel::Image => method(:edge_from_image),
123
+ Coradoc::CoreModel::FootnoteElement => method(:edge_from_footnote)
124
+ }.freeze
125
+ private_constant :EDGE_EXTRACTORS
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Reference
5
+ module Materializer
6
+ # Protocol base. Subclasses declare their (kind, presentation,
7
+ # format) tuple via class-level +.kind+, +.presentation+, +.format+
8
+ # methods, then implement +#materialize+ to produce a
9
+ # CoreModel::InlineElement subtree.
10
+ #
11
+ # Materializers consume Results — they never touch the catalog
12
+ # directly. The Presentation tells them where the target lives.
13
+ class Base
14
+ class << self
15
+ def kind
16
+ nil
17
+ end
18
+
19
+ def presentation
20
+ :any
21
+ end
22
+
23
+ def format
24
+ :any
25
+ end
26
+ end
27
+
28
+ # Render one resolved edge.
29
+ #
30
+ # @param edge [Edge]
31
+ # @param result [Result::Base] the resolved outcome
32
+ # @param node [CoreModel::Base] the original edge-bearing node;
33
+ # return it unchanged to keep the node as authored
34
+ # @param presentation [Presentation::Base]
35
+ # @param pages [Array<Presentation::Page>]
36
+ # @return [CoreModel::Base, nil] replacement node, the original
37
+ # +node+ to keep it, or nil to drop it from the tree
38
+ def materialize(edge:, result:, node:, presentation:, pages:)
39
+ raise NotImplementedError
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Reference
5
+ module Materializer
6
+ # Default fallback. Returns the original edge-bearing node
7
+ # unchanged — an unresolvable or unrenderable reference survives
8
+ # materialization exactly as authored, so round-tripping is
9
+ # always safe and no content is ever lost.
10
+ class Passthrough < Base
11
+ class << self
12
+ def kind
13
+ :any
14
+ end
15
+
16
+ def presentation
17
+ :any
18
+ end
19
+
20
+ def format
21
+ :any
22
+ end
23
+ end
24
+
25
+ def materialize(node:, **)
26
+ node
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end