Class: Prosereflect::Schema::NodeType

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, attrs: {}, content_expression: nil, groups: [], schema: nil, spec: nil, inline: false, atom: false) ⇒ NodeType

Returns a new instance of NodeType.



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/prosereflect/schema/node_type.rb', line 10

def initialize(name:, attrs: {}, content_expression: nil, groups: [],
               schema: nil, spec: nil, inline: false, atom: false)
  @name = name
  @attrs = attrs
  @groups = groups
  @schema = schema
  @spec = spec
  @inline = inline
  @atom = atom
  @mark_set = nil
  @content_expression = content_expression

  # Content match will be built later by Schema#build_content_matches
  # to avoid circular dependency issues during node type construction
  @content_match = ContentMatch.empty
end

Instance Attribute Details

#attrsObject (readonly)

Returns the value of attribute attrs.



6
7
8
# File 'lib/prosereflect/schema/node_type.rb', line 6

def attrs
  @attrs
end

#content_expressionObject (readonly)

Returns the value of attribute content_expression.



6
7
8
# File 'lib/prosereflect/schema/node_type.rb', line 6

def content_expression
  @content_expression
end

#content_matchObject (readonly)

Returns the value of attribute content_match.



6
7
8
# File 'lib/prosereflect/schema/node_type.rb', line 6

def content_match
  @content_match
end

#groupsObject (readonly)

Returns the value of attribute groups.



6
7
8
# File 'lib/prosereflect/schema/node_type.rb', line 6

def groups
  @groups
end

#mark_setObject

Returns the value of attribute mark_set.



8
9
10
# File 'lib/prosereflect/schema/node_type.rb', line 8

def mark_set
  @mark_set
end

#nameObject (readonly)

Returns the value of attribute name.



6
7
8
# File 'lib/prosereflect/schema/node_type.rb', line 6

def name
  @name
end

#schemaObject (readonly)

Returns the value of attribute schema.



6
7
8
# File 'lib/prosereflect/schema/node_type.rb', line 6

def schema
  @schema
end

#specObject (readonly)

Returns the value of attribute spec.



6
7
8
# File 'lib/prosereflect/schema/node_type.rb', line 6

def spec
  @spec
end

Class Method Details

.from_spec(name, schema, spec) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/prosereflect/schema/node_type.rb', line 27

def self.from_spec(name, schema, spec)
  attrs = {}
  if spec.respond_to?(:attrs)
    spec_attrs = spec.attrs
    attrs = if spec_attrs.is_a?(Hash)
              # New format: {"attr_name" => {default: value}} or attr_name as key
              spec_attrs.each_with_object({}) do |(attr_name, attr_spec), hash|
                if attr_spec.respond_to?(:name)
                  hash[attr_name] =
                    Attribute.new(name: attr_spec.name,
                                  default: attr_spec.default)
                else
                  # attr_spec is a Hash with :default, :name, etc as keys
                  attr_name = attr_spec[:name] || attr_spec["name"] || attr_name
                  default = attr_spec[:default] || attr_spec["default"]
                  hash[attr_name] =
                    Attribute.new(name: attr_name, default: default)
                end
              end
            else
              # Old format: values are attribute objects with .name and .default
              spec_attrs.transform_values do |a|
                Attribute.new(name: a.name, default: a.default)
              end
            end
  elsif spec.is_a?(Hash)
    spec_attrs = spec[:attrs] || spec["attrs"] || {}
    attrs = spec_attrs.transform_values do |v|
      Attribute.new(
        name: v[:name] || v["name"] || v.keys.first,
        default: v[:default] || v["default"],
      )
    end
  end

  content_expr = spec.respond_to?(:content) ? spec.content : (spec[:content] || spec["content"])
  groups = spec.respond_to?(:groups) ? spec.groups : parse_groups(spec)
  inline = spec.respond_to?(:inline) ? spec.inline : (spec[:inline] || spec["inline"] || false)
  atom = spec.respond_to?(:atom) ? spec.atom : (spec[:atom] || spec["atom"] || false)

  new(
    name: name,
    attrs: attrs,
    content_expression: content_expr,
    groups: groups,
    schema: schema,
    spec: spec,
    inline: inline,
    atom: atom,
  )
end

.parse_groups(spec) ⇒ Object



79
80
81
82
83
84
# File 'lib/prosereflect/schema/node_type.rb', line 79

def self.parse_groups(spec)
  group_str = spec[:group] || spec["group"]
  return [] unless group_str

  group_str.is_a?(Array) ? group_str : group_str.to_s.split
end

Instance Method Details

#allowed_marks(marks) ⇒ Object



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/prosereflect/schema/node_type.rb', line 248

def allowed_marks(marks)
  return marks if @mark_set.nil?

  result = marks.dup
  filtered = false

  marks.each_with_index do |mark, i|
    unless allows_mark_type?(mark.type)
      result = marks[0...i].dup
      filtered = true
      break
    end
  end

  filtered ? result : marks
end

#allows_mark_type?(mark_type) ⇒ Boolean

Returns:

  • (Boolean)


238
239
240
# File 'lib/prosereflect/schema/node_type.rb', line 238

def allows_mark_type?(mark_type)
  @mark_set.nil? || @mark_set.include?(mark_type)
end

#allows_marks?(marks) ⇒ Boolean

Returns:

  • (Boolean)


242
243
244
245
246
# File 'lib/prosereflect/schema/node_type.rb', line 242

def allows_marks?(marks)
  return true if @mark_set.nil?

  marks.all? { |mark| allows_mark_type?(mark.type) }
end

#check_attrs(attrs) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/prosereflect/schema/node_type.rb', line 224

def check_attrs(attrs)
  attrs ||= {}
  attrs.each_key do |attr_name|
    unless @attrs.key?(attr_name)
      raise Prosereflect::SchemaErrors::ValidationError,
            "Unsupported attribute #{attr_name} for node #{@name}"
    end
  end

  @attrs.each do |name, attr_def|
    attr_def.validate_value(attrs[name]) if attrs.key?(name)
  end
end

#check_content(fragment) ⇒ Object



217
218
219
220
221
222
# File 'lib/prosereflect/schema/node_type.rb', line 217

def check_content(fragment)
  return if valid_content?(fragment)

  raise Prosereflect::SchemaErrors::ValidationError,
        "Invalid content for node #{@name}: #{fragment.to_s[0, 50]}"
end

#compatible_content?(other) ⇒ Boolean

Returns:

  • (Boolean)


265
266
267
# File 'lib/prosereflect/schema/node_type.rb', line 265

def compatible_content?(other)
  self == other || @content_match.compatible?(other.content_match)
end

#compute_attrs(attrs) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/prosereflect/schema/node_type.rb', line 133

def compute_attrs(attrs)
  return default_attrs if attrs.nil?

  built = {}
  @attrs.each do |name, attr_def|
    if attrs.key?(name)
      built[name] = attrs[name]
    elsif attr_def.has_default?
      built[name] = attr_def.default
    else
      raise Prosereflect::SchemaErrors::ValidationError,
            "No value supplied for attribute #{name} on node #{@name}"
    end
  end
  built
end

#create(attrs = nil, content = nil, marks = []) ⇒ Object

Create a node with validation



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/prosereflect/schema/node_type.rb', line 151

def create(attrs = nil, content = nil, marks = [])
  if text?
    raise Prosereflect::SchemaErrors::Error,
          "NodeType.create cannot construct text nodes"
  end

  content_fragment = case content
                     when Fragment then content
                     when nil then Fragment.empty
                     when Array then Fragment.new(content)
                     when Node then Fragment.new([content])
                     end

  attrs = compute_attrs(attrs)
  Node.new(type: self, attrs: attrs, content: content_fragment,
           marks: marks)
end

#create_and_fill(attrs = nil, content = nil, marks = []) ⇒ Object

Create and fill a node with default content



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/prosereflect/schema/node_type.rb', line 185

def create_and_fill(attrs = nil, content = nil, marks = [])
  attrs = compute_attrs(attrs)
  content_fragment = Fragment.from(content)

  if content_fragment.size.positive?
    before = @content_match.fill_before(after: content_fragment,
                                        to_end: false)
    return nil unless before

    content_fragment = before.append(content_fragment)
  end

  matched = @content_match.match_fragment(content_fragment)
  return nil unless matched

  after = matched.fill_before(after: Fragment.empty, to_end: true)
  return nil unless after

  full_content = content_fragment.append(after)
  Node.new(type: self, attrs: attrs, content: full_content, marks: marks)
end

#create_checked(attrs = nil, content = nil, marks = []) ⇒ Object

Create a node with content validation



170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/prosereflect/schema/node_type.rb', line 170

def create_checked(attrs = nil, content = nil, marks = [])
  content_fragment = case content
                     when Fragment then content
                     when nil then Fragment.empty
                     when Array then Fragment.new(content)
                     when Node then Fragment.new([content])
                     end

  check_content(content_fragment)
  attrs = compute_attrs(attrs)
  Node.new(type: self, attrs: attrs, content: content_fragment,
           marks: marks)
end

#default_attrsObject



123
124
125
126
127
128
129
130
131
# File 'lib/prosereflect/schema/node_type.rb', line 123

def default_attrs
  defaults = {}
  @attrs.each do |name, attr_def|
    return nil unless attr_def.has_default?

    defaults[name] = attr_def.default
  end
  defaults.empty? ? nil : defaults
end

#has_required_attrs?Boolean

Returns:

  • (Boolean)


119
120
121
# File 'lib/prosereflect/schema/node_type.rb', line 119

def has_required_attrs?
  @attrs.values.any?(&:required?)
end

#in_group?(group_name) ⇒ Boolean

Returns:

  • (Boolean)


112
113
114
115
116
117
# File 'lib/prosereflect/schema/node_type.rb', line 112

def in_group?(group_name)
  return true if group_name == "inline" && text?
  return true if group_name == "block" && is_block?

  @groups.include?(group_name)
end

#is_atom?Boolean

Returns:

  • (Boolean)


104
105
106
# File 'lib/prosereflect/schema/node_type.rb', line 104

def is_atom?
  is_leaf? || @atom
end

#is_block?Boolean

Returns:

  • (Boolean)


86
87
88
# File 'lib/prosereflect/schema/node_type.rb', line 86

def is_block?
  !@inline && @name != "text"
end

#is_inline?Boolean Also known as: inline?

Returns:

  • (Boolean)


90
91
92
# File 'lib/prosereflect/schema/node_type.rb', line 90

def is_inline?
  !is_block?
end

#is_leaf?Boolean

Returns:

  • (Boolean)


100
101
102
# File 'lib/prosereflect/schema/node_type.rb', line 100

def is_leaf?
  @content_match == ContentMatch.empty || @content_match.edge_count.zero?
end

#is_textblock?Boolean

Returns:

  • (Boolean)


108
109
110
# File 'lib/prosereflect/schema/node_type.rb', line 108

def is_textblock?
  is_block? && @content_match.inline_content?
end

#text?Boolean

Returns:

  • (Boolean)


96
97
98
# File 'lib/prosereflect/schema/node_type.rb', line 96

def text?
  @name == "text"
end

#to_sObject



269
270
271
# File 'lib/prosereflect/schema/node_type.rb', line 269

def to_s
  "<NodeType #{@name}>"
end

#valid_content?(fragment) ⇒ Boolean

Returns:

  • (Boolean)


207
208
209
210
211
212
213
214
215
# File 'lib/prosereflect/schema/node_type.rb', line 207

def valid_content?(fragment)
  result = @content_match.match_fragment(fragment)
  return false unless result&.valid_end

  fragment.content.each do |child|
    return false unless allows_marks?(child.marks)
  end
  true
end