svg_conform 0.1.2 → 0.1.3

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 (28) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +192 -32
  3. data/README.adoc +55 -0
  4. data/lib/svg_conform/element_proxy.rb +10 -10
  5. data/lib/svg_conform/fast_document_analyzer.rb +22 -22
  6. data/lib/svg_conform/node_index_builder.rb +1 -0
  7. data/lib/svg_conform/requirements/allowed_elements_requirement.rb +29 -9
  8. data/lib/svg_conform/requirements/color_restrictions_requirement.rb +8 -6
  9. data/lib/svg_conform/requirements/font_family_requirement.rb +6 -2
  10. data/lib/svg_conform/requirements/forbidden_content_requirement.rb +2 -2
  11. data/lib/svg_conform/requirements/id_reference_requirement.rb +10 -7
  12. data/lib/svg_conform/requirements/invalid_id_references_requirement.rb +7 -6
  13. data/lib/svg_conform/requirements/link_validation_requirement.rb +2 -2
  14. data/lib/svg_conform/requirements/namespace_attributes_requirement.rb +16 -10
  15. data/lib/svg_conform/requirements/namespace_requirement.rb +11 -10
  16. data/lib/svg_conform/requirements/no_external_css_requirement.rb +4 -4
  17. data/lib/svg_conform/requirements/no_external_fonts_requirement.rb +2 -2
  18. data/lib/svg_conform/requirements/no_external_images_requirement.rb +2 -2
  19. data/lib/svg_conform/requirements/style_promotion_requirement.rb +7 -0
  20. data/lib/svg_conform/requirements/viewbox_required_requirement.rb +7 -7
  21. data/lib/svg_conform/sax_document.rb +2 -2
  22. data/lib/svg_conform/sax_validation_handler.rb +14 -10
  23. data/lib/svg_conform/validation_context.rb +9 -7
  24. data/lib/svg_conform/validator.rb +2 -2
  25. data/lib/svg_conform/version.rb +1 -1
  26. data/spec/spec_helper.rb +3 -0
  27. data/spec/support/shared_examples_for_validation_modes.rb +71 -0
  28. metadata +3 -2
@@ -121,6 +121,13 @@ module SvgConform
121
121
  end
122
122
 
123
123
  def validate_sax_element(element, context)
124
+ # Skip if parent is structurally invalid (matches DOM behavior)
125
+ if element.parent && context.node_structurally_invalid?(element.parent)
126
+ # Mark this element as invalid too since it won't be in final document
127
+ context.mark_node_structurally_invalid(element)
128
+ return
129
+ end
130
+
124
131
  # Skip foreign namespace elements if configured (let NamespaceRequirement handle them)
125
132
  if skip_foreign_namespaces && foreign_namespace_sax?(element)
126
133
  return
@@ -135,7 +142,7 @@ module SvgConform
135
142
  message: "Element '#{element_name}' is not allowed in this profile",
136
143
  node: element,
137
144
  severity: :error,
138
- data: { element: element_name }
145
+ data: { element: element_name },
139
146
  )
140
147
  return
141
148
  end
@@ -149,7 +156,7 @@ module SvgConform
149
156
  message: "The element '#{element_name}' is not allowed as a child of '#{parent_name}'",
150
157
  node: element,
151
158
  severity: :error,
152
- data: { element: element_name, parent: parent_name }
159
+ data: { element: element_name, parent: parent_name },
153
160
  )
154
161
  # Mark node as structurally invalid
155
162
  context.mark_node_structurally_invalid(element)
@@ -166,7 +173,7 @@ module SvgConform
166
173
  message: "Element '#{element_name}' is not allowed in this profile",
167
174
  node: element,
168
175
  severity: :error,
169
- data: { element: element_name }
176
+ data: { element: element_name },
170
177
  )
171
178
  # Mark as structurally invalid
172
179
  context.mark_node_structurally_invalid(element)
@@ -184,7 +191,7 @@ module SvgConform
184
191
  requirement_id: id,
185
192
  message: error[:message],
186
193
  node: element,
187
- severity: :error
194
+ severity: :error,
188
195
  )
189
196
  end
190
197
  end
@@ -339,7 +346,13 @@ module SvgConform
339
346
  def foreign_namespace_sax?(element)
340
347
  return false unless skip_foreign_namespaces
341
348
 
342
- # Check if element has a namespace
349
+ # Check if element name has a namespace prefix (e.g., rdf:RDF, cc:Work)
350
+ if element.name.include?(":")
351
+ # Element has prefix, it's in a foreign namespace
352
+ return true
353
+ end
354
+
355
+ # Also check xmlns attribute
343
356
  element_namespace = element.namespace
344
357
 
345
358
  # No namespace or empty namespace means SVG namespace (default)
@@ -374,7 +387,9 @@ module SvgConform
374
387
 
375
388
  return errors unless element_configs&.any?
376
389
 
377
- element_config = element_configs.find { |config| config.tag == element_name }
390
+ element_config = element_configs.find do |config|
391
+ config.tag == element_name
392
+ end
378
393
  return errors unless element_config&.attr
379
394
 
380
395
  allowed_attrs = []
@@ -414,6 +429,11 @@ module SvgConform
414
429
  attr_name = attr.name.downcase
415
430
  next if attr_name.start_with?("xmlns:")
416
431
  next if attr_name.start_with?("xml:")
432
+
433
+ # Skip namespaced attributes (those with colon prefix like inkscape:label)
434
+ # This matches DOM behavior where XPath skips attributes with namespaces
435
+ next if attr_name.include?(":") && !attr_name.start_with?("xmlns:")
436
+
417
437
  next if attr.namespace
418
438
  next if attr_name.start_with?("data-")
419
439
 
@@ -422,7 +442,7 @@ module SvgConform
422
442
  errors << {
423
443
  type: :explicitly_disallowed,
424
444
  attribute: attr_name,
425
- message: "Attribute '#{attr_name}' is explicitly disallowed on element '#{element_name}'"
445
+ message: "Attribute '#{attr_name}' is explicitly disallowed on element '#{element_name}'",
426
446
  }
427
447
  next
428
448
  end
@@ -433,7 +453,7 @@ module SvgConform
433
453
  errors << {
434
454
  type: :not_allowed,
435
455
  attribute: attr_name,
436
- message: "Attribute '#{attr_name}' is not allowed on element '#{element_name}'"
456
+ message: "Attribute '#{attr_name}' is not allowed on element '#{element_name}'",
437
457
  }
438
458
  end
439
459
 
@@ -462,7 +482,7 @@ module SvgConform
462
482
  errors << {
463
483
  type: :globally_disallowed,
464
484
  attribute: attr_name,
465
- message: "Attribute '#{attr_name}' is globally disallowed in this profile"
485
+ message: "Attribute '#{attr_name}' is globally disallowed in this profile",
466
486
  }
467
487
  end
468
488
 
@@ -85,7 +85,8 @@ module SvgConform
85
85
  return if context.node_structurally_invalid?(element)
86
86
 
87
87
  # Check color-related attributes
88
- color_attributes = %w[fill stroke color stop-color flood-color lighting-color]
88
+ color_attributes = %w[fill stroke color stop-color flood-color
89
+ lighting-color]
89
90
 
90
91
  color_attributes.each do |attr_name|
91
92
  value = element.raw_attributes[attr_name]
@@ -101,8 +102,8 @@ module SvgConform
101
102
  data: {
102
103
  attribute: attr_name,
103
104
  value: value,
104
- element: element.name
105
- }
105
+ element: element.name,
106
+ },
106
107
  )
107
108
  end
108
109
 
@@ -111,7 +112,8 @@ module SvgConform
111
112
  return unless style_value
112
113
 
113
114
  styles = parse_style(style_value)
114
- color_properties = %w[fill stroke color stop-color flood-color lighting-color]
115
+ color_properties = %w[fill stroke color stop-color flood-color
116
+ lighting-color]
115
117
 
116
118
  color_properties.each do |prop|
117
119
  value = styles[prop]
@@ -127,8 +129,8 @@ module SvgConform
127
129
  data: {
128
130
  attribute: prop,
129
131
  value: value,
130
- element: element.name
131
- }
132
+ element: element.name,
133
+ },
132
134
  )
133
135
  end
134
136
  end
@@ -47,19 +47,23 @@ module SvgConform
47
47
  end
48
48
 
49
49
  def validate_sax_element(element, context)
50
+ # Skip if parent is structurally invalid
51
+ return if element.parent && context.node_structurally_invalid?(element.parent)
52
+
50
53
  # Check font-family attribute only
51
54
  font_family = element.raw_attributes["font-family"]
52
55
  return unless font_family
53
56
 
54
57
  if svgcheck_compatibility
55
- check_font_family_svgcheck_mode(element, context, font_family, "font-family")
58
+ check_font_family_svgcheck_mode(element, context, font_family,
59
+ "font-family")
56
60
  elsif !valid_font_family?(font_family)
57
61
  context.add_error(
58
62
  requirement_id: id,
59
63
  message: "Font family '#{font_family}' is not allowed in this profile",
60
64
  node: element,
61
65
  severity: :error,
62
- data: { attribute: "font-family", value: font_family }
66
+ data: { attribute: "font-family", value: font_family },
63
67
  )
64
68
  end
65
69
  end
@@ -63,7 +63,7 @@ module SvgConform
63
63
  requirement_id: id,
64
64
  message: "Forbidden element '#{element.name}' is not allowed",
65
65
  node: element,
66
- severity: :error
66
+ severity: :error,
67
67
  )
68
68
  end
69
69
 
@@ -76,7 +76,7 @@ module SvgConform
76
76
  requirement_id: id,
77
77
  message: "Forbidden attribute '#{attr_name}' is not allowed",
78
78
  node: element,
79
- severity: :error
79
+ severity: :error,
80
80
  )
81
81
  end
82
82
  end
@@ -10,7 +10,7 @@ module SvgConform
10
10
  true
11
11
  end
12
12
 
13
- def collect_sax_data(element, context)
13
+ def collect_sax_data(element, _context)
14
14
  # Initialize collections on first call
15
15
  @collected_ids ||= Set.new
16
16
  @collected_url_refs ||= []
@@ -22,7 +22,8 @@ module SvgConform
22
22
  @collected_ids.add(id_value) if id_value && !id_value.empty?
23
23
 
24
24
  # Collect url() references
25
- url_attributes = %w[fill stroke marker-start marker-mid marker-end clip-path mask filter]
25
+ url_attributes = %w[fill stroke marker-start marker-mid marker-end
26
+ clip-path mask filter]
26
27
  url_attributes.each do |attr_name|
27
28
  attr_value = element.raw_attributes[attr_name]
28
29
  next unless attr_value
@@ -45,12 +46,13 @@ module SvgConform
45
46
  # Collect href references
46
47
  href_value = element.raw_attributes["href"] || element.raw_attributes["xlink:href"]
47
48
  if href_value&.start_with?("#")
48
- ref_id = href_value[1..] # Remove #
49
+ ref_id = href_value[1..] # Remove #
49
50
  @collected_href_refs << [element, ref_id]
50
51
  end
51
52
 
52
53
  # Collect other ID references
53
- id_ref_attributes = %w[for aria-labelledby aria-describedby aria-controls aria-owns]
54
+ id_ref_attributes = %w[for aria-labelledby aria-describedby
55
+ aria-controls aria-owns]
54
56
  id_ref_attributes.each do |attr_name|
55
57
  attr_value = element.raw_attributes[attr_name]
56
58
  next unless attr_value
@@ -58,6 +60,7 @@ module SvgConform
58
60
  ref_ids = attr_value.split(/\s+/)
59
61
  ref_ids.each do |ref_id|
60
62
  next if ref_id.empty?
63
+
61
64
  @collected_other_refs << [element, ref_id, attr_name]
62
65
  end
63
66
  end
@@ -77,7 +80,7 @@ module SvgConform
77
80
  context.add_error(
78
81
  node: element,
79
82
  message: message,
80
- requirement_id: id
83
+ requirement_id: id,
81
84
  )
82
85
  end
83
86
 
@@ -87,7 +90,7 @@ module SvgConform
87
90
  context.add_error(
88
91
  node: element,
89
92
  message: "Reference to undefined ID '#{ref_id}' in href attribute",
90
- requirement_id: id
93
+ requirement_id: id,
91
94
  )
92
95
  end
93
96
 
@@ -97,7 +100,7 @@ module SvgConform
97
100
  context.add_error(
98
101
  node: element,
99
102
  message: "Reference to undefined ID '#{ref_id}' in #{attr_name} attribute",
100
- requirement_id: id
103
+ requirement_id: id,
101
104
  )
102
105
  end
103
106
  end
@@ -24,15 +24,15 @@ module SvgConform
24
24
  def initialize(*args)
25
25
  super
26
26
  @collected_ids = Set.new
27
- @use_element_refs = [] # [element, ref_id, href]
28
- @other_refs = [] # [element, ref_id, attr_name, value]
27
+ @use_element_refs = [] # [element, ref_id, href]
28
+ @other_refs = [] # [element, ref_id, attr_name, value]
29
29
  end
30
30
 
31
31
  def needs_deferred_validation?
32
32
  true
33
33
  end
34
34
 
35
- def collect_sax_data(element, context)
35
+ def collect_sax_data(element, _context)
36
36
  # Initialize collections on first call
37
37
  @collected_ids ||= Set.new
38
38
  @use_element_refs ||= []
@@ -53,7 +53,8 @@ module SvgConform
53
53
 
54
54
  # Collect other ID references if enabled
55
55
  if check_other_references
56
- id_reference_attributes = %w[clip-path mask filter marker-start marker-mid marker-end fill stroke]
56
+ id_reference_attributes = %w[clip-path mask filter marker-start
57
+ marker-mid marker-end fill stroke]
57
58
 
58
59
  id_reference_attributes.each do |attr_name|
59
60
  attr_value = element.raw_attributes[attr_name]
@@ -87,7 +88,7 @@ module SvgConform
87
88
  node: element,
88
89
  message: "use element references non-existent ID: #{ref_id}",
89
90
  severity: :error,
90
- data: { invalid_id: ref_id, href: href }
91
+ data: { invalid_id: ref_id, href: href },
91
92
  )
92
93
  end
93
94
 
@@ -107,7 +108,7 @@ module SvgConform
107
108
  node: element,
108
109
  message: message,
109
110
  severity: :error,
110
- data: { invalid_id: ref_id, attribute: attr_name, value: value }
111
+ data: { invalid_id: ref_id, attribute: attr_name, value: value },
111
112
  )
112
113
  end
113
114
  end
@@ -54,7 +54,7 @@ module SvgConform
54
54
  requirement_id: id,
55
55
  message: "Link href '#{href_value}' contains non-ASCII characters",
56
56
  node: element,
57
- severity: :error
57
+ severity: :error,
58
58
  )
59
59
  end
60
60
 
@@ -70,7 +70,7 @@ module SvgConform
70
70
  requirement_id: id,
71
71
  message: "IRI attribute '#{attr_name}' value '#{iri_value}' contains non-ASCII characters",
72
72
  node: element,
73
- severity: :error
73
+ severity: :error,
74
74
  )
75
75
  end
76
76
  end
@@ -42,8 +42,16 @@ module SvgConform
42
42
  end
43
43
 
44
44
  def validate_sax_element(element, context)
45
+ # Extract local name without prefix for exemption check
46
+ local_name = if element.name.include?(":")
47
+ element.name.split(":",
48
+ 2).last
49
+ else
50
+ element.name
51
+ end
52
+
45
53
  # Skip validation for exempt elements (e.g., RDF metadata elements)
46
- return if exempt_elements.include?(element.name)
54
+ return if exempt_elements.include?(local_name) || exempt_elements.include?(element.name)
47
55
 
48
56
  # Check all attributes for namespace violations
49
57
  element.attributes.each do |attr|
@@ -67,12 +75,12 @@ module SvgConform
67
75
 
68
76
  # Determine if this namespace is invalid based on configuration
69
77
  invalid_namespace = if allowed_namespaces.empty?
70
- # Blacklist mode: disallowed namespaces are forbidden
71
- disallowed_namespaces.include?(namespace_uri)
72
- else
73
- # Whitelist mode: only allowed namespaces are permitted
74
- !allowed_namespaces.include?(namespace_uri)
75
- end
78
+ # Blacklist mode: disallowed namespaces are forbidden
79
+ disallowed_namespaces.include?(namespace_uri)
80
+ else
81
+ # Whitelist mode: only allowed namespaces are permitted
82
+ !allowed_namespaces.include?(namespace_uri)
83
+ end
76
84
 
77
85
  return unless invalid_namespace
78
86
 
@@ -81,7 +89,7 @@ module SvgConform
81
89
  message: "Element '#{element.name}' does not allow attributes with namespace '#{namespace_uri}'",
82
90
  node: element,
83
91
  severity: :error,
84
- data: { attribute: attr_name, namespace: namespace_uri }
92
+ data: { attribute: attr_name, namespace: namespace_uri },
85
93
  )
86
94
  end
87
95
 
@@ -100,8 +108,6 @@ module SvgConform
100
108
  nil
101
109
  end
102
110
 
103
- private
104
-
105
111
  def check_attribute_nodes(node, context)
106
112
  node.attribute_nodes.each do |attr|
107
113
  # Check if attribute has a namespace
@@ -213,8 +213,8 @@ module SvgConform
213
213
  data: {
214
214
  element_name: element.name,
215
215
  namespace: element_namespace,
216
- allowed_namespaces: effective_allowed_namespaces
217
- }
216
+ allowed_namespaces: effective_allowed_namespaces,
217
+ },
218
218
  )
219
219
  return
220
220
  end
@@ -230,24 +230,25 @@ module SvgConform
230
230
  data: {
231
231
  element_name: element.name,
232
232
  namespace: element_namespace,
233
- disallowed_namespaces: disallowed_namespaces
234
- }
233
+ disallowed_namespaces: disallowed_namespaces,
234
+ },
235
235
  )
236
236
  end
237
237
 
238
238
  private
239
239
 
240
240
  def get_element_namespace_sax(element)
241
- # Try to get namespace from the element
242
- namespace = element.namespace
243
- return namespace if namespace && !namespace.empty?
244
-
245
- # If no namespace found, check if element has a prefix (indicating it's namespaced)
241
+ # Check if element name has a namespace prefix (e.g., rdf:RDF, cc:Work)
246
242
  if element.name.include?(":")
247
243
  prefix = element.name.split(":").first
248
- return find_namespace_uri_for_prefix_sax(element, prefix)
244
+ namespace_uri = find_namespace_uri_for_prefix_sax(element, prefix)
245
+ return namespace_uri if namespace_uri
249
246
  end
250
247
 
248
+ # Try to get namespace from xmlns attribute
249
+ namespace = element.namespace
250
+ return namespace if namespace && !namespace.empty?
251
+
251
252
  nil
252
253
  end
253
254
 
@@ -131,7 +131,7 @@ module SvgConform
131
131
  requirement_id: id,
132
132
  message: "External CSS import not allowed: #{url}",
133
133
  node: element,
134
- severity: :error
134
+ severity: :error,
135
135
  )
136
136
  end
137
137
  end
@@ -145,7 +145,7 @@ module SvgConform
145
145
  requirement_id: id,
146
146
  message: "External CSS import not allowed: #{url}",
147
147
  node: element,
148
- severity: :error
148
+ severity: :error,
149
149
  )
150
150
  end
151
151
 
@@ -161,7 +161,7 @@ module SvgConform
161
161
  requirement_id: id,
162
162
  message: "External CSS link not allowed: #{href}",
163
163
  node: element,
164
- severity: :error
164
+ severity: :error,
165
165
  )
166
166
  end
167
167
 
@@ -179,7 +179,7 @@ module SvgConform
179
179
  requirement_id: id,
180
180
  message: "External URL reference in style attribute not allowed: #{url}",
181
181
  node: element,
182
- severity: :error
182
+ severity: :error,
183
183
  )
184
184
  end
185
185
 
@@ -127,7 +127,7 @@ module SvgConform
127
127
  requirement_id: id,
128
128
  message: "External font reference not allowed: #{url}. Fonts must be embedded as data URIs.",
129
129
  node: element,
130
- severity: :error
130
+ severity: :error,
131
131
  )
132
132
  end
133
133
  end
@@ -154,7 +154,7 @@ module SvgConform
154
154
  requirement_id: id,
155
155
  message: "External font URL in style attribute not allowed: #{url}. Fonts must be embedded as data URIs.",
156
156
  node: element,
157
- severity: :error
157
+ severity: :error,
158
158
  )
159
159
  end
160
160
 
@@ -88,7 +88,7 @@ module SvgConform
88
88
  requirement_id: id,
89
89
  message: "External image reference not allowed: #{href}. Images must be embedded as data URIs.",
90
90
  node: element,
91
- severity: :error
91
+ severity: :error,
92
92
  )
93
93
  end
94
94
 
@@ -105,7 +105,7 @@ module SvgConform
105
105
  requirement_id: id,
106
106
  message: "External image URL in style attribute not allowed: #{url}. Images must be embedded as data URIs.",
107
107
  node: element,
108
- severity: :error
108
+ severity: :error,
109
109
  )
110
110
  end
111
111
  end
@@ -20,6 +20,13 @@ module SvgConform
20
20
  end
21
21
  end
22
22
 
23
+ def validate_sax_element(element, context)
24
+ style_attr = element.raw_attributes["style"]
25
+ return if style_attr.nil? || style_attr.strip.empty?
26
+
27
+ validate_style_properties(element, style_attr, context)
28
+ end
29
+
23
30
  private
24
31
 
25
32
  def validate_style_properties(element, style_attr, context)
@@ -95,7 +95,7 @@ module SvgConform
95
95
  message: "SVG root element must have a viewBox attribute",
96
96
  node: element,
97
97
  severity: :error,
98
- data: { missing_attribute: "viewBox" }
98
+ data: { missing_attribute: "viewBox" },
99
99
  )
100
100
 
101
101
  # Add informational message about calculated viewBox if width/height are present
@@ -112,8 +112,8 @@ module SvgConform
112
112
  data: {
113
113
  calculated_viewbox: calculated_viewbox,
114
114
  source_width: width,
115
- source_height: height
116
- }
115
+ source_height: height,
116
+ },
117
117
  )
118
118
  end
119
119
  return
@@ -130,8 +130,8 @@ module SvgConform
130
130
  severity: :error,
131
131
  data: {
132
132
  viewbox_value: viewbox,
133
- parsed_parts: parts
134
- }
133
+ parsed_parts: parts,
134
+ },
135
135
  )
136
136
  return
137
137
  end
@@ -150,8 +150,8 @@ module SvgConform
150
150
  data: {
151
151
  viewbox_value: viewbox,
152
152
  width: width,
153
- height: height
154
- }
153
+ height: height,
154
+ },
155
155
  )
156
156
  end
157
157
 
@@ -27,14 +27,14 @@ module SvgConform
27
27
  def validate_with_profile(profile)
28
28
  handler = SaxValidationHandler.new(profile)
29
29
  parser = Nokogiri::XML::SAX::Parser.new(handler)
30
-
30
+
31
31
  begin
32
32
  parser.parse(@content)
33
33
  rescue StandardError => e
34
34
  # Handle parse errors
35
35
  handler.add_parse_error(e)
36
36
  end
37
-
37
+
38
38
  handler.result
39
39
  end
40
40
 
@@ -13,11 +13,11 @@ module SvgConform
13
13
 
14
14
  def initialize(profile)
15
15
  @profile = profile
16
- @element_stack = [] # Track parent-child hierarchy
17
- @path_stack = [] # Current element path
18
- @position_counters = [] # Stack of sibling counters per level
16
+ @element_stack = [] # Track parent-child hierarchy
17
+ @path_stack = [] # Current element path
18
+ @position_counters = [] # Stack of sibling counters per level
19
19
  @parse_errors = []
20
- @result = nil # Will be set in end_document
20
+ @result = nil # Will be set in end_document
21
21
 
22
22
  # Create validation context (without document reference for SAX)
23
23
  @context = create_sax_context
@@ -50,13 +50,13 @@ module SvgConform
50
50
  attributes: attrs,
51
51
  position: position,
52
52
  path: @path_stack.dup,
53
- parent: @element_stack.last
53
+ parent: @element_stack.last,
54
54
  )
55
55
 
56
56
  # Push to stacks
57
57
  @element_stack.push(element)
58
58
  @path_stack.push("#{name}[#{position}]")
59
- @position_counters.push({}) # New level for this element's children
59
+ @position_counters.push({}) # New level for this element's children
60
60
 
61
61
  # Validate with immediate requirements
62
62
  @immediate_requirements.each do |req|
@@ -65,12 +65,15 @@ module SvgConform
65
65
 
66
66
  # Deferred requirements may need to collect data
67
67
  @deferred_requirements.each do |req|
68
- req.collect_sax_data(element, @context) if req.respond_to?(:collect_sax_data)
68
+ if req.respond_to?(:collect_sax_data)
69
+ req.collect_sax_data(element,
70
+ @context)
71
+ end
69
72
  end
70
73
  end
71
74
 
72
75
  # SAX Event: Element end tag
73
- def end_element(name)
76
+ def end_element(_name)
74
77
  @element_stack.pop
75
78
  @path_stack.pop
76
79
  @position_counters.pop
@@ -79,6 +82,7 @@ module SvgConform
79
82
  # SAX Event: Text content
80
83
  def characters(string)
81
84
  return if @element_stack.empty?
85
+
82
86
  @element_stack.last.text_content << string
83
87
  end
84
88
 
@@ -109,7 +113,7 @@ module SvgConform
109
113
  node: nil,
110
114
  message: "Parse error: #{error.message}",
111
115
  requirement_id: "parse_error",
112
- severity: :error
116
+ severity: :error,
113
117
  )
114
118
  end
115
119
 
@@ -138,7 +142,7 @@ module SvgConform
138
142
  context.instance_variable_set(:@data, {})
139
143
  context.instance_variable_set(:@structurally_invalid_node_ids, Set.new)
140
144
  context.instance_variable_set(:@node_id_cache, {})
141
- context.instance_variable_set(:@cache_populated, true) # Skip population for SAX
145
+ context.instance_variable_set(:@cache_populated, true) # Skip population for SAX
142
146
  context
143
147
  end
144
148