svg_conform 0.1.4 → 0.1.5
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 +4 -4
- data/.rubocop_todo.yml +182 -21
- data/README.adoc +391 -989
- data/config/profiles/metanorma.yml +5 -0
- data/docs/api_reference.adoc +1355 -0
- data/docs/cli_guide.adoc +846 -0
- data/docs/reference_manifest.adoc +370 -0
- data/docs/requirements.adoc +68 -1
- data/examples/document_input_demo.rb +102 -0
- data/lib/svg_conform/document.rb +40 -1
- data/lib/svg_conform/profile.rb +15 -9
- data/lib/svg_conform/references/base_reference.rb +130 -0
- data/lib/svg_conform/references/id_definition.rb +38 -0
- data/lib/svg_conform/references/reference_classifier.rb +45 -0
- data/lib/svg_conform/references/reference_manifest.rb +129 -0
- data/lib/svg_conform/references.rb +11 -0
- data/lib/svg_conform/remediations/namespace_attribute_remediation.rb +34 -43
- data/lib/svg_conform/requirements/id_collection_requirement.rb +38 -0
- data/lib/svg_conform/requirements/id_reference_requirement.rb +11 -0
- data/lib/svg_conform/requirements/invalid_id_references_requirement.rb +3 -0
- data/lib/svg_conform/requirements/link_validation_requirement.rb +114 -31
- data/lib/svg_conform/requirements/no_external_css_requirement.rb +5 -2
- data/lib/svg_conform/requirements.rb +11 -9
- data/lib/svg_conform/sax_validation_handler.rb +16 -1
- data/lib/svg_conform/validation_context.rb +67 -1
- data/lib/svg_conform/validation_result.rb +43 -2
- data/lib/svg_conform/validator.rb +56 -16
- data/lib/svg_conform/version.rb +1 -1
- data/lib/svg_conform.rb +11 -2
- data/spec/svg_conform/commands/svgcheck_compare_command_spec.rb +1 -0
- data/spec/svg_conform/commands/svgcheck_compatibility_command_spec.rb +1 -0
- data/spec/svg_conform/commands/svgcheck_generate_command_spec.rb +1 -0
- data/spec/svg_conform/references/integration_spec.rb +206 -0
- data/spec/svg_conform/references/reference_classifier_spec.rb +142 -0
- data/spec/svg_conform/references/reference_manifest_spec.rb +307 -0
- data/spec/svg_conform/requirements/id_reference_state_spec.rb +93 -0
- data/spec/svg_conform/validator_input_types_spec.rb +172 -0
- metadata +17 -2
|
@@ -0,0 +1,1355 @@
|
|
|
1
|
+
= SvgConform Ruby API Reference
|
|
2
|
+
:toc: left
|
|
3
|
+
:toclevels: 4
|
|
4
|
+
:sectlinks:
|
|
5
|
+
:sectanchors:
|
|
6
|
+
:source-highlighter: rouge
|
|
7
|
+
|
|
8
|
+
== Purpose
|
|
9
|
+
|
|
10
|
+
This document provides comprehensive API documentation for the SvgConform Ruby library.
|
|
11
|
+
|
|
12
|
+
The API enables programmatic:
|
|
13
|
+
|
|
14
|
+
* SVG validation against conformance profiles
|
|
15
|
+
* Automatic remediation of common issues
|
|
16
|
+
* Custom profile creation and management
|
|
17
|
+
* Reference manifest generation and analysis
|
|
18
|
+
* Batch processing of SVG files
|
|
19
|
+
|
|
20
|
+
== Quick start
|
|
21
|
+
|
|
22
|
+
[source,ruby]
|
|
23
|
+
----
|
|
24
|
+
require 'svg_conform'
|
|
25
|
+
|
|
26
|
+
# Validate an SVG file
|
|
27
|
+
validator = SvgConform::Validator.new
|
|
28
|
+
result = validator.validate_file('image.svg', profile: :metanorma)
|
|
29
|
+
|
|
30
|
+
if result.valid?
|
|
31
|
+
puts "✓ SVG is valid"
|
|
32
|
+
else
|
|
33
|
+
puts "✗ Found #{result.errors.count} errors"
|
|
34
|
+
result.errors.each { |e| puts " - #{e.message}" }
|
|
35
|
+
end
|
|
36
|
+
----
|
|
37
|
+
|
|
38
|
+
== Core classes
|
|
39
|
+
|
|
40
|
+
=== SvgConform::Validator
|
|
41
|
+
|
|
42
|
+
The main entry point for validation operations.
|
|
43
|
+
|
|
44
|
+
==== Constructor
|
|
45
|
+
|
|
46
|
+
[source,ruby]
|
|
47
|
+
----
|
|
48
|
+
SvgConform::Validator.new(options = {})
|
|
49
|
+
----
|
|
50
|
+
|
|
51
|
+
**Parameters**:
|
|
52
|
+
|
|
53
|
+
* `options` (Hash) - Configuration options
|
|
54
|
+
** `:fix` (Boolean) - Enable automatic fixes (default: `false`)
|
|
55
|
+
** `:strict` (Boolean) - Strict validation mode (default: `false`)
|
|
56
|
+
** `:mode` (Symbol) - Validation mode: `:sax`, `:dom`, or `:auto` (default: `:sax`)
|
|
57
|
+
|
|
58
|
+
**Returns**: `Validator` instance
|
|
59
|
+
|
|
60
|
+
**Example**:
|
|
61
|
+
|
|
62
|
+
[source,ruby]
|
|
63
|
+
----
|
|
64
|
+
validator = SvgConform::Validator.new(mode: :sax)
|
|
65
|
+
----
|
|
66
|
+
|
|
67
|
+
==== Instance methods
|
|
68
|
+
|
|
69
|
+
===== validate(input, profile:, **options)
|
|
70
|
+
|
|
71
|
+
Validate SVG content or document object.
|
|
72
|
+
|
|
73
|
+
**Parameters**:
|
|
74
|
+
|
|
75
|
+
* `input` - SVG input (String, Moxml::Document, Moxml::Element, Nokogiri::XML::Document, Nokogiri::XML::Element)
|
|
76
|
+
* `profile` (Symbol or Profile) - Profile to validate against
|
|
77
|
+
* `**options` - Additional options merged with constructor options
|
|
78
|
+
|
|
79
|
+
**Returns**: `ValidationResult`
|
|
80
|
+
|
|
81
|
+
**Supported input types**:
|
|
82
|
+
|
|
83
|
+
* `String` - Raw XML content
|
|
84
|
+
* `Moxml::Document` or `Moxml::Element` - Moxml DOM objects
|
|
85
|
+
* `Nokogiri::XML::Document` or `Nokogiri::XML::Element` - Nokogiri DOM objects
|
|
86
|
+
* Any object responding to `to_xml`
|
|
87
|
+
|
|
88
|
+
**Example**:
|
|
89
|
+
|
|
90
|
+
[source,ruby]
|
|
91
|
+
----
|
|
92
|
+
# String input
|
|
93
|
+
result = validator.validate(svg_string, profile: :metanorma)
|
|
94
|
+
|
|
95
|
+
# Nokogiri element (common in Metanorma integration)
|
|
96
|
+
nokogiri_element = Nokogiri::XML(svg_string).root
|
|
97
|
+
result = validator.validate(nokogiri_element, profile: :metanorma)
|
|
98
|
+
|
|
99
|
+
# Moxml document
|
|
100
|
+
moxml_doc = Moxml.new.parse(svg_string)
|
|
101
|
+
result = validator.validate(moxml_doc, profile: :metanorma)
|
|
102
|
+
----
|
|
103
|
+
|
|
104
|
+
===== validate_file(file_path, profile:, **options)
|
|
105
|
+
|
|
106
|
+
Validate an SVG file.
|
|
107
|
+
|
|
108
|
+
**Parameters**:
|
|
109
|
+
|
|
110
|
+
* `file_path` (String) - Path to SVG file
|
|
111
|
+
* `profile` (Symbol or Profile) - Profile to validate against
|
|
112
|
+
* `**options` - Additional options
|
|
113
|
+
|
|
114
|
+
**Returns**: `ValidationResult`
|
|
115
|
+
|
|
116
|
+
**Raises**: `ValidationError` if file not found
|
|
117
|
+
|
|
118
|
+
**Example**:
|
|
119
|
+
|
|
120
|
+
[source,ruby]
|
|
121
|
+
----
|
|
122
|
+
result = validator.validate_file('image.svg', profile: :svg_1_2_rfc)
|
|
123
|
+
----
|
|
124
|
+
|
|
125
|
+
===== validate_files(file_paths, profile:, **options)
|
|
126
|
+
|
|
127
|
+
Validate multiple files.
|
|
128
|
+
|
|
129
|
+
**Parameters**:
|
|
130
|
+
|
|
131
|
+
* `file_paths` (Array<String>) - Array of file paths
|
|
132
|
+
* `profile` (Symbol or Profile) - Profile to validate against
|
|
133
|
+
* `**options` - Additional options
|
|
134
|
+
|
|
135
|
+
**Returns**: `Hash<String, ValidationResult>` - Hash mapping file paths to results
|
|
136
|
+
|
|
137
|
+
**Example**:
|
|
138
|
+
|
|
139
|
+
[source,ruby]
|
|
140
|
+
----
|
|
141
|
+
files = ['image1.svg', 'image2.svg', 'image3.svg']
|
|
142
|
+
results = validator.validate_files(files, profile: :metanorma)
|
|
143
|
+
|
|
144
|
+
results.each do |file, result|
|
|
145
|
+
puts "#{file}: #{result.valid? ? '✓' : '✗'}"
|
|
146
|
+
end
|
|
147
|
+
----
|
|
148
|
+
|
|
149
|
+
===== available_profiles
|
|
150
|
+
|
|
151
|
+
Get list of available profiles.
|
|
152
|
+
|
|
153
|
+
**Returns**: `Array<Symbol>` - Profile names
|
|
154
|
+
|
|
155
|
+
**Example**:
|
|
156
|
+
|
|
157
|
+
[source,ruby]
|
|
158
|
+
----
|
|
159
|
+
profiles = validator.available_profiles
|
|
160
|
+
# => [:base, :svg_1_2_rfc, :metanorma, :lucid, ...]
|
|
161
|
+
----
|
|
162
|
+
|
|
163
|
+
=== SvgConform::Profile
|
|
164
|
+
|
|
165
|
+
Represents an SVG validation profile with requirements and remediations.
|
|
166
|
+
|
|
167
|
+
==== Class methods
|
|
168
|
+
|
|
169
|
+
===== load_from_file(file_path)
|
|
170
|
+
|
|
171
|
+
Load a profile from a YAML file.
|
|
172
|
+
|
|
173
|
+
**Parameters**:
|
|
174
|
+
|
|
175
|
+
* `file_path` (String) - Path to profile YAML file
|
|
176
|
+
|
|
177
|
+
**Returns**: `Profile` instance
|
|
178
|
+
|
|
179
|
+
**Example**:
|
|
180
|
+
|
|
181
|
+
[source,ruby]
|
|
182
|
+
----
|
|
183
|
+
profile = SvgConform::Profile.load_from_file('config/profiles/custom.yml')
|
|
184
|
+
----
|
|
185
|
+
|
|
186
|
+
===== save_to_file(profile, file_path)
|
|
187
|
+
|
|
188
|
+
Save a profile to a YAML file.
|
|
189
|
+
|
|
190
|
+
**Parameters**:
|
|
191
|
+
|
|
192
|
+
* `profile` (Profile) - Profile instance to save
|
|
193
|
+
* `file_path` (String) - Output file path
|
|
194
|
+
|
|
195
|
+
**Example**:
|
|
196
|
+
|
|
197
|
+
[source,ruby]
|
|
198
|
+
----
|
|
199
|
+
SvgConform::Profile.save_to_file(profile, 'output.yml')
|
|
200
|
+
----
|
|
201
|
+
|
|
202
|
+
==== Instance methods
|
|
203
|
+
|
|
204
|
+
===== validate(document)
|
|
205
|
+
|
|
206
|
+
Validate a document against this profile.
|
|
207
|
+
|
|
208
|
+
**Parameters**:
|
|
209
|
+
|
|
210
|
+
* `document` - Document to validate (Document, SaxDocument, or object responding to `to_xml`)
|
|
211
|
+
|
|
212
|
+
**Returns**: `ValidationResult`
|
|
213
|
+
|
|
214
|
+
**Example**:
|
|
215
|
+
|
|
216
|
+
[source,ruby]
|
|
217
|
+
----
|
|
218
|
+
profile = SvgConform::Profiles.get(:metanorma)
|
|
219
|
+
document = SvgConform::Document.from_file('image.svg')
|
|
220
|
+
result = profile.validate(document)
|
|
221
|
+
----
|
|
222
|
+
|
|
223
|
+
===== apply_remediations(document)
|
|
224
|
+
|
|
225
|
+
Apply all remediations defined in the profile.
|
|
226
|
+
|
|
227
|
+
**Parameters**:
|
|
228
|
+
|
|
229
|
+
* `document` (Document) - Document to remediate
|
|
230
|
+
|
|
231
|
+
**Returns**: `Array` - Array of changes made
|
|
232
|
+
|
|
233
|
+
**Example**:
|
|
234
|
+
|
|
235
|
+
[source,ruby]
|
|
236
|
+
----
|
|
237
|
+
profile = SvgConform::Profiles.get(:metanorma)
|
|
238
|
+
document = SvgConform::Document.from_file('image.svg')
|
|
239
|
+
|
|
240
|
+
changes = profile.apply_remediations(document)
|
|
241
|
+
puts "Applied #{changes.length} changes"
|
|
242
|
+
|
|
243
|
+
File.write('fixed.svg', document.to_xml)
|
|
244
|
+
----
|
|
245
|
+
|
|
246
|
+
===== add_requirement(requirement)
|
|
247
|
+
|
|
248
|
+
Add a requirement to the profile.
|
|
249
|
+
|
|
250
|
+
**Parameters**:
|
|
251
|
+
|
|
252
|
+
* `requirement` (BaseRequirement) - Requirement instance
|
|
253
|
+
|
|
254
|
+
**Returns**: `self` (for chaining)
|
|
255
|
+
|
|
256
|
+
**Example**:
|
|
257
|
+
|
|
258
|
+
[source,ruby]
|
|
259
|
+
----
|
|
260
|
+
profile = SvgConform::Profile.new
|
|
261
|
+
profile.add_requirement(
|
|
262
|
+
SvgConform::Requirements::ViewboxRequiredRequirement.new
|
|
263
|
+
)
|
|
264
|
+
----
|
|
265
|
+
|
|
266
|
+
===== remove_requirement(requirement_id)
|
|
267
|
+
|
|
268
|
+
Remove a requirement by ID.
|
|
269
|
+
|
|
270
|
+
**Parameters**:
|
|
271
|
+
|
|
272
|
+
* `requirement_id` (String) - Requirement identifier
|
|
273
|
+
|
|
274
|
+
**Returns**: `self` (for chaining)
|
|
275
|
+
|
|
276
|
+
===== has_requirement?(requirement_id)
|
|
277
|
+
|
|
278
|
+
Check if profile has a specific requirement.
|
|
279
|
+
|
|
280
|
+
**Parameters**:
|
|
281
|
+
|
|
282
|
+
* `requirement_id` (String) - Requirement identifier
|
|
283
|
+
|
|
284
|
+
**Returns**: `Boolean`
|
|
285
|
+
|
|
286
|
+
===== add_remediation(remediation)
|
|
287
|
+
|
|
288
|
+
Add a remediation to the profile.
|
|
289
|
+
|
|
290
|
+
**Parameters**:
|
|
291
|
+
|
|
292
|
+
* `remediation` (BaseRemediation) - Remediation instance
|
|
293
|
+
|
|
294
|
+
**Returns**: `self` (for chaining)
|
|
295
|
+
|
|
296
|
+
===== remove_remediation(remediation_id)
|
|
297
|
+
|
|
298
|
+
Remove a remediation by ID.
|
|
299
|
+
|
|
300
|
+
**Parameters**:
|
|
301
|
+
|
|
302
|
+
* `remediation_id` (String) - Remediation identifier
|
|
303
|
+
|
|
304
|
+
**Returns**: `self` (for chaining)
|
|
305
|
+
|
|
306
|
+
===== requirement_count
|
|
307
|
+
|
|
308
|
+
Get number of requirements in the profile.
|
|
309
|
+
|
|
310
|
+
**Returns**: `Integer`
|
|
311
|
+
|
|
312
|
+
===== remediation_count
|
|
313
|
+
|
|
314
|
+
Get number of remediations in the profile.
|
|
315
|
+
|
|
316
|
+
**Returns**: `Integer`
|
|
317
|
+
|
|
318
|
+
==== Attributes
|
|
319
|
+
|
|
320
|
+
* `name` (String) - Profile name
|
|
321
|
+
* `description` (String) - Profile description
|
|
322
|
+
* `requirements` (Array<BaseRequirement>) - Array of requirements
|
|
323
|
+
* `remediations` (Array<BaseRemediation>) - Array of remediations
|
|
324
|
+
|
|
325
|
+
=== SvgConform::ValidationResult
|
|
326
|
+
|
|
327
|
+
Result object returned from validation operations.
|
|
328
|
+
|
|
329
|
+
==== Instance methods
|
|
330
|
+
|
|
331
|
+
===== valid?
|
|
332
|
+
|
|
333
|
+
Check if document is valid.
|
|
334
|
+
|
|
335
|
+
**Returns**: `Boolean`
|
|
336
|
+
|
|
337
|
+
===== has_errors?
|
|
338
|
+
|
|
339
|
+
Check if errors were found.
|
|
340
|
+
|
|
341
|
+
**Returns**: `Boolean`
|
|
342
|
+
|
|
343
|
+
===== has_warnings?
|
|
344
|
+
|
|
345
|
+
Check if warnings were found.
|
|
346
|
+
|
|
347
|
+
**Returns**: `Boolean`
|
|
348
|
+
|
|
349
|
+
===== fixable?
|
|
350
|
+
|
|
351
|
+
Check if issues can be automatically fixed.
|
|
352
|
+
|
|
353
|
+
**Returns**: `Boolean`
|
|
354
|
+
|
|
355
|
+
===== error_count
|
|
356
|
+
|
|
357
|
+
Get number of errors.
|
|
358
|
+
|
|
359
|
+
**Returns**: `Integer`
|
|
360
|
+
|
|
361
|
+
===== warning_count
|
|
362
|
+
|
|
363
|
+
Get number of warnings.
|
|
364
|
+
|
|
365
|
+
**Returns**: `Integer`
|
|
366
|
+
|
|
367
|
+
===== failed_requirements
|
|
368
|
+
|
|
369
|
+
Get requirements that failed validation.
|
|
370
|
+
|
|
371
|
+
**Returns**: `Array` - Array of failed requirement errors
|
|
372
|
+
|
|
373
|
+
===== reference_manifest
|
|
374
|
+
|
|
375
|
+
Get the reference manifest.
|
|
376
|
+
|
|
377
|
+
**Returns**: `ReferenceManifest` instance
|
|
378
|
+
|
|
379
|
+
===== available_ids
|
|
380
|
+
|
|
381
|
+
Convenience accessor for all defined IDs.
|
|
382
|
+
|
|
383
|
+
**Returns**: `Array<IdDefinition>`
|
|
384
|
+
|
|
385
|
+
===== internal_references
|
|
386
|
+
|
|
387
|
+
Get all internal references (fragment identifiers).
|
|
388
|
+
|
|
389
|
+
**Returns**: `Array<BaseReference>`
|
|
390
|
+
|
|
391
|
+
===== external_references
|
|
392
|
+
|
|
393
|
+
Get all external references.
|
|
394
|
+
|
|
395
|
+
**Returns**: `Array<BaseReference>`
|
|
396
|
+
|
|
397
|
+
===== has_external_references?
|
|
398
|
+
|
|
399
|
+
Check if document has external references.
|
|
400
|
+
|
|
401
|
+
**Returns**: `Boolean`
|
|
402
|
+
|
|
403
|
+
===== unresolved_internal_references
|
|
404
|
+
|
|
405
|
+
Get internal references that don't resolve to defined IDs.
|
|
406
|
+
|
|
407
|
+
**Returns**: `Array<BaseReference>`
|
|
408
|
+
|
|
409
|
+
===== to_h
|
|
410
|
+
|
|
411
|
+
Convert result to hash.
|
|
412
|
+
|
|
413
|
+
**Returns**: `Hash`
|
|
414
|
+
|
|
415
|
+
===== to_json
|
|
416
|
+
|
|
417
|
+
Convert result to JSON string.
|
|
418
|
+
|
|
419
|
+
**Returns**: `String` - JSON representation
|
|
420
|
+
|
|
421
|
+
==== Attributes
|
|
422
|
+
|
|
423
|
+
* `document` - The validated document
|
|
424
|
+
* `profile` - The profile used
|
|
425
|
+
* `errors` (Array) - Validation errors
|
|
426
|
+
* `warnings` (Array) - Validation warnings
|
|
427
|
+
* `reference_manifest` (ReferenceManifest) - ID and reference tracking
|
|
428
|
+
|
|
429
|
+
==== Example
|
|
430
|
+
|
|
431
|
+
[source,ruby]
|
|
432
|
+
----
|
|
433
|
+
result = validator.validate(svg_content, profile: :metanorma)
|
|
434
|
+
|
|
435
|
+
puts "Valid: #{result.valid?}"
|
|
436
|
+
puts "Errors: #{result.error_count}"
|
|
437
|
+
puts "IDs: #{result.available_ids.map(&:id_value).join(', ')}"
|
|
438
|
+
|
|
439
|
+
if result.has_unresolved_references?
|
|
440
|
+
puts "Unresolved references:"
|
|
441
|
+
result.unresolved_internal_references.each do |ref|
|
|
442
|
+
puts " - #{ref.value} at line #{ref.line_number}"
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
----
|
|
446
|
+
|
|
447
|
+
=== SvgConform::Document
|
|
448
|
+
|
|
449
|
+
DOM wrapper for SVG documents using Moxml.
|
|
450
|
+
|
|
451
|
+
==== Class methods
|
|
452
|
+
|
|
453
|
+
===== new(content)
|
|
454
|
+
|
|
455
|
+
Create document from XML string.
|
|
456
|
+
|
|
457
|
+
**Parameters**:
|
|
458
|
+
|
|
459
|
+
* `content` (String) - XML content
|
|
460
|
+
|
|
461
|
+
**Returns**: `Document` instance
|
|
462
|
+
|
|
463
|
+
**Example**:
|
|
464
|
+
|
|
465
|
+
[source,ruby]
|
|
466
|
+
----
|
|
467
|
+
document = SvgConform::Document.new(svg_string)
|
|
468
|
+
----
|
|
469
|
+
|
|
470
|
+
===== from_file(file_path)
|
|
471
|
+
|
|
472
|
+
Create document from file.
|
|
473
|
+
|
|
474
|
+
**Parameters**:
|
|
475
|
+
|
|
476
|
+
* `file_path` (String) - Path to SVG file
|
|
477
|
+
|
|
478
|
+
**Returns**: `Document` instance
|
|
479
|
+
|
|
480
|
+
**Example**:
|
|
481
|
+
|
|
482
|
+
[source,ruby]
|
|
483
|
+
----
|
|
484
|
+
document = SvgConform::Document.from_file('image.svg')
|
|
485
|
+
----
|
|
486
|
+
|
|
487
|
+
===== from_content(content)
|
|
488
|
+
|
|
489
|
+
Alias for `new(content)`.
|
|
490
|
+
|
|
491
|
+
===== from_node(node)
|
|
492
|
+
|
|
493
|
+
Create document from a DOM node (Nokogiri or Moxml).
|
|
494
|
+
|
|
495
|
+
**Parameters**:
|
|
496
|
+
|
|
497
|
+
* `node` - DOM node (Nokogiri::XML::Node or Moxml element)
|
|
498
|
+
|
|
499
|
+
**Returns**: `Document` instance
|
|
500
|
+
|
|
501
|
+
**Example**:
|
|
502
|
+
|
|
503
|
+
[source,ruby]
|
|
504
|
+
----
|
|
505
|
+
nokogiri_element = Nokogiri::XML(svg_string).root
|
|
506
|
+
document = SvgConform::Document.from_node(nokogiri_element)
|
|
507
|
+
----
|
|
508
|
+
|
|
509
|
+
==== Instance methods
|
|
510
|
+
|
|
511
|
+
===== root
|
|
512
|
+
|
|
513
|
+
Get the root element.
|
|
514
|
+
|
|
515
|
+
**Returns**: Moxml element
|
|
516
|
+
|
|
517
|
+
===== to_xml
|
|
518
|
+
|
|
519
|
+
Serialize document to XML string.
|
|
520
|
+
|
|
521
|
+
**Returns**: `String` - XML content
|
|
522
|
+
|
|
523
|
+
===== find(xpath)
|
|
524
|
+
|
|
525
|
+
Find first element matching XPath.
|
|
526
|
+
|
|
527
|
+
**Parameters**:
|
|
528
|
+
|
|
529
|
+
* `xpath` (String) - XPath expression
|
|
530
|
+
|
|
531
|
+
**Returns**: Moxml element or `nil`
|
|
532
|
+
|
|
533
|
+
===== find_all(xpath)
|
|
534
|
+
|
|
535
|
+
Find all elements matching XPath.
|
|
536
|
+
|
|
537
|
+
**Parameters**:
|
|
538
|
+
|
|
539
|
+
* `xpath` (String) - XPath expression
|
|
540
|
+
|
|
541
|
+
**Returns**: `Array` - Array of Moxml elements
|
|
542
|
+
|
|
543
|
+
==== Example
|
|
544
|
+
|
|
545
|
+
[source,ruby]
|
|
546
|
+
----
|
|
547
|
+
document = SvgConform::Document.from_file('image.svg')
|
|
548
|
+
|
|
549
|
+
# Access root element
|
|
550
|
+
root = document.root
|
|
551
|
+
puts "Root: #{root.name}"
|
|
552
|
+
|
|
553
|
+
# Find elements
|
|
554
|
+
rects = document.find_all('//svg:rect')
|
|
555
|
+
puts "Found #{rects.size} rectangles"
|
|
556
|
+
|
|
557
|
+
# Modify and serialize
|
|
558
|
+
root.set_attribute('width', '500')
|
|
559
|
+
File.write('modified.svg', document.to_xml)
|
|
560
|
+
----
|
|
561
|
+
|
|
562
|
+
=== SvgConform::RemediationRunner
|
|
563
|
+
|
|
564
|
+
Runner for applying remediations to SVG files.
|
|
565
|
+
|
|
566
|
+
==== Constructor
|
|
567
|
+
|
|
568
|
+
[source,ruby]
|
|
569
|
+
----
|
|
570
|
+
SvgConform::RemediationRunner.new(profile:, options: {})
|
|
571
|
+
----
|
|
572
|
+
|
|
573
|
+
**Parameters**:
|
|
574
|
+
|
|
575
|
+
* `profile` (Symbol or Profile) - Profile to use
|
|
576
|
+
* `options` (Hash) - Options
|
|
577
|
+
** `:verbose` (Boolean) - Verbose output (default: `false`)
|
|
578
|
+
** `:strict` (Boolean) - Strict mode (default: `false`)
|
|
579
|
+
|
|
580
|
+
**Returns**: `RemediationRunner` instance
|
|
581
|
+
|
|
582
|
+
==== Instance methods
|
|
583
|
+
|
|
584
|
+
===== run_remediation(svg_content, filename: nil)
|
|
585
|
+
|
|
586
|
+
Run remediation on SVG content.
|
|
587
|
+
|
|
588
|
+
**Parameters**:
|
|
589
|
+
|
|
590
|
+
* `svg_content` (String) - SVG XML content
|
|
591
|
+
* `filename` (String) - Optional filename for reporting
|
|
592
|
+
|
|
593
|
+
**Returns**: `RemediationRunResult`
|
|
594
|
+
|
|
595
|
+
===== run_remediation_file(file_path)
|
|
596
|
+
|
|
597
|
+
Run remediation on a file.
|
|
598
|
+
|
|
599
|
+
**Parameters**:
|
|
600
|
+
|
|
601
|
+
* `file_path` (String) - Path to SVG file
|
|
602
|
+
|
|
603
|
+
**Returns**: `RemediationRunResult`
|
|
604
|
+
|
|
605
|
+
**Raises**: Error if file not found
|
|
606
|
+
|
|
607
|
+
===== run_remediation_batch(file_paths)
|
|
608
|
+
|
|
609
|
+
Run remediation on multiple files.
|
|
610
|
+
|
|
611
|
+
**Parameters**:
|
|
612
|
+
|
|
613
|
+
* `file_paths` (Array<String>) - Array of file paths
|
|
614
|
+
|
|
615
|
+
**Returns**: `Hash<String, RemediationRunResult>` - Results by file path
|
|
616
|
+
|
|
617
|
+
===== has_remediations?
|
|
618
|
+
|
|
619
|
+
Check if profile has remediations.
|
|
620
|
+
|
|
621
|
+
**Returns**: `Boolean`
|
|
622
|
+
|
|
623
|
+
===== available_remediations
|
|
624
|
+
|
|
625
|
+
Get remediations from the profile.
|
|
626
|
+
|
|
627
|
+
**Returns**: `Array<BaseRemediation>`
|
|
628
|
+
|
|
629
|
+
==== Example
|
|
630
|
+
|
|
631
|
+
[source,ruby]
|
|
632
|
+
----
|
|
633
|
+
profile = SvgConform::Profiles.get(:metanorma)
|
|
634
|
+
runner = SvgConform::RemediationRunner.new(profile: profile)
|
|
635
|
+
|
|
636
|
+
result = runner.run_remediation_file('input.svg')
|
|
637
|
+
|
|
638
|
+
if result.success?
|
|
639
|
+
File.write('output.svg', result.remediated_content)
|
|
640
|
+
puts "✓ Fixed #{result.issues_fixed} issues"
|
|
641
|
+
else
|
|
642
|
+
puts "✗ Remediation failed: #{result.error.message}"
|
|
643
|
+
end
|
|
644
|
+
----
|
|
645
|
+
|
|
646
|
+
=== SvgConform::RemediationRunResult
|
|
647
|
+
|
|
648
|
+
Result of remediation operation.
|
|
649
|
+
|
|
650
|
+
==== Instance methods
|
|
651
|
+
|
|
652
|
+
===== success?
|
|
653
|
+
|
|
654
|
+
Check if remediation was successful (all issues fixed).
|
|
655
|
+
|
|
656
|
+
**Returns**: `Boolean`
|
|
657
|
+
|
|
658
|
+
===== error?
|
|
659
|
+
|
|
660
|
+
Check if an error occurred.
|
|
661
|
+
|
|
662
|
+
**Returns**: `Boolean`
|
|
663
|
+
|
|
664
|
+
===== issues_fixed
|
|
665
|
+
|
|
666
|
+
Get number of issues fixed.
|
|
667
|
+
|
|
668
|
+
**Returns**: `Integer`
|
|
669
|
+
|
|
670
|
+
===== remediations_applied
|
|
671
|
+
|
|
672
|
+
Get number of remediations applied.
|
|
673
|
+
|
|
674
|
+
**Returns**: `Integer`
|
|
675
|
+
|
|
676
|
+
===== content_modified?
|
|
677
|
+
|
|
678
|
+
Check if content was modified.
|
|
679
|
+
|
|
680
|
+
**Returns**: `Boolean`
|
|
681
|
+
|
|
682
|
+
===== changes_summary
|
|
683
|
+
|
|
684
|
+
Get summary of changes.
|
|
685
|
+
|
|
686
|
+
**Returns**: `String`
|
|
687
|
+
|
|
688
|
+
===== to_h
|
|
689
|
+
|
|
690
|
+
Convert to hash.
|
|
691
|
+
|
|
692
|
+
**Returns**: `Hash`
|
|
693
|
+
|
|
694
|
+
==== Attributes
|
|
695
|
+
|
|
696
|
+
* `filename` (String) - Source filename
|
|
697
|
+
* `original_content` (String) - Original SVG content
|
|
698
|
+
* `remediated_content` (String) - Fixed SVG content
|
|
699
|
+
* `initial_validation` (ValidationResult) - Validation before fixes
|
|
700
|
+
* `final_validation` (ValidationResult) - Validation after fixes
|
|
701
|
+
* `error` - Error if occurred
|
|
702
|
+
|
|
703
|
+
== Profile management
|
|
704
|
+
|
|
705
|
+
=== SvgConform::Profiles
|
|
706
|
+
|
|
707
|
+
Module for managing built-in profiles.
|
|
708
|
+
|
|
709
|
+
==== Module methods
|
|
710
|
+
|
|
711
|
+
===== get(profile_name)
|
|
712
|
+
|
|
713
|
+
Get a profile by name.
|
|
714
|
+
|
|
715
|
+
**Parameters**:
|
|
716
|
+
|
|
717
|
+
* `profile_name` (Symbol or String) - Profile identifier
|
|
718
|
+
|
|
719
|
+
**Returns**: `Profile` instance or `nil`
|
|
720
|
+
|
|
721
|
+
**Example**:
|
|
722
|
+
|
|
723
|
+
[source,ruby]
|
|
724
|
+
----
|
|
725
|
+
profile = SvgConform::Profiles.get(:metanorma)
|
|
726
|
+
profile = SvgConform::Profiles.get('svg_1_2_rfc')
|
|
727
|
+
----
|
|
728
|
+
|
|
729
|
+
===== available_profiles
|
|
730
|
+
|
|
731
|
+
Get list of available profile names.
|
|
732
|
+
|
|
733
|
+
**Returns**: `Array<Symbol>`
|
|
734
|
+
|
|
735
|
+
**Example**:
|
|
736
|
+
|
|
737
|
+
[source,ruby]
|
|
738
|
+
----
|
|
739
|
+
profiles = SvgConform::Profiles.available_profiles
|
|
740
|
+
# => [:base, :svg_1_2_rfc, :metanorma, ...]
|
|
741
|
+
|
|
742
|
+
profiles.each do |name|
|
|
743
|
+
profile = SvgConform::Profiles.get(name)
|
|
744
|
+
puts "#{name}: #{profile.description}"
|
|
745
|
+
end
|
|
746
|
+
----
|
|
747
|
+
|
|
748
|
+
=== Built-in profiles
|
|
749
|
+
|
|
750
|
+
[cols="1,3"]
|
|
751
|
+
|===
|
|
752
|
+
| Profile | Description
|
|
753
|
+
|
|
754
|
+
| `:base`
|
|
755
|
+
| Minimal SVG validation with basic structural checks
|
|
756
|
+
|
|
757
|
+
| `:svg_1_2_rfc`
|
|
758
|
+
| RFC 7996 compliance for IETF XMLRFC documents (strict)
|
|
759
|
+
|
|
760
|
+
| `:svg_1_2_rfc_with_rdf`
|
|
761
|
+
| RFC 7996 with RDF/Dublin Core metadata support
|
|
762
|
+
|
|
763
|
+
| `:metanorma`
|
|
764
|
+
| Metanorma document generation requirements
|
|
765
|
+
|
|
766
|
+
| `:lucid`
|
|
767
|
+
| Lucid chart export compatibility
|
|
768
|
+
|
|
769
|
+
| `:no_external_css`
|
|
770
|
+
| Disallow external CSS, require inline styles
|
|
771
|
+
|===
|
|
772
|
+
|
|
773
|
+
== Requirements
|
|
774
|
+
|
|
775
|
+
All requirement classes inherit from `SvgConform::Requirements::BaseRequirement`.
|
|
776
|
+
|
|
777
|
+
=== Common requirement methods
|
|
778
|
+
|
|
779
|
+
All requirements support:
|
|
780
|
+
|
|
781
|
+
* `validate_document(document, context)` - Validate a document
|
|
782
|
+
* `id` - Get requirement ID
|
|
783
|
+
* `description` - Get requirement description
|
|
784
|
+
|
|
785
|
+
=== Available requirements
|
|
786
|
+
|
|
787
|
+
==== ViewboxRequiredRequirement
|
|
788
|
+
|
|
789
|
+
Ensures SVG has a `viewBox` attribute.
|
|
790
|
+
|
|
791
|
+
[source,ruby]
|
|
792
|
+
----
|
|
793
|
+
requirement = SvgConform::Requirements::ViewboxRequiredRequirement.new
|
|
794
|
+
----
|
|
795
|
+
|
|
796
|
+
==== AllowedElementsRequirement
|
|
797
|
+
|
|
798
|
+
Validates elements against whitelist.
|
|
799
|
+
|
|
800
|
+
**Configuration**:
|
|
801
|
+
|
|
802
|
+
* `allowed_elements` (Array<String>) - Allowed element names
|
|
803
|
+
|
|
804
|
+
[source,ruby]
|
|
805
|
+
----
|
|
806
|
+
requirement = SvgConform::Requirements::AllowedElementsRequirement.new(
|
|
807
|
+
config: {
|
|
808
|
+
'allowed_elements' => ['svg', 'rect', 'circle', 'path']
|
|
809
|
+
}
|
|
810
|
+
)
|
|
811
|
+
----
|
|
812
|
+
|
|
813
|
+
==== ColorRestrictionsRequirement
|
|
814
|
+
|
|
815
|
+
Enforces color format restrictions.
|
|
816
|
+
|
|
817
|
+
**Configuration**:
|
|
818
|
+
|
|
819
|
+
* `allowed_formats` (Array<String>) - e.g., `['hex', 'rgb']`
|
|
820
|
+
|
|
821
|
+
==== FontFamilyRequirement
|
|
822
|
+
|
|
823
|
+
Restricts font families.
|
|
824
|
+
|
|
825
|
+
**Configuration**:
|
|
826
|
+
|
|
827
|
+
* `allowed_families` (Array<String>) - Allowed font names
|
|
828
|
+
|
|
829
|
+
[source,ruby]
|
|
830
|
+
----
|
|
831
|
+
requirement = SvgConform::Requirements::FontFamilyRequirement.new(
|
|
832
|
+
config: {
|
|
833
|
+
'allowed_families' => ['Arial', 'Helvetica', 'sans-serif']
|
|
834
|
+
}
|
|
835
|
+
)
|
|
836
|
+
----
|
|
837
|
+
|
|
838
|
+
==== NoExternalImagesRequirement
|
|
839
|
+
|
|
840
|
+
Disallows external image references.
|
|
841
|
+
|
|
842
|
+
==== NoExternalFontsRequirement
|
|
843
|
+
|
|
844
|
+
Disallows external font references.
|
|
845
|
+
|
|
846
|
+
==== NoExternalCssRequirement
|
|
847
|
+
|
|
848
|
+
Disallows external CSS stylesheets.
|
|
849
|
+
|
|
850
|
+
==== NamespaceRequirement
|
|
851
|
+
|
|
852
|
+
Validates namespace declarations.
|
|
853
|
+
|
|
854
|
+
**Configuration**:
|
|
855
|
+
|
|
856
|
+
* `allowed_namespaces` (Array<String>) - Allowed namespace URIs
|
|
857
|
+
|
|
858
|
+
==== IdReferenceRequirement
|
|
859
|
+
|
|
860
|
+
Validates that all ID references resolve.
|
|
861
|
+
|
|
862
|
+
==== InvalidIdReferencesRequirement
|
|
863
|
+
|
|
864
|
+
Detects invalid or malformed ID references.
|
|
865
|
+
|
|
866
|
+
==== LinkValidationRequirement
|
|
867
|
+
|
|
868
|
+
Validates hyperlink targets.
|
|
869
|
+
|
|
870
|
+
==== StyleRequirement
|
|
871
|
+
|
|
872
|
+
Validates style attributes and values.
|
|
873
|
+
|
|
874
|
+
==== StylePromotionRequirement
|
|
875
|
+
|
|
876
|
+
Checks for presentational attributes that should be styles.
|
|
877
|
+
|
|
878
|
+
==== ForbiddenContentRequirement
|
|
879
|
+
|
|
880
|
+
Detects forbidden elements or attributes.
|
|
881
|
+
|
|
882
|
+
**Configuration**:
|
|
883
|
+
|
|
884
|
+
* `forbidden_elements` (Array<String>) - Forbidden element names
|
|
885
|
+
* `forbidden_attributes` (Array<String>) - Forbidden attribute names
|
|
886
|
+
|
|
887
|
+
== Remediations
|
|
888
|
+
|
|
889
|
+
All remediation classes inherit from `SvgConform::Remediations::BaseRemediation`.
|
|
890
|
+
|
|
891
|
+
=== Common remediation methods
|
|
892
|
+
|
|
893
|
+
All remediations support:
|
|
894
|
+
|
|
895
|
+
* `apply(document, context)` - Apply remediation
|
|
896
|
+
* `should_execute?(failed_requirements)` - Check if should run
|
|
897
|
+
* `id` - Get remediation ID
|
|
898
|
+
|
|
899
|
+
=== Available remediations
|
|
900
|
+
|
|
901
|
+
==== ViewboxRemediation
|
|
902
|
+
|
|
903
|
+
Adds missing `viewBox` attribute (calculated from dimensions).
|
|
904
|
+
|
|
905
|
+
==== ColorRemediation
|
|
906
|
+
|
|
907
|
+
Converts colors to allowed formats.
|
|
908
|
+
|
|
909
|
+
==== FontRemediation
|
|
910
|
+
|
|
911
|
+
Replaces disallowed fonts with alternatives.
|
|
912
|
+
|
|
913
|
+
==== FontEmbeddingRemediation
|
|
914
|
+
|
|
915
|
+
Embeds external fonts as data URIs or inline.
|
|
916
|
+
|
|
917
|
+
==== ImageEmbeddingRemediation
|
|
918
|
+
|
|
919
|
+
Embeds external images as data URIs.
|
|
920
|
+
|
|
921
|
+
==== NoExternalCssRemediation
|
|
922
|
+
|
|
923
|
+
Inlines external CSS stylesheets.
|
|
924
|
+
|
|
925
|
+
==== NamespaceRemediation
|
|
926
|
+
|
|
927
|
+
Fixes namespace declarations.
|
|
928
|
+
|
|
929
|
+
==== NamespaceAttributeRemediation
|
|
930
|
+
|
|
931
|
+
Removes unwanted namespace attributes.
|
|
932
|
+
|
|
933
|
+
==== InvalidIdReferencesRemediation
|
|
934
|
+
|
|
935
|
+
Fixes or removes invalid ID references.
|
|
936
|
+
|
|
937
|
+
==== StylePromotionRemediation
|
|
938
|
+
|
|
939
|
+
Converts presentation attributes to inline styles.
|
|
940
|
+
|
|
941
|
+
== Reference tracking
|
|
942
|
+
|
|
943
|
+
=== SvgConform::References::ReferenceManifest
|
|
944
|
+
|
|
945
|
+
Tracks all IDs and references in an SVG document.
|
|
946
|
+
|
|
947
|
+
==== Instance methods
|
|
948
|
+
|
|
949
|
+
===== available_ids
|
|
950
|
+
|
|
951
|
+
Get all defined IDs.
|
|
952
|
+
|
|
953
|
+
**Returns**: `Array<IdDefinition>`
|
|
954
|
+
|
|
955
|
+
===== internal_references
|
|
956
|
+
|
|
957
|
+
Get all internal references (fragments).
|
|
958
|
+
|
|
959
|
+
**Returns**: `Array<BaseReference>`
|
|
960
|
+
|
|
961
|
+
===== external_references
|
|
962
|
+
|
|
963
|
+
Get all external references.
|
|
964
|
+
|
|
965
|
+
**Returns**: `Array<BaseReference>`
|
|
966
|
+
|
|
967
|
+
===== unresolved_internal_references
|
|
968
|
+
|
|
969
|
+
Get references that don't resolve to defined IDs.
|
|
970
|
+
|
|
971
|
+
**Returns**: `Array<BaseReference>`
|
|
972
|
+
|
|
973
|
+
===== statistics
|
|
974
|
+
|
|
975
|
+
Get reference statistics.
|
|
976
|
+
|
|
977
|
+
**Returns**: `Hash` with counts
|
|
978
|
+
|
|
979
|
+
===== to_h
|
|
980
|
+
|
|
981
|
+
Convert to hash.
|
|
982
|
+
|
|
983
|
+
**Returns**: `Hash`
|
|
984
|
+
|
|
985
|
+
==== Example
|
|
986
|
+
|
|
987
|
+
[source,ruby]
|
|
988
|
+
----
|
|
989
|
+
result = validator.validate(svg_content, profile: :metanorma)
|
|
990
|
+
manifest = result.reference_manifest
|
|
991
|
+
|
|
992
|
+
puts "IDs defined: #{manifest.available_ids.size}"
|
|
993
|
+
manifest.available_ids.each do |id|
|
|
994
|
+
puts " - #{id.id_value} (#{id.element_name})"
|
|
995
|
+
end
|
|
996
|
+
|
|
997
|
+
puts "Internal refs: #{manifest.internal_references.size}"
|
|
998
|
+
puts "External refs: #{manifest.external_references.size}"
|
|
999
|
+
|
|
1000
|
+
if manifest.unresolved_internal_references.any?
|
|
1001
|
+
puts "Unresolved:"
|
|
1002
|
+
manifest.unresolved_internal_references.each do |ref|
|
|
1003
|
+
puts " - #{ref.value} at line #{ref.line_number}"
|
|
1004
|
+
end
|
|
1005
|
+
end
|
|
1006
|
+
----
|
|
1007
|
+
|
|
1008
|
+
=== SvgConform::References::IdDefinition
|
|
1009
|
+
|
|
1010
|
+
Represents an ID definition.
|
|
1011
|
+
|
|
1012
|
+
==== Attributes
|
|
1013
|
+
|
|
1014
|
+
* `id_value` (String) - The ID value
|
|
1015
|
+
* `element_name` (String) - Element containing the ID
|
|
1016
|
+
* `line_number` (Integer) - Line number in source
|
|
1017
|
+
* `namespace` (String) - Element namespace
|
|
1018
|
+
|
|
1019
|
+
=== SvgConform::References::BaseReference
|
|
1020
|
+
|
|
1021
|
+
Represents a reference to an ID.
|
|
1022
|
+
|
|
1023
|
+
==== Attributes
|
|
1024
|
+
|
|
1025
|
+
* `value` (String) - Reference value (may include fragment)
|
|
1026
|
+
* `reference_type` (String) - Type: 'url', 'href', 'fill', 'fragment'
|
|
1027
|
+
* `line_number` (Integer) - Line number in source
|
|
1028
|
+
* `element_name` (String) - Element containing reference
|
|
1029
|
+
* `attribute_name` (String) - Attribute name
|
|
1030
|
+
|
|
1031
|
+
==== Methods
|
|
1032
|
+
|
|
1033
|
+
* `external?` - Check if reference is external
|
|
1034
|
+
* `internal?` - Check if reference is internal (fragment)
|
|
1035
|
+
* `fragment` - Get fragment identifier (without `#`)
|
|
1036
|
+
|
|
1037
|
+
== Advanced usage
|
|
1038
|
+
|
|
1039
|
+
=== Custom profile creation
|
|
1040
|
+
|
|
1041
|
+
==== From scratch
|
|
1042
|
+
|
|
1043
|
+
[source,ruby]
|
|
1044
|
+
----
|
|
1045
|
+
profile = SvgConform::Profile.new
|
|
1046
|
+
profile.name = 'custom'
|
|
1047
|
+
profile.description = 'Custom validation profile'
|
|
1048
|
+
|
|
1049
|
+
# Add requirements
|
|
1050
|
+
profile.add_requirement(
|
|
1051
|
+
SvgConform::Requirements::ViewboxRequiredRequirement.new
|
|
1052
|
+
)
|
|
1053
|
+
profile.add_requirement(
|
|
1054
|
+
SvgConform::Requirements::NoExternalImagesRequirement.new
|
|
1055
|
+
)
|
|
1056
|
+
|
|
1057
|
+
# Add remediations
|
|
1058
|
+
profile.add_remediation(
|
|
1059
|
+
SvgConform::Remediations::ViewboxRemediation.new
|
|
1060
|
+
)
|
|
1061
|
+
profile.add_remediation(
|
|
1062
|
+
SvgConform::Remediations::ImageEmbeddingRemediation.new
|
|
1063
|
+
)
|
|
1064
|
+
|
|
1065
|
+
# Save to file
|
|
1066
|
+
SvgConform::Profile.save_to_file(profile, 'custom-profile.yml')
|
|
1067
|
+
----
|
|
1068
|
+
|
|
1069
|
+
==== From YAML
|
|
1070
|
+
|
|
1071
|
+
[source,yaml]
|
|
1072
|
+
----
|
|
1073
|
+
# custom-profile.yml
|
|
1074
|
+
name: custom
|
|
1075
|
+
description: Custom validation profile
|
|
1076
|
+
|
|
1077
|
+
requirements:
|
|
1078
|
+
- type: viewbox_required
|
|
1079
|
+
- type: no_external_images
|
|
1080
|
+
- type: font_family
|
|
1081
|
+
config:
|
|
1082
|
+
allowed_families:
|
|
1083
|
+
- Arial
|
|
1084
|
+
- Helvetica
|
|
1085
|
+
|
|
1086
|
+
remediations:
|
|
1087
|
+
- type: viewbox
|
|
1088
|
+
- type: image_embedding
|
|
1089
|
+
----
|
|
1090
|
+
|
|
1091
|
+
[source,ruby]
|
|
1092
|
+
----
|
|
1093
|
+
profile = SvgConform::Profile.load_from_file('custom-profile.yml')
|
|
1094
|
+
----
|
|
1095
|
+
|
|
1096
|
+
=== Batch processing pattern
|
|
1097
|
+
|
|
1098
|
+
[source,ruby]
|
|
1099
|
+
----
|
|
1100
|
+
validator = SvgConform::Validator.new
|
|
1101
|
+
results = {}
|
|
1102
|
+
errors = []
|
|
1103
|
+
|
|
1104
|
+
Dir.glob('images/**/*.svg') do |file|
|
|
1105
|
+
begin
|
|
1106
|
+
result = validator.validate_file(file, profile: :metanorma)
|
|
1107
|
+
results[file] = result
|
|
1108
|
+
|
|
1109
|
+
unless result.valid?
|
|
1110
|
+
errors << { file: file, errors: result.errors }
|
|
1111
|
+
end
|
|
1112
|
+
rescue => e
|
|
1113
|
+
errors << { file: file, error: e.message }
|
|
1114
|
+
end
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
# Summary
|
|
1118
|
+
valid_count = results.count { |_, r| r.valid? }
|
|
1119
|
+
puts "#{valid_count}/#{results.size} files valid"
|
|
1120
|
+
|
|
1121
|
+
# Detailed errors
|
|
1122
|
+
if errors.any?
|
|
1123
|
+
puts "\nFiles with errors:"
|
|
1124
|
+
errors.each do |item|
|
|
1125
|
+
puts "#{item[:file]}:"
|
|
1126
|
+
if item[:errors]
|
|
1127
|
+
item[:errors].each { |e| puts " - #{e.message}" }
|
|
1128
|
+
else
|
|
1129
|
+
puts " - #{item[:error]}"
|
|
1130
|
+
end
|
|
1131
|
+
end
|
|
1132
|
+
end
|
|
1133
|
+
----
|
|
1134
|
+
|
|
1135
|
+
=== Remediation workflow
|
|
1136
|
+
|
|
1137
|
+
[source,ruby]
|
|
1138
|
+
----
|
|
1139
|
+
profile = SvgConform::Profiles.get(:metanorma)
|
|
1140
|
+
runner = SvgConform::RemediationRunner.new(profile: profile)
|
|
1141
|
+
|
|
1142
|
+
Dir.glob('input/**/*.svg').each do |input_file|
|
|
1143
|
+
output_file = input_file.sub('input/', 'output/')
|
|
1144
|
+
|
|
1145
|
+
# Ensure output directory exists
|
|
1146
|
+
FileUtils.mkdir_p(File.dirname(output_file))
|
|
1147
|
+
|
|
1148
|
+
# Run remediation
|
|
1149
|
+
result = runner.run_remediation_file(input_file)
|
|
1150
|
+
|
|
1151
|
+
if result.success?
|
|
1152
|
+
File.write(output_file, result.remediated_content)
|
|
1153
|
+
puts "✓ #{input_file} → #{output_file} (#{result.issues_fixed} fixed)"
|
|
1154
|
+
else
|
|
1155
|
+
puts "✗ #{input_file}: #{result.error.message}"
|
|
1156
|
+
end
|
|
1157
|
+
end
|
|
1158
|
+
----
|
|
1159
|
+
|
|
1160
|
+
=== Reference manifest analysis
|
|
1161
|
+
|
|
1162
|
+
[source,ruby]
|
|
1163
|
+
----
|
|
1164
|
+
result = validator.validate(svg_content, profile: :metanorma)
|
|
1165
|
+
manifest = result.reference_manifest
|
|
1166
|
+
|
|
1167
|
+
# Export manifest
|
|
1168
|
+
File.write('manifest.json', manifest.to_json)
|
|
1169
|
+
File.write('manifest.yaml', manifest.to_yaml)
|
|
1170
|
+
|
|
1171
|
+
# Analyze ID usage
|
|
1172
|
+
manifest.available_ids.each do |id_def|
|
|
1173
|
+
# Find references to this ID
|
|
1174
|
+
refs = manifest.internal_references.select do |ref|
|
|
1175
|
+
ref.fragment == id_def.id_value
|
|
1176
|
+
end
|
|
1177
|
+
|
|
1178
|
+
puts "ID '#{id_def.id_value}' referenced #{refs.size} times"
|
|
1179
|
+
end
|
|
1180
|
+
|
|
1181
|
+
# Check for unused IDs
|
|
1182
|
+
manifest.available_ids.each do |id_def|
|
|
1183
|
+
referenced = manifest.internal_references.any? do |ref|
|
|
1184
|
+
ref.fragment == id_def.id_value
|
|
1185
|
+
end
|
|
1186
|
+
|
|
1187
|
+
puts "Unused ID: #{id_def.id_value}" unless referenced
|
|
1188
|
+
end
|
|
1189
|
+
----
|
|
1190
|
+
|
|
1191
|
+
=== Integration with Metanorma
|
|
1192
|
+
|
|
1193
|
+
[source,ruby]
|
|
1194
|
+
----
|
|
1195
|
+
# Metanorma integration example
|
|
1196
|
+
class SVGProcessor
|
|
1197
|
+
def initialize
|
|
1198
|
+
@validator = SvgConform::Validator.new
|
|
1199
|
+
@profile = SvgConform::Profiles.get(:metanorma)
|
|
1200
|
+
@runner = SvgConform::RemediationRunner.new(profile: @profile)
|
|
1201
|
+
end
|
|
1202
|
+
|
|
1203
|
+
def process_svg_element(nokogiri_element)
|
|
1204
|
+
# Validate without serializing by user
|
|
1205
|
+
result = @validator.validate(nokogiri_element, profile: :metanorma)
|
|
1206
|
+
|
|
1207
|
+
if result.valid?
|
|
1208
|
+
return { status: :valid, element: nokogiri_element }
|
|
1209
|
+
end
|
|
1210
|
+
|
|
1211
|
+
# Apply fixes
|
|
1212
|
+
remediation = @runner.run_remediation(nokogiri_element.to_xml)
|
|
1213
|
+
|
|
1214
|
+
if remediation.success?
|
|
1215
|
+
# Parse fixed content and return new element
|
|
1216
|
+
fixed_doc = Nokogiri::XML(remediation.remediated_content)
|
|
1217
|
+
return {
|
|
1218
|
+
status: :fixed,
|
|
1219
|
+
element: fixed_doc.root,
|
|
1220
|
+
issues_fixed: remediation.issues_fixed
|
|
1221
|
+
}
|
|
1222
|
+
else
|
|
1223
|
+
return {
|
|
1224
|
+
status: :error,
|
|
1225
|
+
errors: result.errors,
|
|
1226
|
+
element: nokogiri_element
|
|
1227
|
+
}
|
|
1228
|
+
end
|
|
1229
|
+
end
|
|
1230
|
+
end
|
|
1231
|
+
----
|
|
1232
|
+
|
|
1233
|
+
== Error handling
|
|
1234
|
+
|
|
1235
|
+
=== Common errors
|
|
1236
|
+
|
|
1237
|
+
==== ValidationError
|
|
1238
|
+
|
|
1239
|
+
Raised when validation fails due to invalid input.
|
|
1240
|
+
|
|
1241
|
+
[source,ruby]
|
|
1242
|
+
----
|
|
1243
|
+
begin
|
|
1244
|
+
result = validator.validate_file('missing.svg', profile: :metanorma)
|
|
1245
|
+
rescue SvgConform::ValidationError => e
|
|
1246
|
+
puts "Validation error: #{e.message}"
|
|
1247
|
+
end
|
|
1248
|
+
----
|
|
1249
|
+
|
|
1250
|
+
==== ProfileError
|
|
1251
|
+
|
|
1252
|
+
Raised when profile is not found or invalid.
|
|
1253
|
+
|
|
1254
|
+
[source,ruby]
|
|
1255
|
+
----
|
|
1256
|
+
begin
|
|
1257
|
+
profile = SvgConform::Profiles.get(:nonexistent)
|
|
1258
|
+
rescue SvgConform::ProfileError => e
|
|
1259
|
+
puts "Profile error: #{e.message}"
|
|
1260
|
+
end
|
|
1261
|
+
----
|
|
1262
|
+
|
|
1263
|
+
=== Best practices
|
|
1264
|
+
|
|
1265
|
+
[source,ruby]
|
|
1266
|
+
----
|
|
1267
|
+
def safe_validate(file_path, profile)
|
|
1268
|
+
validator = SvgConform::Validator.new
|
|
1269
|
+
|
|
1270
|
+
# Check file exists
|
|
1271
|
+
unless File.exist?(file_path)
|
|
1272
|
+
return { error: "File not found: #{file_path}" }
|
|
1273
|
+
end
|
|
1274
|
+
|
|
1275
|
+
# Validate
|
|
1276
|
+
begin
|
|
1277
|
+
result = validator.validate_file(file_path, profile: profile)
|
|
1278
|
+
{
|
|
1279
|
+
valid: result.valid?,
|
|
1280
|
+
errors: result.errors.map(&:message),
|
|
1281
|
+
warnings: result.warnings.map(&:message)
|
|
1282
|
+
}
|
|
1283
|
+
rescue SvgConform::ValidationError => e
|
|
1284
|
+
{ error: "Validation failed: #{e.message}" }
|
|
1285
|
+
rescue => e
|
|
1286
|
+
{ error: "Unexpected error: #{e.message}" }
|
|
1287
|
+
end
|
|
1288
|
+
end
|
|
1289
|
+
----
|
|
1290
|
+
|
|
1291
|
+
== Performance considerations
|
|
1292
|
+
|
|
1293
|
+
=== SAX vs DOM modes
|
|
1294
|
+
|
|
1295
|
+
**SAX mode** (default):
|
|
1296
|
+
|
|
1297
|
+
* Memory-efficient, constant memory usage
|
|
1298
|
+
* Handles any file size (tested up to 100MB+)
|
|
1299
|
+
* Slightly slower for small files (<100KB)
|
|
1300
|
+
* Recommended for production
|
|
1301
|
+
|
|
1302
|
+
**DOM mode**:
|
|
1303
|
+
|
|
1304
|
+
* Fast for small files
|
|
1305
|
+
* Can hang or crash on large files (>1MB)
|
|
1306
|
+
* Required for remediations
|
|
1307
|
+
* Use only for small files
|
|
1308
|
+
|
|
1309
|
+
[source,ruby]
|
|
1310
|
+
----
|
|
1311
|
+
# SAX mode (recommended for validation only)
|
|
1312
|
+
validator = SvgConform::Validator.new(mode: :sax)
|
|
1313
|
+
|
|
1314
|
+
# Auto mode (SAX for files >1MB, DOM otherwise)
|
|
1315
|
+
validator = SvgConform::Validator.new(mode: :auto)
|
|
1316
|
+
|
|
1317
|
+
# DOM mode (only for small files)
|
|
1318
|
+
validator = SvgConform::Validator.new(mode: :dom)
|
|
1319
|
+
----
|
|
1320
|
+
|
|
1321
|
+
=== Batch processing optimization
|
|
1322
|
+
|
|
1323
|
+
[source,ruby]
|
|
1324
|
+
----
|
|
1325
|
+
# Efficient batch processing
|
|
1326
|
+
validator = SvgConform::Validator.new(mode: :sax)
|
|
1327
|
+
profile = SvgConform::Profiles.get(:metanorma) # Cache profile
|
|
1328
|
+
|
|
1329
|
+
files.each do |file|
|
|
1330
|
+
result = validator.validate_file(file, profile: profile)
|
|
1331
|
+
# Process result...
|
|
1332
|
+
end
|
|
1333
|
+
----
|
|
1334
|
+
|
|
1335
|
+
== Related documentation
|
|
1336
|
+
|
|
1337
|
+
* link:../README.adoc[Main README]
|
|
1338
|
+
* link:cli_guide.adoc[CLI Guide]
|
|
1339
|
+
* link:profiles.adoc[Profile Documentation]
|
|
1340
|
+
* link:requirements.adoc[Requirements Reference]
|
|
1341
|
+
* link:remediation.adoc[Remediation Reference]
|
|
1342
|
+
* link:reference_manifest.adoc[Reference Manifest Documentation]
|
|
1343
|
+
|
|
1344
|
+
== Support
|
|
1345
|
+
|
|
1346
|
+
For issues or questions:
|
|
1347
|
+
|
|
1348
|
+
* GitHub: https://github.com/metanorma/svg_conform
|
|
1349
|
+
* Issues: https://github.com/metanorma/svg_conform/issues
|
|
1350
|
+
|
|
1351
|
+
== Conclusion
|
|
1352
|
+
|
|
1353
|
+
The SvgConform API provides a comprehensive toolkit for SVG validation and remediation. The object-oriented design makes it easy to extend with custom requirements and remediations, while the built-in profiles handle common use cases.
|
|
1354
|
+
|
|
1355
|
+
For CLI usage, see the link:cli_guide.adoc[CLI Guide].
|