Class: Prosereflect::Schema::ContentMatch

Inherits:
Object
  • Object
show all
Defined in:
lib/prosereflect/schema/content_match.rb

Overview

Represents a match state for node content expressions Parses expressions like “block+”, “inline*”, “(paragraph | heading)2,4”

Defined Under Namespace

Classes: TokenStream

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(valid_end:, next_edges: []) ⇒ ContentMatch

Returns a new instance of ContentMatch.



20
21
22
23
24
# File 'lib/prosereflect/schema/content_match.rb', line 20

def initialize(valid_end:, next_edges: [])
  @valid_end = valid_end
  @next_edges = next_edges
  @wrap_cache = [] # [[target_node_type, computed_wrapping]]
end

Instance Attribute Details

#next_edgesObject (readonly)

Returns the value of attribute next_edges.



18
19
20
# File 'lib/prosereflect/schema/content_match.rb', line 18

def next_edges
  @next_edges
end

#valid_endObject (readonly)

Returns the value of attribute valid_end.



18
19
20
# File 'lib/prosereflect/schema/content_match.rb', line 18

def valid_end
  @valid_end
end

#wrap_cacheObject (readonly)

Returns the value of attribute wrap_cache.



18
19
20
# File 'lib/prosereflect/schema/content_match.rb', line 18

def wrap_cache
  @wrap_cache
end

Class Method Details

.emptyObject



26
27
28
# File 'lib/prosereflect/schema/content_match.rb', line 26

def self.empty
  @empty ||= new(valid_end: true, next_edges: [])
end

.parse(expression, node_types) ⇒ Object

Parse content expression and return ContentMatch



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/prosereflect/schema/content_match.rb', line 124

def self.parse(expression, node_types)
  return empty if expression.nil? || expression.empty?

  stream = TokenStream.new(expression, node_types)
  return empty if stream.peek.nil?

  expr = parse_expression(stream)
  unless stream.peek.nil?
    stream.error("Unexpected trailing text")
  end

  nfa_result = to_nfa(expr)
  dfa_result = to_dfa(nfa_result)
  check_for_dead_ends(dfa_result, stream)
  dfa_result
end

Instance Method Details

#compatible?(other) ⇒ Boolean

Check if this content expression is compatible with another

Returns:

  • (Boolean)


52
53
54
55
56
# File 'lib/prosereflect/schema/content_match.rb', line 52

def compatible?(other)
  @next_edges.any? do |i|
    other.next_edges.any? { |j| i.type == j.type }
  end
end

#default_typeObject

Get the default type for this match (first non-text type without required attrs)



41
42
43
44
45
46
47
48
49
# File 'lib/prosereflect/schema/content_match.rb', line 41

def default_type
  @next_edges.each do |edge|
    type = edge.type
    if !type.text? && !type.has_required_attrs?
      return type
    end
  end
  nil
end

#edge(n) ⇒ Object

Get edge at index n



100
101
102
103
104
105
106
107
# File 'lib/prosereflect/schema/content_match.rb', line 100

def edge(n)
  if n >= edge_count
    raise Prosereflect::SchemaErrors::ContentMatchError,
          "There's no #{n}th edge in this content match"
  end

  @next_edges[n]
end

#edge_countObject

Number of edges



95
96
97
# File 'lib/prosereflect/schema/content_match.rb', line 95

def edge_count
  @next_edges.length
end

#fill_before(after:, to_end: false, start_index: 0) ⇒ Object

Fill in content before the given fragment Returns a Fragment if successful, nil otherwise



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/prosereflect/schema/content_match.rb', line 60

def fill_before(after:, to_end: false, start_index: 0)
  seen = [self]

  search = ->(match, types) do
    finished = match_fragment(after, start_index)
    if finished && (!to_end || finished.valid_end)
      return make_fragment(types)
    end

    match.next_edges.each do |edge|
      type = edge.type
      next_match = edge.next_match
      if !type.text? && !type.has_required_attrs? && !seen.include?(next_match)
        seen << next_match
        result = search.call(next_match, types + [type])
        return result if result
      end
    end
    nil
  end

  search.call(self, [])
end

#find_wrapping(target_node_type) ⇒ Object

Find wrapping nodes to reach the target type



85
86
87
88
89
90
91
92
# File 'lib/prosereflect/schema/content_match.rb', line 85

def find_wrapping(target_node_type)
  cached = @wrap_cache.find { |entry| entry[0] == target_node_type }
  return cached[1] if cached

  computed = compute_wrapping(target_node_type)
  @wrap_cache << [target_node_type, computed]
  computed
end

#inline_content?Boolean

Check if this match has inline content

Returns:

  • (Boolean)


36
37
38
# File 'lib/prosereflect/schema/content_match.rb', line 36

def inline_content?
  @next_edges.any? && @next_edges.first.type.is_a?(NodeType) && @next_edges.first.type.inline?
end

#make_fragment(types) ⇒ Object

For creating test fragments



587
588
589
590
591
592
593
594
# File 'lib/prosereflect/schema/content_match.rb', line 587

def make_fragment(types)
  return nil if types.empty?

  nodes = types.map(&:create_and_fill)
  return nil if nodes.any?(&:nil?)

  Fragment.new(nodes)
end

#match_fragment(fragment, start: 0, end_index: nil) ⇒ Object

Match a fragment and return the next match state



110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/prosereflect/schema/content_match.rb', line 110

def match_fragment(fragment, start: 0, end_index: nil)
  end_index ||= fragment.content.size
  current = self
  i = start

  while current && i < end_index
    child = fragment[i]
    current = current.match_type(child.type)
    i += 1
  end
  current
end

#match_type(node_type) ⇒ Object

Match a node type and return the next match state



31
32
33
# File 'lib/prosereflect/schema/content_match.rb', line 31

def match_type(node_type)
  @next_edges.find { |edge| edge.type == node_type }&.next_match
end