svg_conform 0.1.8 → 0.1.9

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 28ff5f6535023bd5817354101e080198c1f5e8b6a9cf0694555a45734e653283
4
- data.tar.gz: ef1f87272855005ac53e4bc2f06467298c3e58dd3a332a5207d9f27aabf3b9a4
3
+ metadata.gz: e4879f5648a06585ca68ae1430f928c327b267c3de96e92ae54333a002127243
4
+ data.tar.gz: 7fb876639e3d0d935020089981803d43e2648131bf7a074b0b127605c99009ff
5
5
  SHA512:
6
- metadata.gz: d4a56e3f70abddf80258a6774300c99db15d996f892ba8c8fd6a7d9e78d7c19432a3264d8958312c5e7cff5006628a648775dd3cbc4d06d9de0fde5ef179e9d3
7
- data.tar.gz: 93d8c6ef9a74d628fa6b48aa4ab92ba41c4abc804d29d8b2670656e6114e869b614c0fef80363d393e7d72311e903a113fcf74cb060e8d41ea54f528a0442a77
6
+ metadata.gz: ca32f36fb1bcb98fc25be20fc237291f5fa0efedd55c71cb62b8862e60c3628f05673a0d16def9deda237e4a3b53312c78e39d3daf2bb42700e58f388a2d38c8
7
+ data.tar.gz: '0086cffd30f8c81aba4e157caf6a0bfe197f4ce597971941e4ddcc136f0ac7cf11460eb8e4103094a6636aa546c5d687c0f724697e441c509a94e1697bc41857'
data/.rubocop_todo.yml CHANGED
@@ -1,80 +1,26 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-01-21 09:23:23 UTC using RuboCop version 1.82.1.
3
+ # on 2026-01-22 08:33:31 UTC using RuboCop version 1.82.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 4
10
- # This cop supports safe autocorrection (--autocorrect).
11
- # Configuration parameters: EnforcedStyle, IndentationWidth.
12
- # SupportedStyles: with_first_argument, with_fixed_indentation
13
- Layout/ArgumentAlignment:
14
- Exclude:
15
- - 'lib/svg_conform/validation_context.rb'
16
-
17
- # Offense count: 2
9
+ # Offense count: 1
18
10
  # This cop supports safe autocorrection (--autocorrect).
19
11
  # Configuration parameters: EnforcedStyleAlignWith.
20
12
  # SupportedStylesAlignWith: either, start_of_block, start_of_line
21
13
  Layout/BlockAlignment:
22
14
  Exclude:
23
- - 'lib/svg_conform/sax_validation_handler.rb'
24
15
  - 'spec/svg_conform/profiles/svg_1_2_rfc_profile_spec.rb'
25
16
 
26
- # Offense count: 1
27
- # This cop supports safe autocorrection (--autocorrect).
28
- Layout/BlockEndNewline:
29
- Exclude:
30
- - 'lib/svg_conform/sax_validation_handler.rb'
31
-
32
- # Offense count: 1
33
- # This cop supports safe autocorrection (--autocorrect).
34
- Layout/ElseAlignment:
35
- Exclude:
36
- - 'lib/svg_conform/requirements/no_external_css_requirement.rb'
37
-
38
- # Offense count: 1
39
- # This cop supports safe autocorrection (--autocorrect).
40
- # Configuration parameters: EnforcedStyleAlignWith.
41
- # SupportedStylesAlignWith: keyword, variable, start_of_line
42
- Layout/EndAlignment:
43
- Exclude:
44
- - 'lib/svg_conform/requirements/no_external_css_requirement.rb'
45
-
46
- # Offense count: 4
47
- # This cop supports safe autocorrection (--autocorrect).
48
- # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
49
- # SupportedHashRocketStyles: key, separator, table
50
- # SupportedColonStyles: key, separator, table
51
- # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
52
- Layout/HashAlignment:
53
- Exclude:
54
- - 'lib/svg_conform/validation_context.rb'
55
-
56
- # Offense count: 4
57
- # This cop supports safe autocorrection (--autocorrect).
58
- # Configuration parameters: Width, AllowedPatterns.
59
- Layout/IndentationWidth:
60
- Exclude:
61
- - 'lib/svg_conform/requirements/no_external_css_requirement.rb'
62
- - 'lib/svg_conform/sax_validation_handler.rb'
63
-
64
- # Offense count: 643
17
+ # Offense count: 647
65
18
  # This cop supports safe autocorrection (--autocorrect).
66
19
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
67
20
  # URISchemes: http, https
68
21
  Layout/LineLength:
69
22
  Enabled: false
70
23
 
71
- # Offense count: 2
72
- # This cop supports safe autocorrection (--autocorrect).
73
- # Configuration parameters: AllowInHeredoc.
74
- Layout/TrailingWhitespace:
75
- Exclude:
76
- - 'lib/svg_conform/validation_context.rb'
77
-
78
24
  # Offense count: 2
79
25
  # Configuration parameters: AllowedMethods.
80
26
  # AllowedMethods: enums
@@ -124,12 +70,12 @@ Lint/UnreachableCode:
124
70
  Exclude:
125
71
  - 'lib/svg_conform/commands/check.rb'
126
72
 
127
- # Offense count: 150
73
+ # Offense count: 151
128
74
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
129
75
  Metrics/AbcSize:
130
76
  Enabled: false
131
77
 
132
- # Offense count: 23
78
+ # Offense count: 24
133
79
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
134
80
  # AllowedMethods: refine
135
81
  Metrics/BlockLength:
@@ -140,12 +86,12 @@ Metrics/BlockLength:
140
86
  Metrics/BlockNesting:
141
87
  Max: 4
142
88
 
143
- # Offense count: 126
89
+ # Offense count: 127
144
90
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
145
91
  Metrics/CyclomaticComplexity:
146
92
  Enabled: false
147
93
 
148
- # Offense count: 262
94
+ # Offense count: 263
149
95
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
150
96
  Metrics/MethodLength:
151
97
  Max: 154
@@ -155,7 +101,7 @@ Metrics/MethodLength:
155
101
  Metrics/ParameterLists:
156
102
  Max: 9
157
103
 
158
- # Offense count: 100
104
+ # Offense count: 101
159
105
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
160
106
  Metrics/PerceivedComplexity:
161
107
  Enabled: false
@@ -193,7 +139,7 @@ RSpec/DescribeClass:
193
139
  - 'spec/svg_conform/references/integration_spec.rb'
194
140
  - 'spec/svgcheck_compatibility_spec.rb'
195
141
 
196
- # Offense count: 136
142
+ # Offense count: 139
197
143
  # Configuration parameters: CountAsOne.
198
144
  RSpec/ExampleLength:
199
145
  Max: 53
@@ -258,11 +204,3 @@ Style/OptionalBooleanParameter:
258
204
  Style/RedundantCondition:
259
205
  Exclude:
260
206
  - 'lib/svg_conform/external_checkers/svgcheck/parser.rb'
261
-
262
- # Offense count: 1
263
- # This cop supports safe autocorrection (--autocorrect).
264
- # Configuration parameters: EnforcedStyleForMultiline.
265
- # SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma
266
- Style/TrailingCommaInArguments:
267
- Exclude:
268
- - 'lib/svg_conform/sax_validation_handler.rb'
@@ -16,6 +16,8 @@ requirements:
16
16
  allowed_namespaces:
17
17
  - "http://www.w3.org/2000/svg"
18
18
  - "" # Default namespace (no prefix)
19
+ allowed_attribute_patterns:
20
+ - "on*" # Allow event handler attributes (onmouseover, onmouseout, etc.) per issue #57
19
21
  element_configs:
20
22
  # Direct encoding from svgcheck word_properties.py elements dictionary
21
23
  - tag: "svg"
@@ -156,6 +158,7 @@ requirements:
156
158
  description: "Validates ID references point to existing elements"
157
159
 
158
160
  # Forbidden content - no multimedia, scripting, etc.
161
+ # Note: onmouseover, onmouseout, onfocus, onblur are intentionally allowed per issue #57
159
162
  - id: "forbidden_content"
160
163
  type: "ForbiddenContentRequirement"
161
164
  description: "Prohibits multimedia, scripting content"
data/docs/profiles.adoc CHANGED
@@ -268,10 +268,24 @@ See link:remediation.adoc#namespace-attribute-remediation[NamespaceAttributeReme
268
268
  * **Flexible colors**: Any colors allowed (unlike RFC 7996 black/white restriction)
269
269
  * **Flexible fonts**: Any font families allowed (unlike RFC 7996 generic-only restriction)
270
270
  * **Flexible styles**: Any CSS styles allowed
271
+ * **Event handlers allowed**: Supports event attributes (`on*`) for interactivity
271
272
  * **Self-contained resources**: All CSS and fonts must be embedded
272
273
  * **Structural compliance**: Proper namespaces and viewBox required
273
274
  * **No external dependencies**: External CSS and fonts strictly prohibited
274
275
 
276
+ **Event Handler Support**:
277
+
278
+ The metanorma profile allows SVG event handler attributes (e.g., `onmouseover`, `onmouseout`, `onfocus`, `onblur`) for interactive elements. This is configured using wildcard attribute patterns:
279
+
280
+ [source,yaml]
281
+ ----
282
+ requirements:
283
+ - type: "AllowedElementsRequirement"
284
+ id: "allowed_elements"
285
+ allowed_attribute_patterns:
286
+ - "on*" # Allow all event handler attributes
287
+ ----
288
+
275
289
  **Requirements**:
276
290
  [source,yaml]
277
291
  ----
@@ -174,6 +174,62 @@ foreign). Used with `skip_foreign_namespaces`. Default: `[]`.
174
174
  namespaces. When `true`, RDF namespace elements are considered valid and
175
175
  skipped. Default: `false`.
176
176
 
177
+ `allowed_attribute_patterns`:: List of wildcard attribute patterns to exempt from
178
+ validation. Attributes matching any pattern will be allowed regardless of whether
179
+ they appear in element `attributes` lists. Patterns support suffix wildcards (`*`).
180
+ Default: `[]`.
181
+
182
+ * **Common patterns**:
183
+ +
184
+ **`"on*"`**:: Allows all event handler attributes (`onclick`, `onmouseover`, `onmouseout`, etc.)
185
+ **`"data-*"`**:: Allows all `data-` custom attributes
186
+
187
+ * **Precedence**: When an attribute matches both an allowed pattern and an
188
+ element-specific disallowed attribute (prefixed with `!`), the allowed pattern
189
+ takes precedence and a warning is emitted during validation.
190
+
191
+ ==== Wildcard attribute patterns
192
+
193
+ .Using allowed_attribute_patterns to exempt event handler attributes
194
+ [example]
195
+ ====
196
+ [source,yaml]
197
+ ----
198
+ - id: "svg_elements_with_events"
199
+ type: "AllowedElementsRequirement"
200
+ description: "Restrict to allowed SVG elements but allow event handlers"
201
+ allowed_attribute_patterns:
202
+ - "on*" # Allow all event handler attributes
203
+ - "data-*" # Allow all data-* custom attributes
204
+ check_attributes: true
205
+ element_configs:
206
+ - tag: "polygon"
207
+ attributes: ["points", "id", "fill"]
208
+ # !onclick is disallowed, but on* pattern takes precedence with warning
209
+ - tag: "rect"
210
+ attributes: ["x", "y", "width", "height"]
211
+ ----
212
+ ====
213
+
214
+ With this configuration:
215
+ * Event attributes (`onclick`, `onmouseover`, etc.) are allowed on all elements
216
+ * `data-*` custom attributes are allowed on all elements
217
+ * Other attributes not in element `attributes` lists are still rejected
218
+
219
+ .Conflict detection warning
220
+ [example]
221
+ ====
222
+ When an allowed pattern conflicts with an element-specific disallowed attribute
223
+ (prefixed with `!`), a warning is emitted:
224
+
225
+ [source,text]
226
+ ----
227
+ Configuration warning in svg_elements_with_events: Element 'polygon' has
228
+ disallowed attributes [onclick] that match allowed_attribute_patterns [on*].
229
+ Allowed patterns take precedence over element-specific disallowed attributes.
230
+ ----
231
+ ====
232
+
177
233
  ==== Configuration
178
234
 
179
235
  .Example configuration of AllowedElementsRequirement
@@ -8,20 +8,19 @@ module SvgConform
8
8
  # Validates that only allowed SVG elements and their attributes are used
9
9
  class AllowedElementsRequirement < BaseRequirement
10
10
  attribute :type, :string, default: -> { "AllowedElementsRequirement" }
11
- attribute :element_configs, ElementRequirementConfig, collection: true, default: -> {
12
- []
13
- }
14
- attribute :disallowed_elements, :string, collection: true, default: -> {
15
- []
16
- }
11
+ attribute :element_configs, ElementRequirementConfig, collection: true,
12
+ initialize_empty: true
13
+ attribute :disallowed_elements, :string, collection: true,
14
+ initialize_empty: true
17
15
  attribute :check_attributes, :boolean, default: false
18
16
  attribute :check_invalid_attributes, :boolean, default: false
17
+ attribute :allowed_attribute_patterns, :string, collection: true,
18
+ initialize_empty: true
19
19
  attribute :check_parent_child, :boolean, default: false
20
20
  attribute :parent_child_rules, :string, default: -> { {} }
21
21
  attribute :skip_foreign_namespaces, :boolean, default: false
22
- attribute :allowed_namespaces, :string, collection: true, default: -> {
23
- []
24
- }
22
+ attribute :allowed_namespaces, :string, collection: true,
23
+ initialize_empty: true
25
24
  attribute :allow_rdf_metadata, :boolean, default: false
26
25
 
27
26
  # RDF-related namespaces (same as in NamespaceRequirement for consistency)
@@ -41,13 +40,51 @@ module SvgConform
41
40
  map "disallowed_elements", to: :disallowed_elements
42
41
  map "check_attributes", to: :check_attributes
43
42
  map "check_invalid_attributes", to: :check_invalid_attributes
43
+ map "allowed_attribute_patterns", to: :allowed_attribute_patterns
44
44
  map "check_parent_child", to: :check_parent_child
45
45
  map "skip_foreign_namespaces", to: :skip_foreign_namespaces
46
46
  map "allowed_namespaces", to: :allowed_namespaces
47
47
  map "allow_rdf_metadata", to: :allow_rdf_metadata
48
48
  end
49
49
 
50
+ # Check for configuration conflicts and emit warnings
51
+ def validate_configuration
52
+ return if allowed_attribute_patterns.empty? || !element_configs&.any?
53
+
54
+ element_configs.each do |element_config|
55
+ next unless element_config&.attr
56
+
57
+ disallowed_attrs = []
58
+ element_config.attr.each do |attribute|
59
+ if attribute.start_with?("!")
60
+ disallowed_attrs << attribute[1..].downcase
61
+ end
62
+ end
63
+
64
+ next if disallowed_attrs.empty?
65
+
66
+ # Check if any disallowed attribute matches an allowed pattern
67
+ conflicts = disallowed_attrs.select do |disallowed|
68
+ matches_allowed_pattern?(disallowed)
69
+ end
70
+
71
+ if conflicts.any?
72
+ warn "Configuration warning in #{id}: " \
73
+ "Element '#{element_config.tag}' has disallowed attributes [#{conflicts.join(', ')}] " \
74
+ "that match allowed_attribute_patterns [#{allowed_attribute_patterns.join(', ')}]. " \
75
+ "Allowed patterns take precedence over element-specific disallowed attributes."
76
+ end
77
+ end
78
+ end
79
+
50
80
  def check(node, context)
81
+ # Validate configuration once on first use
82
+ @_config_validated ||= false
83
+ unless @_config_validated
84
+ validate_configuration
85
+ @_config_validated = true
86
+ end
87
+
51
88
  return unless element?(node)
52
89
 
53
90
  # Skip foreign namespace elements if configured (let NamespaceRequirement handle them)
@@ -121,6 +158,13 @@ module SvgConform
121
158
  end
122
159
 
123
160
  def validate_sax_element(element, context)
161
+ # Validate configuration once on first use
162
+ @_config_validated ||= false
163
+ unless @_config_validated
164
+ validate_configuration
165
+ @_config_validated = true
166
+ end
167
+
124
168
  # Skip if parent is structurally invalid (matches DOM behavior)
125
169
  if element.parent && context.node_structurally_invalid?(element.parent)
126
170
  # Mark this element as invalid too since it won't be in final document
@@ -202,6 +246,18 @@ module SvgConform
202
246
  disallowed_elements&.include?(element_name) || false
203
247
  end
204
248
 
249
+ # Check if an attribute name matches any of the allowed_attribute_patterns
250
+ def matches_allowed_pattern?(attr_name)
251
+ allowed_attribute_patterns.any? do |pattern|
252
+ if pattern.end_with?("*")
253
+ prefix = pattern[0..-2] # Remove trailing *
254
+ attr_name.downcase.start_with?(prefix)
255
+ else
256
+ attr_name.downcase == pattern
257
+ end
258
+ end
259
+ end
260
+
205
261
  def invalid_parent_child?(parent_name, child_name)
206
262
  return false unless element_configs&.any?
207
263
 
@@ -290,6 +346,9 @@ module SvgConform
290
346
  # Check if matches data-* pattern (wildcard pattern)
291
347
  next if attr_name.start_with?("data-")
292
348
 
349
+ # Check if matches allowed attribute patterns (wildcard patterns)
350
+ next if matches_allowed_pattern?(attr_name)
351
+
293
352
  # Check if explicitly disallowed
294
353
  if disallowed_attrs.include?(attr_name)
295
354
  errors << {
@@ -437,6 +496,9 @@ module SvgConform
437
496
  next if attr.namespace
438
497
  next if attr_name.start_with?("data-")
439
498
 
499
+ # Check if matches allowed attribute patterns (wildcard patterns)
500
+ next if matches_allowed_pattern?(attr_name)
501
+
440
502
  # Check if explicitly disallowed
441
503
  if disallowed_attrs.include?(attr_name)
442
504
  errors << {
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SvgConform
4
- VERSION = "0.1.8"
4
+ VERSION = "0.1.9"
5
5
  end
@@ -114,5 +114,68 @@ RSpec.describe SvgConform::Requirements::AllowedElementsRequirement do
114
114
 
115
115
  expect(requirement).to be_a(described_class)
116
116
  end
117
+
118
+ it "accepts allowed_attribute_patterns for wildcard attribute exemption" do
119
+ requirement = described_class.new(
120
+ id: "test_patterns",
121
+ description: "Test with allowed patterns",
122
+ allowed_attribute_patterns: ["on*", "data-*"],
123
+ )
124
+
125
+ expect(requirement.allowed_attribute_patterns).to eq(["on*", "data-*"])
126
+ end
127
+
128
+ it "allows attributes matching allowed_attribute_patterns" do
129
+ svg_with_event_handlers = <<~SVG
130
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
131
+ <polygon points="10,10 90,10 50,90"
132
+ onmouseout="handleOut()"
133
+ onmouseover="handleOver()"
134
+ data-custom="value"/>
135
+ <rect x="10" y="10" width="20" height="20" onclick="forbidden()"/>
136
+ </svg>
137
+ SVG
138
+
139
+ document = SvgConform::Document.from_content(svg_with_event_handlers)
140
+ requirement = described_class.new(
141
+ id: "test_patterns",
142
+ description: "Test with allowed patterns",
143
+ allowed_attribute_patterns: ["on*"], # Allow all event attributes
144
+ check_attributes: true,
145
+ )
146
+
147
+ context = SvgConform::ValidationContext.new(document, nil)
148
+ requirement.validate_document(document, context)
149
+
150
+ # onmouseout and onmouseover should be allowed (match "on*" pattern)
151
+ # onclick should also be allowed (matches "on*" pattern)
152
+ # data-custom should NOT be allowed (not in allowed attributes, not in pattern)
153
+ expect(context.errors).to be_empty
154
+ end
155
+
156
+ it "warns when allowed_attribute_patterns conflicts with element-specific disallowed attributes" do
157
+ # Create a requirement with conflicting configuration
158
+ requirement = described_class.new(
159
+ id: "test_conflict",
160
+ description: "Test with conflicting patterns",
161
+ allowed_attribute_patterns: ["on*"], # Allows all event attributes
162
+ element_configs: [
163
+ SvgConform::Requirements::ElementRequirementConfig.new(
164
+ tag: "polygon",
165
+ attr: ["points", "!onclick"], # onclick is disallowed on polygon
166
+ ),
167
+ ],
168
+ )
169
+
170
+ # Trigger validation by calling check and capture stderr
171
+ document = SvgConform::Document.from_content(
172
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><polygon points="10,10 90,10 50,90"/></svg>',
173
+ )
174
+ context = SvgConform::ValidationContext.new(document, nil)
175
+
176
+ expect { requirement.check(document.root, context) }
177
+ .to output(/Configuration warning.*onclick.*allowed_attribute_patterns.*Allowed patterns take precedence/m)
178
+ .to_stderr
179
+ end
117
180
  end
118
181
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: svg_conform
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-21 00:00:00.000000000 Z
11
+ date: 2026-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model