svg_conform 0.2.4 → 0.2.6
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/.gitattributes +1 -0
- data/.github/workflows/rake.yml +3 -0
- data/Gemfile +7 -3
- data/Gemfile.lock +272 -0
- data/TODO.issue-100/00-overview.md +135 -0
- data/TODO.issue-100/01-build-lens-core-v1.md +92 -0
- data/TODO.issue-100/02-adopt-lens-core-in-svg.md +123 -0
- data/TODO.issue-100/03-ship-svg-1-1-full-static.md +138 -0
- data/TODO.issue-100/04-ship-svg-2-secure-static.md +131 -0
- data/TODO.issue-100/05-freeze-candidate-and-handoff.md +108 -0
- data/TODO.issue-100/90-progress.md +65 -0
- data/config/profiles/metanorma.yml +3 -3
- data/config/profiles/svg_1_1_full_static.yml +69 -0
- data/config/profiles/svg_2_secure_static.yml +55 -0
- data/config/requirements/processing_features_static.yml +29 -0
- data/data/svg_1_1/manifest.yml +7 -0
- data/data/svg_1_1/svg11-flat-20110816.dtd +5664 -0
- data/docs/api_reference.adoc +19 -1
- data/docs/cli_guide.adoc +35 -45
- data/docs/diagnostics.adoc +73 -0
- data/docs/lens_core_handoff.adoc +84 -0
- data/docs/profiles.adoc +94 -1
- data/lib/svg_conform/cli.rb +3 -3
- data/lib/svg_conform/commands/check.rb +11 -60
- data/lib/svg_conform/conformance_report.rb +79 -1
- data/lib/svg_conform/diagnostics/lens_core_adapter.rb +200 -0
- data/lib/svg_conform/document.rb +12 -1
- data/lib/svg_conform/node_helpers.rb +1 -1
- data/lib/svg_conform/profile.rb +11 -2
- data/lib/svg_conform/quality_metrics/complexity_calculator.rb +3 -3
- data/lib/svg_conform/references/base_reference.rb +153 -0
- data/lib/svg_conform/report_comparator.rb +4 -4
- data/lib/svg_conform/requirements/base_requirement.rb +6 -0
- data/lib/svg_conform/requirements/external_references_requirement.rb +234 -0
- data/lib/svg_conform/requirements/link_validation_requirement.rb +2 -0
- data/lib/svg_conform/requirements/processing_features_requirement.rb +175 -0
- data/lib/svg_conform/requirements/svg_1_1_document_requirement.rb +213 -0
- data/lib/svg_conform/requirements.rb +6 -0
- data/lib/svg_conform/sax_document.rb +8 -4
- data/lib/svg_conform/sax_validation_handler.rb +22 -1
- data/lib/svg_conform/validation/css2_core_grammar.rb +359 -0
- data/lib/svg_conform/validation/css_references.rb +122 -0
- data/lib/svg_conform/validation/error_tracker.rb +3 -1
- data/lib/svg_conform/validation/svg_1_1_document_conformance.rb +155 -0
- data/lib/svg_conform/validation_context.rb +25 -5
- data/lib/svg_conform/validation_result.rb +91 -16
- data/lib/svg_conform/version.rb +1 -1
- data/lib/svg_conform.rb +24 -0
- data/spec/fixtures/diagnostics/external-reference-info.svg +6 -0
- data/spec/fixtures/plantuml/README.adoc +11 -0
- data/spec/fixtures/plantuml/unit-type.svg +1 -0
- data/spec/fixtures/profiles/svg_1_1_full_static/invalid/animation.svg +4 -0
- data/spec/fixtures/profiles/svg_1_1_full_static/invalid/css-grammar.svg +4 -0
- data/spec/fixtures/profiles/svg_1_1_full_static/invalid/dtd-attribute.svg +4 -0
- data/spec/fixtures/profiles/svg_1_1_full_static/invalid/dtd-content-model.svg +4 -0
- data/spec/fixtures/profiles/svg_1_1_full_static/invalid/interaction.svg +4 -0
- data/spec/fixtures/profiles/svg_1_1_full_static/invalid/malformed-xml.svg +4 -0
- data/spec/fixtures/profiles/svg_1_1_full_static/invalid/script.svg +5 -0
- data/spec/fixtures/profiles/svg_1_1_full_static/invalid/wrong-namespace.svg +4 -0
- data/spec/fixtures/profiles/svg_1_1_full_static/invalid/wrong-version.svg +4 -0
- data/spec/fixtures/profiles/svg_1_1_full_static/invalid/xml-stylesheet.svg +5 -0
- data/spec/fixtures/profiles/svg_1_1_full_static/valid/css.svg +5 -0
- data/spec/fixtures/profiles/svg_1_1_full_static/valid/foreign-metadata.svg +6 -0
- data/spec/fixtures/profiles/svg_1_1_full_static/valid/minimal-full.svg +5 -0
- data/spec/fixtures/profiles/svg_1_1_full_static/valid/xml-stylesheet.svg +5 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/animation.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/css-import.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/css-url.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/data-bad-png.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/data-empty-png.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/data-malformed-base64.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/data-malformed-percent.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/data-unsupported-type.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/file-like-fragment.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/http.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/https.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/interaction.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/protocol-relative.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/relative-file-fragment.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/relative-file.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/script.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/svg-data-excessive-nesting.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/svg-data-malformed-xml.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/svg-data-nested-external.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/urn.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/invalid/xml-stylesheet.svg +5 -0
- data/spec/fixtures/profiles/svg_2_secure_static/valid/jpeg-base64.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/valid/jpeg-percent.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/valid/png-base64.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/valid/png-percent.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/valid/same-document-fragment.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/valid/svg-base64.svg +4 -0
- data/spec/fixtures/profiles/svg_2_secure_static/valid/svg-percent.svg +4 -0
- data/spec/svg_conform/commands/check_command_spec.rb +22 -2
- data/spec/svg_conform/commands/profiles_command_spec.rb +18 -0
- data/spec/svg_conform/conformance_report_spec.rb +90 -0
- data/spec/svg_conform/diagnostics/lens_core_adapter_spec.rb +136 -0
- data/spec/svg_conform/gemspec_spec.rb +21 -0
- data/spec/svg_conform/profiles/svg_1_1_full_static_profile_spec.rb +146 -0
- data/spec/svg_conform/profiles/svg_2_secure_static_profile_spec.rb +150 -0
- data/spec/svg_conform/profiles_plantuml_spec.rb +81 -0
- data/spec/svg_conform/references/data_uri_reference_spec.rb +97 -0
- data/spec/svg_conform/references/integration_spec.rb +30 -0
- data/spec/svg_conform/references/reference_classifier_spec.rb +17 -0
- data/spec/svg_conform/requirements/external_references_requirement_spec.rb +109 -0
- data/spec/svg_conform/requirements/link_validation_requirement_spec.rb +16 -0
- data/spec/svg_conform/requirements/processing_features_requirement_spec.rb +75 -0
- data/spec/svg_conform/requirements/svg_1_1_document_requirement_spec.rb +141 -0
- data/spec/svg_conform/validation/css2_core_grammar_spec.rb +116 -0
- data/spec/svg_conform/validation/svg_1_1_document_conformance_spec.rb +144 -0
- data/spec/svg_conform/validation_context_source_plumbing_spec.rb +122 -0
- data/spec/svg_conform/validation_context_spec.rb +39 -0
- data/spec/svg_conform/validation_result_spec.rb +123 -0
- data/svg_conform.gemspec +3 -2
- metadata +102 -6
data/docs/api_reference.adoc
CHANGED
|
@@ -336,10 +336,28 @@ Check if document is valid.
|
|
|
336
336
|
|
|
337
337
|
===== has_errors?
|
|
338
338
|
|
|
339
|
-
Check if errors were found
|
|
339
|
+
Check if errors or validity errors were found (the same condition as
|
|
340
|
+
`valid?` being false).
|
|
340
341
|
|
|
341
342
|
**Returns**: `Boolean`
|
|
342
343
|
|
|
344
|
+
===== infos / info_count
|
|
345
|
+
|
|
346
|
+
Informational diagnostics (for example external reference notices) and their
|
|
347
|
+
count. Infos never affect `valid?`.
|
|
348
|
+
|
|
349
|
+
===== to_lens_core_report(timestamp: nil)
|
|
350
|
+
|
|
351
|
+
Returns the canonical `LensCore::Report` for this result. Errors and validity
|
|
352
|
+
errors are both severity `error`. The timestamp is included only when the
|
|
353
|
+
caller supplies one. See xref:diagnostics.adoc[Diagnostics].
|
|
354
|
+
|
|
355
|
+
===== to_h / to_json / to_s
|
|
356
|
+
|
|
357
|
+
Project the canonical report. `to_h` is symbol-keyed. `file`,
|
|
358
|
+
`fixes_applied`, `fixable` and `reference_manifest` are retained as
|
|
359
|
+
compatibility extensions.
|
|
360
|
+
|
|
343
361
|
===== has_warnings?
|
|
344
362
|
|
|
345
363
|
Check if warnings were found.
|
data/docs/cli_guide.adoc
CHANGED
|
@@ -125,6 +125,18 @@ Available profiles:
|
|
|
125
125
|
* `metanorma` - Metanorma requirements
|
|
126
126
|
* `lucid` - Lucid profile
|
|
127
127
|
* `no_external_css` - No external CSS
|
|
128
|
+
* `svg_1_1_full_static` - SVG 1.1 Full document grammar, static processing (see link:profiles.adoc#svg-1-1-full-static-profile[profiles])
|
|
129
|
+
* `svg_2_secure_static` - SVG 2 secure-static resource rules (opt-in; see link:profiles.adoc#svg-2-secure-static-profile[profiles])
|
|
130
|
+
|
|
131
|
+
Examples:
|
|
132
|
+
|
|
133
|
+
* `svg_conform check drawing.svg --profile svg_1_1_full_static`
|
|
134
|
+
* SVG 2 secure-static:
|
|
135
|
+
|
|
136
|
+
[source,sh]
|
|
137
|
+
----
|
|
138
|
+
svg_conform check drawing.svg --profile svg_2_secure_static --format json
|
|
139
|
+
----
|
|
128
140
|
|
|
129
141
|
==== Output format (single file)
|
|
130
142
|
|
|
@@ -133,9 +145,9 @@ Output format for single file mode (default: `table`)
|
|
|
133
145
|
+
|
|
134
146
|
Options:
|
|
135
147
|
+
|
|
136
|
-
* `table` - Human-readable
|
|
137
|
-
* `json` - JSON
|
|
138
|
-
* `yaml` - YAML
|
|
148
|
+
* `table` - Human-readable text (default); an alias for the LensCore human renderer
|
|
149
|
+
* `json` - LensCore diagnostics JSON (schema version 1)
|
|
150
|
+
* `yaml` - LensCore diagnostics YAML (schema version 1)
|
|
139
151
|
|
|
140
152
|
`--output=FILE` or `-o`::
|
|
141
153
|
Save report to file instead of stdout
|
|
@@ -403,67 +415,45 @@ fi
|
|
|
403
415
|
|
|
404
416
|
==== Table format (default)
|
|
405
417
|
|
|
406
|
-
|
|
418
|
+
Plain text rendered by LensCore from the canonical report. Errors, warnings
|
|
419
|
+
and infos all appear; see xref:diagnostics.adoc[Diagnostics].
|
|
407
420
|
|
|
408
421
|
[source,text]
|
|
409
422
|
----
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
File: file.svg
|
|
413
|
-
Profile: metanorma
|
|
414
|
-
Valid: ✗
|
|
415
|
-
Total Errors: 2
|
|
416
|
-
|
|
417
|
-
Validation Errors:
|
|
423
|
+
svg_conform 0.2.4, profile metanorma
|
|
424
|
+
INVALID: 1 error, 0 warnings, 1 info
|
|
418
425
|
|
|
419
|
-
|
|
420
|
-
|
|
426
|
+
error viewbox_required (viewbox_violation)
|
|
427
|
+
at /svg[1]
|
|
428
|
+
Viewbox is required
|
|
429
|
+
fix: Add missing viewBox attribute
|
|
421
430
|
|
|
422
|
-
|
|
423
|
-
|
|
431
|
+
info link_validation (reference_violation)
|
|
432
|
+
at /svg[1]/a[1]
|
|
433
|
+
External reference: https://example.com/page
|
|
424
434
|
----
|
|
425
435
|
|
|
426
436
|
==== JSON format
|
|
427
437
|
|
|
428
|
-
|
|
438
|
+
The LensCore diagnostics report, schema version 1 (see
|
|
439
|
+
xref:diagnostics.adoc[Diagnostics]). No timestamp is emitted by the CLI.
|
|
429
440
|
|
|
430
441
|
[source,json]
|
|
431
442
|
----
|
|
432
443
|
{
|
|
433
|
-
"
|
|
434
|
-
"
|
|
444
|
+
"schema_version": 1,
|
|
445
|
+
"producer": { "name": "svg_conform", "version": "0.2.4" },
|
|
446
|
+
"profile": { "id": "metanorma", "citations": [] },
|
|
435
447
|
"valid": false,
|
|
436
|
-
"errors":
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
{
|
|
440
|
-
"requirement_id": "viewbox_required",
|
|
441
|
-
"message": "Viewbox is required",
|
|
442
|
-
"element": "svg",
|
|
443
|
-
"line_number": 1
|
|
444
|
-
}
|
|
445
|
-
]
|
|
446
|
-
}
|
|
448
|
+
"summary": { "errors": 1, "warnings": 0, "infos": 1 },
|
|
449
|
+
"diagnostics": [ ... ],
|
|
450
|
+
"file": "file.svg"
|
|
447
451
|
}
|
|
448
452
|
----
|
|
449
453
|
|
|
450
454
|
==== YAML format
|
|
451
455
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
[source,yaml]
|
|
455
|
-
----
|
|
456
|
-
filename: file.svg
|
|
457
|
-
profile: metanorma
|
|
458
|
-
valid: false
|
|
459
|
-
errors:
|
|
460
|
-
total_count: 2
|
|
461
|
-
issues:
|
|
462
|
-
- requirement_id: viewbox_required
|
|
463
|
-
message: Viewbox is required
|
|
464
|
-
element: svg
|
|
465
|
-
line_number: 1
|
|
466
|
-
----
|
|
456
|
+
The same report and key order as the JSON format, rendered as YAML.
|
|
467
457
|
|
|
468
458
|
==== Batch report format
|
|
469
459
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
= SvgConform Diagnostics
|
|
2
|
+
:toc: left
|
|
3
|
+
:source-highlighter: rouge
|
|
4
|
+
|
|
5
|
+
== Purpose
|
|
6
|
+
|
|
7
|
+
SvgConform reports one validation result as one
|
|
8
|
+
https://github.com/claricle/lens_core[LensCore] diagnostics report (schema
|
|
9
|
+
version 1). JSON, YAML and human output all render from that report.
|
|
10
|
+
|
|
11
|
+
== Producing a report
|
|
12
|
+
|
|
13
|
+
[source,ruby]
|
|
14
|
+
----
|
|
15
|
+
result = SvgConform::Validator.new.validate_file("a.svg", profile: :svg_1_2_rfc)
|
|
16
|
+
|
|
17
|
+
report = result.to_lens_core_report # LensCore::Report
|
|
18
|
+
report = result.to_lens_core_report(timestamp: Time.now) # timestamp only if given
|
|
19
|
+
|
|
20
|
+
LensCore::Renderers::Json.render(report)
|
|
21
|
+
LensCore::Renderers::Yaml.render(report)
|
|
22
|
+
LensCore::Renderers::Human.render(report)
|
|
23
|
+
----
|
|
24
|
+
|
|
25
|
+
`SvgConform::Diagnostics::LensCoreAdapter.call(result, filename: nil,
|
|
26
|
+
timestamp: nil)` is the single entry point behind these.
|
|
27
|
+
|
|
28
|
+
== Mapping
|
|
29
|
+
|
|
30
|
+
[cols="2,1,3"]
|
|
31
|
+
|===
|
|
32
|
+
| svg_conform source | Severity | Kind
|
|
33
|
+
|
|
34
|
+
| `errors` | `error` | the issue's violation type, else `validation`
|
|
35
|
+
| `validity_errors` | `error` | `validity`
|
|
36
|
+
| `warnings` | `warning` | the issue's violation type, else `validation`
|
|
37
|
+
| `infos` | `info` | the issue's violation type, else `notice`
|
|
38
|
+
|===
|
|
39
|
+
|
|
40
|
+
An `unknown_violation` type counts as absent. Order within each channel is
|
|
41
|
+
preserved and channels are emitted in the order above. Requirement IDs are
|
|
42
|
+
the explicit IDs on the issues; infos created through
|
|
43
|
+
`ValidationContext#add_notice(requirement_id:)` and
|
|
44
|
+
`#add_external_reference_notice(requirement_id:)` carry their requirement's
|
|
45
|
+
ID. `valid` is true only when there are no errors and no validity errors.
|
|
46
|
+
|
|
47
|
+
Each diagnostic carries `location` (XML line, column and element path when
|
|
48
|
+
known), `remediation` (the suggested action when remediable) and `context`
|
|
49
|
+
(`element`, remediation flags, and the issue `data`). Issue data must be
|
|
50
|
+
JSON-safe; symbols become strings, and any other object raises
|
|
51
|
+
`SvgConform::Diagnostics::UnsupportedContextError`.
|
|
52
|
+
|
|
53
|
+
== Citations
|
|
54
|
+
|
|
55
|
+
`Profile` accepts `specification_uri` and `specification_label`; every
|
|
56
|
+
requirement accepts `citation_uri` and `citation_label`. Both are read from
|
|
57
|
+
profile YAML and become LensCore citations on the report profile and on
|
|
58
|
+
diagnostics for that requirement. Existing profiles may omit them.
|
|
59
|
+
|
|
60
|
+
== Transition
|
|
61
|
+
|
|
62
|
+
For one transition the previous surfaces remain callable:
|
|
63
|
+
|
|
64
|
+
* `ValidationResult#to_h`, `#to_json`, `#to_s(format:)` project the canonical
|
|
65
|
+
report, with `file`, `fixes_applied`, `fixable` and `reference_manifest` as
|
|
66
|
+
compatibility extensions.
|
|
67
|
+
* `ConformanceReport.from_svg_conform_result` (deprecated) builds the
|
|
68
|
+
canonical report first and projects its errors and warnings into the legacy
|
|
69
|
+
YAML shape. `use_svgcheck_mapping:` and `from_svgcheck_result` keep their
|
|
70
|
+
signatures; svgcheck filtering happens after canonical construction and
|
|
71
|
+
never mutates the report.
|
|
72
|
+
* `svg_conform check --format table|json|yaml` renders through LensCore;
|
|
73
|
+
`table` is an alias for the human renderer.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
= LensCore v1 freeze candidate: adoption handoff
|
|
2
|
+
:toc: left
|
|
3
|
+
:source-highlighter: rouge
|
|
4
|
+
|
|
5
|
+
== Purpose
|
|
6
|
+
|
|
7
|
+
This page lets another lens adopt https://github.com/claricle/lens_core[LensCore]
|
|
8
|
+
without reading any SVG code. It names the exact immutable commits and digests.
|
|
9
|
+
Nothing here is tagged, released or published.
|
|
10
|
+
|
|
11
|
+
== Pinned facts
|
|
12
|
+
|
|
13
|
+
[cols="2,5"]
|
|
14
|
+
|===
|
|
15
|
+
| LensCore freeze commit | `d8903de61dec5b600853e1d605a712cc83f99c41` (claricle/lens_core#4 merge commit)
|
|
16
|
+
| Schema path | `schemas/lens-diagnostics-v1.json`
|
|
17
|
+
| Schema SHA-256 | `90f0976166aaa88417d3eec59cab8288e085b89230a9521e431169acb3e87cba`
|
|
18
|
+
| Schema version | `1` (`LensCore::SCHEMA_VERSION`)
|
|
19
|
+
| Golden manifest | `spec/fixtures/v1/golden/manifest.yml`
|
|
20
|
+
| Contract commit used to render the goldens | `98c21c82bc97e3c8c4038048acdcec4a177c5f7c` (schema and `lib/` are identical at the freeze commit)
|
|
21
|
+
| svg_conform producer commit named by the manifest | `5bf0f80af2a80bb5fd9f8a68cc95e470ae43e5d0`
|
|
22
|
+
| svg_conform pin | `Gemfile` and `Gemfile.lock` resolve LensCore to the freeze commit above
|
|
23
|
+
|===
|
|
24
|
+
|
|
25
|
+
== Adopting
|
|
26
|
+
|
|
27
|
+
Pin the exact commit, never a branch:
|
|
28
|
+
|
|
29
|
+
[source,ruby]
|
|
30
|
+
----
|
|
31
|
+
gem "lens_core", git: "https://github.com/claricle/lens_core.git",
|
|
32
|
+
ref: "d8903de61dec5b600853e1d605a712cc83f99c41"
|
|
33
|
+
----
|
|
34
|
+
|
|
35
|
+
Canonical calls:
|
|
36
|
+
|
|
37
|
+
[source,ruby]
|
|
38
|
+
----
|
|
39
|
+
report = LensCore::Parsers::Json.parse(File.binread("report.json")) # or Parsers::Yaml
|
|
40
|
+
LensCore::Renderers::Json.render(report) # canonical bytes
|
|
41
|
+
LensCore::Renderers::Yaml.render(report)
|
|
42
|
+
LensCore::Renderers::Human.render(report)
|
|
43
|
+
----
|
|
44
|
+
|
|
45
|
+
Rendering a parsed canonical golden returns the input bytes exactly. Timestamps
|
|
46
|
+
are omitted unless the producer passes one.
|
|
47
|
+
|
|
48
|
+
== Compatibility and errors
|
|
49
|
+
|
|
50
|
+
* A `schema_version` other than `1` raises `LensCore::UnsupportedVersionError`;
|
|
51
|
+
a non-integer one raises `LensCore::SchemaError`.
|
|
52
|
+
* Within version 1 consumers accept new `kind` values and unknown members.
|
|
53
|
+
Removing a member, changing its meaning, or adding a `severity` or location
|
|
54
|
+
`type` is a new schema version.
|
|
55
|
+
* Other failures are `LensCore::ParseError`, `UnsafeYamlError`,
|
|
56
|
+
`InvalidEncodingError` and `SchemaError` (with the failing path).
|
|
57
|
+
* `summary` is derived and checked on parse; `valid: true` with an error is
|
|
58
|
+
rejected. The full rules are in LensCore `docs/diagnostics-v1.adoc`.
|
|
59
|
+
|
|
60
|
+
== Evidence completed
|
|
61
|
+
|
|
62
|
+
* Three real svg_conform exports in `spec/fixtures/v1/golden/svg_conform/`:
|
|
63
|
+
SVG 1.1 invalid CSS grammar, SVG 2 external reference, and a four-channel
|
|
64
|
+
report (error, validity error, warning, info) built through the public
|
|
65
|
+
`SvgConform::Diagnostics::LensCoreAdapter`.
|
|
66
|
+
* Each golden validates against the schema, parses, and renders byte for byte;
|
|
67
|
+
summary counts equal the diagnostics.
|
|
68
|
+
* `spec/fixtures/host_app/` reads the whole golden directory using LensCore
|
|
69
|
+
alone (no svg_conform, no other lens).
|
|
70
|
+
* `script/refresh_svg_goldens PATH_TO_SVG_CONFORM` regenerates the three SVG
|
|
71
|
+
goldens from a checkout at the manifest's producer commit.
|
|
72
|
+
|
|
73
|
+
== Not evidence
|
|
74
|
+
|
|
75
|
+
`spec/fixtures/v1/golden/examples/binary-location.json` is a format-neutral
|
|
76
|
+
contract example of the binary location form. It is not PNG, EMF or PostScript
|
|
77
|
+
implementation evidence; no lens other than svg_conform has adopted LensCore.
|
|
78
|
+
|
|
79
|
+
== Future work, outside this issue
|
|
80
|
+
|
|
81
|
+
* EMF, PNG and PostScript adapters.
|
|
82
|
+
* Tagging and publishing a LensCore gem, and replacing every Git pin with a
|
|
83
|
+
released constraint.
|
|
84
|
+
* Migrating other lenses.
|
data/docs/profiles.adoc
CHANGED
|
@@ -17,7 +17,46 @@ Each profile consists of:
|
|
|
17
17
|
|
|
18
18
|
== Available profiles
|
|
19
19
|
|
|
20
|
-
SvgConform includes
|
|
20
|
+
SvgConform includes built-in profiles, each designed for different use cases and compliance standards.
|
|
21
|
+
|
|
22
|
+
[[svg-1-1-full-static-profile]]
|
|
23
|
+
=== SVG 1.1 Full static profile (`:svg_1_1_full_static`)
|
|
24
|
+
|
|
25
|
+
**Purpose**: Opt-in check that a document is a conforming SVG 1.1 (Second Edition) Full document under static processing. It validates the Recommendation's document grammar; it is not an element allow-list.
|
|
26
|
+
|
|
27
|
+
**Specification**: https://www.w3.org/TR/2011/REC-SVG11-20110816/[SVG 1.1 Second Edition]
|
|
28
|
+
|
|
29
|
+
**Requirements** (each reports under its own ID and carries its normative citation in issues and LensCore reports):
|
|
30
|
+
|
|
31
|
+
[cols="2,4,3"]
|
|
32
|
+
|===
|
|
33
|
+
| ID | Rule | Citation
|
|
34
|
+
|
|
35
|
+
| `svg11.document.xml` | Well-formed XML; the original declaration, doctype and processing instructions are provable from the source | https://www.w3.org/TR/2011/REC-SVG11-20110816/conform.html#ConformingSVGDocuments[Conforming SVG Documents]
|
|
36
|
+
| `svg11.document.root` | Document element is `svg` in `http://www.w3.org/2000/svg` | https://www.w3.org/TR/2011/REC-SVG11-20110816/struct.html#SVGElement[The svg element]
|
|
37
|
+
| `svg11.document.version` | Root declares `version="1.1"` | https://www.w3.org/TR/2011/REC-SVG11-20110816/struct.html#SVGElementVersionAttribute[version attribute]
|
|
38
|
+
| `svg11.document.grammar` | Elements, attributes and content models validate against the vendored flat DTD (`data/svg_1_1/svg11-flat-20110816.dtd`) after the Appendix G.2 foreign-namespace transform, applied to a copy | https://www.w3.org/TR/2011/REC-SVG11-20110816/conform.html#ConformingSVGDocuments[Conforming SVG Documents]
|
|
39
|
+
| `svg11.document.css2` | `style` attributes and `style` elements parse under the CSS 2 core grammar (syntax only) | https://www.w3.org/TR/2008/REC-CSS2-20080411/grammar.html[CSS 2 grammar]
|
|
40
|
+
| `svg11.document.xml_stylesheet` | `xml-stylesheet` processing instructions have valid pseudo-attributes, an `href`, and a CSS `type` | https://www.w3.org/TR/1999/REC-xml-stylesheet-19990629/[xml-stylesheet]
|
|
41
|
+
| `svg11.processing.script` | No `script` element or script event-handler attribute | https://www.w3.org/TR/2011/REC-SVG11-20110816/conform.html#ConformingSVGViewers[Conforming SVG Viewers]
|
|
42
|
+
| `svg11.processing.animation` | No animation element or animation-timing attribute | same
|
|
43
|
+
| `svg11.processing.interaction` | No interaction-only event attribute, `cursor` element or `cursor` attribute | same
|
|
44
|
+
|===
|
|
45
|
+
|
|
46
|
+
**Example**:
|
|
47
|
+
|
|
48
|
+
[source,shell]
|
|
49
|
+
----
|
|
50
|
+
svg_conform check drawing.svg --profile svg_1_1_full_static --format json
|
|
51
|
+
----
|
|
52
|
+
|
|
53
|
+
**Behavior notes**:
|
|
54
|
+
|
|
55
|
+
* Validation is offline: the DTD is resolved from the gem, and any external entity declaration is an error. No file is read and no network request is made.
|
|
56
|
+
* DOM and SAX validation give the same result for the same input. A malformed document cannot be loaded as a DOM, so DOM mode raises `SvgConform::ParseError` where SAX reports `svg11.document.xml`.
|
|
57
|
+
* Declaration, doctype and processing-instruction checks need the original text. A document validated from an already-parsed node (re-serialized source) is reported under `svg11.document.xml` because those claims cannot be proved.
|
|
58
|
+
|
|
59
|
+
**Non-goals**: SVG Tiny or Basic, rendering or browser behavior, CSS property and value semantics, external style sheets referenced by `xml-stylesheet`, and remediation (the profile has none).
|
|
21
60
|
|
|
22
61
|
[[svg-1-2-rfc-profile]]
|
|
23
62
|
=== SVG 1.2 RFC profile (`:svg_1_2_rfc`)
|
|
@@ -184,6 +223,51 @@ remediations:
|
|
|
184
223
|
|
|
185
224
|
See link:remediation.adoc#viewbox-remediation[ViewboxRemediation] and link:remediation.adoc#namespace-remediation[NamespaceRemediation] for detailed remediation information.
|
|
186
225
|
|
|
226
|
+
[[svg-2-secure-static-profile]]
|
|
227
|
+
=== SVG 2 secure-static profile (`:svg_2_secure_static`)
|
|
228
|
+
|
|
229
|
+
**Purpose**: Checks the resource and processing rules of the secure-static processing mode in the SVG 2 Candidate Recommendation of 6 October 2026 (`https://www.w3.org/TR/2026/CR-SVG2-20261006/conform.html#secure-static-mode`).
|
|
230
|
+
|
|
231
|
+
The profile is opt-in. No existing profile and no default selection changes.
|
|
232
|
+
|
|
233
|
+
**Stable requirement IDs** (all cite the dated secure-static section):
|
|
234
|
+
|
|
235
|
+
[cols="1,3"]
|
|
236
|
+
|===
|
|
237
|
+
|ID |Rule
|
|
238
|
+
|
|
239
|
+
|`svg2.secure_static.script` |Rejects `script` elements and script event-handler attributes.
|
|
240
|
+
|`svg2.secure_static.animation` |Rejects SVG animation elements and timing attributes, and CSS `animation*` / `transition*` attributes.
|
|
241
|
+
|`svg2.secure_static.interaction` |Rejects interaction event attributes and the `cursor` attribute or element.
|
|
242
|
+
|`svg2.secure_static.external_reference` |Rejects references that need another document, a local file or the network.
|
|
243
|
+
|===
|
|
244
|
+
|
|
245
|
+
The first three IDs configure the shared `ProcessingFeaturesRequirement`; the fourth is `ExternalReferencesRequirement`, which classifies every reference with `SvgConform::References::ReferenceClassifier`.
|
|
246
|
+
|
|
247
|
+
**Accepted references**:
|
|
248
|
+
|
|
249
|
+
[cols="2,1,3"]
|
|
250
|
+
|===
|
|
251
|
+
|Reference |Result |Notes
|
|
252
|
+
|
|
253
|
+
|`#fragment` in the current document |allowed |`link_validation` still reports a missing target.
|
|
254
|
+
|`data:image/png` or `data:image/jpeg`, base64 or percent-encoded |allowed |Payload must be non-empty and a structurally valid PNG (chunk checksums) or JPEG (SOI/EOI).
|
|
255
|
+
|`data:image/svg+xml`, base64 or percent-encoded |allowed |Must decode to one well-formed SVG document with no external reference of its own; nesting is limited to 3 levels.
|
|
256
|
+
|Other media types, malformed base64 or percent-encoding |rejected |
|
|
257
|
+
|`http:`, `https:`, `//host/...`, `urn:`, `file:` and other schemes |rejected |
|
|
258
|
+
|Relative paths, including `other.svg#id` |rejected |
|
|
259
|
+
|===
|
|
260
|
+
|
|
261
|
+
Inspected locations: `href` and `xlink:href`; `fill`, `stroke`, `filter`, `clip-path`, `mask`, `marker-*` and `cursor` attributes; CSS `url(...)` and `@import` in `style` attributes and `style` elements; and `xml-stylesheet` processing instructions in the original source.
|
|
262
|
+
|
|
263
|
+
**Non-goals**: This profile does not claim complete SVG 2 document, rendering or CSS conformance. It does not check `font-face` sources, CSS `@font-face`, script or animation expressed only inside CSS rules, or the contents of raster images.
|
|
264
|
+
|
|
265
|
+
**Example**:
|
|
266
|
+
[source,sh]
|
|
267
|
+
----
|
|
268
|
+
svg_conform check drawing.svg --profile svg_2_secure_static
|
|
269
|
+
----
|
|
270
|
+
|
|
187
271
|
[[no-external-css-profile]]
|
|
188
272
|
=== No external CSS profile (`:no_external_css`)
|
|
189
273
|
|
|
@@ -274,6 +358,15 @@ See link:remediation.adoc#namespace-attribute-remediation[NamespaceAttributeReme
|
|
|
274
358
|
* **Structural compliance**: Proper namespaces and viewBox required
|
|
275
359
|
* **No external dependencies**: External CSS and fonts strictly prohibited
|
|
276
360
|
|
|
361
|
+
**PlantUML attributes**:
|
|
362
|
+
|
|
363
|
+
The Metanorma compatibility profile accepts `contentStyleType` and `zoomAndPan`
|
|
364
|
+
on `svg`, `textLength` and `lengthAdjust` on `text`, and `title` on `a`.
|
|
365
|
+
These explicit allowances preserve generated diagrams' measured text layout and
|
|
366
|
+
link titles. They do not change the strict RFC 7996 profiles or permit arbitrary
|
|
367
|
+
additional attributes. Fragment links into a surrounding document must also be
|
|
368
|
+
validated by that document's build.
|
|
369
|
+
|
|
277
370
|
**Event Handler Support**:
|
|
278
371
|
|
|
279
372
|
The metanorma profile allows SVG event handler attributes (e.g., `onmouseover`, `onmouseout`, `onfocus`, `onblur`) for interactive elements. This is configured using wildcard attribute patterns:
|
data/lib/svg_conform/cli.rb
CHANGED
|
@@ -45,9 +45,9 @@ module SvgConform
|
|
|
45
45
|
- Directory: Requires --output-dir or --in-place
|
|
46
46
|
|
|
47
47
|
REPORT FORMATS:
|
|
48
|
-
- table: Human-readable (default for single file)
|
|
49
|
-
- json: JSON
|
|
50
|
-
- yaml: YAML
|
|
48
|
+
- table: Human-readable text (default for single file; alias for the LensCore human renderer)
|
|
49
|
+
- json: LensCore diagnostics JSON (schema version 1)
|
|
50
|
+
- yaml: LensCore diagnostics YAML (schema version 1)
|
|
51
51
|
|
|
52
52
|
EXAMPLES:
|
|
53
53
|
svg_conform check file.svg -p metanorma
|
|
@@ -69,78 +69,29 @@ module SvgConform
|
|
|
69
69
|
result = validator.validate_file(@file,
|
|
70
70
|
profile: @options[:profile].to_sym)
|
|
71
71
|
|
|
72
|
-
#
|
|
73
|
-
report = SvgConform::
|
|
74
|
-
File.basename(@file)
|
|
75
|
-
result,
|
|
76
|
-
profile: @options[:profile].to_sym,
|
|
72
|
+
# One canonical report; every output format renders from it.
|
|
73
|
+
report = SvgConform::Diagnostics::LensCoreAdapter.call(
|
|
74
|
+
result, filename: File.basename(@file)
|
|
77
75
|
)
|
|
78
76
|
|
|
79
|
-
|
|
80
|
-
case @options[:format]
|
|
81
|
-
when "yaml"
|
|
82
|
-
output_yaml(report)
|
|
83
|
-
when "json"
|
|
84
|
-
output_json(report)
|
|
85
|
-
else
|
|
86
|
-
output_table(report)
|
|
87
|
-
end
|
|
77
|
+
write_output(render(report))
|
|
88
78
|
|
|
89
79
|
# Create remediated file if requested
|
|
90
80
|
create_remediated_file(result) if @options[:fix]
|
|
91
81
|
|
|
92
82
|
# Exit with appropriate code based on validation result
|
|
93
|
-
exit(report.valid
|
|
83
|
+
exit(report.valid ? 0 : 1)
|
|
94
84
|
end
|
|
95
85
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
puts "Total Errors: #{report.errors.total_count}"
|
|
103
|
-
puts
|
|
104
|
-
|
|
105
|
-
if report.errors.total_count.positive?
|
|
106
|
-
puts Paint["Validation Errors:", :bold]
|
|
107
|
-
puts
|
|
108
|
-
|
|
109
|
-
# Group errors by type
|
|
110
|
-
error_groups = report.errors.issues.group_by(&:message)
|
|
111
|
-
|
|
112
|
-
error_groups.each do |message, errors|
|
|
113
|
-
puts Paint["• #{message}", :red]
|
|
114
|
-
if errors.size > 1
|
|
115
|
-
puts Paint[" (#{errors.size} occurrences)",
|
|
116
|
-
:black]
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Show first few locations
|
|
120
|
-
errors.first(3).each do |error|
|
|
121
|
-
puts Paint[" at #{error.element}", :black] if error.element
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
if errors.size > 3
|
|
125
|
-
puts Paint[" ... and #{errors.size - 3} more",
|
|
126
|
-
:black]
|
|
127
|
-
end
|
|
128
|
-
puts
|
|
129
|
-
end
|
|
130
|
-
else
|
|
131
|
-
puts Paint["✓ No validation errors found", :green]
|
|
86
|
+
# "table" stays an accepted alias for the human renderer.
|
|
87
|
+
def render(report)
|
|
88
|
+
case @options[:format]
|
|
89
|
+
when "yaml" then LensCore::Renderers::Yaml.render(report)
|
|
90
|
+
when "json" then LensCore::Renderers::Json.render(report)
|
|
91
|
+
else LensCore::Renderers::Human.render(report)
|
|
132
92
|
end
|
|
133
93
|
end
|
|
134
94
|
|
|
135
|
-
def output_yaml(report)
|
|
136
|
-
output = report.to_yaml
|
|
137
|
-
write_output(output)
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def output_json(report)
|
|
141
|
-
write_output(report.to_json)
|
|
142
|
-
end
|
|
143
|
-
|
|
144
95
|
def write_output(content)
|
|
145
96
|
if @options[:output]
|
|
146
97
|
File.write(@options[:output], content)
|
|
@@ -74,13 +74,20 @@ module SvgConform
|
|
|
74
74
|
# Create report from SvgConform ValidationResult
|
|
75
75
|
def self.from_svg_conform_result(filename, validation_result, profile: nil,
|
|
76
76
|
use_svgcheck_mapping: false)
|
|
77
|
+
# The canonical LensCore report is built first, for every path; the
|
|
78
|
+
# legacy shape below is a deprecated projection of it. Without the
|
|
79
|
+
# optional lens_core gem the report projects the validation result
|
|
80
|
+
# directly.
|
|
81
|
+
canonical = if SvgConform.lens_core_available?
|
|
82
|
+
validation_result.to_lens_core_report
|
|
83
|
+
end
|
|
77
84
|
report = new
|
|
78
85
|
report.filename = filename
|
|
79
86
|
report.profile = profile
|
|
80
87
|
report.tool = "svg_conform"
|
|
81
88
|
report.version = SvgConform::VERSION
|
|
82
89
|
report.timestamp = Time.now.iso8601
|
|
83
|
-
report.valid = validation_result.valid?
|
|
90
|
+
report.valid = canonical ? canonical.valid : validation_result.valid?
|
|
84
91
|
report.errors = IssueSummary.new
|
|
85
92
|
report.errors.total_count = 0
|
|
86
93
|
report.errors.by_requirement = {}
|
|
@@ -104,6 +111,15 @@ use_svgcheck_mapping: false)
|
|
|
104
111
|
end
|
|
105
112
|
end
|
|
106
113
|
|
|
114
|
+
unless use_svgcheck_mapping
|
|
115
|
+
if canonical
|
|
116
|
+
project_canonical(report, canonical)
|
|
117
|
+
else
|
|
118
|
+
project_validation_result(report, validation_result)
|
|
119
|
+
end
|
|
120
|
+
return report
|
|
121
|
+
end
|
|
122
|
+
|
|
107
123
|
# Process errors (including conditional validity_errors for svgcheck compatibility)
|
|
108
124
|
all_errors = validation_result.errors.dup
|
|
109
125
|
|
|
@@ -180,6 +196,68 @@ use_svgcheck_mapping: false)
|
|
|
180
196
|
report
|
|
181
197
|
end
|
|
182
198
|
|
|
199
|
+
# Fills the legacy summaries from the canonical report's error and
|
|
200
|
+
# warning diagnostics (infos have no legacy channel).
|
|
201
|
+
def self.project_canonical(report, canonical)
|
|
202
|
+
{
|
|
203
|
+
"error" => report.errors, "warning" => report.warnings
|
|
204
|
+
}.each do |severity, summary|
|
|
205
|
+
diagnostics = canonical.diagnostics.select do |d|
|
|
206
|
+
d.severity == severity
|
|
207
|
+
end
|
|
208
|
+
summary.total_count = diagnostics.length
|
|
209
|
+
diagnostics.group_by(&:requirement_id).each do |id, list|
|
|
210
|
+
summary.by_requirement[id] = list.length
|
|
211
|
+
end
|
|
212
|
+
summary.issues = diagnostics.map { |d| issue_from(d) }
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def self.issue_from(diagnostic)
|
|
217
|
+
data = diagnostic.context["data"]
|
|
218
|
+
data = {} unless data.is_a?(Hash)
|
|
219
|
+
issue = ConformanceIssue.new
|
|
220
|
+
issue.type = diagnostic.severity
|
|
221
|
+
issue.requirement_id = diagnostic.requirement_id
|
|
222
|
+
issue.message = diagnostic.message
|
|
223
|
+
issue.element = diagnostic.context["element"]
|
|
224
|
+
if diagnostic.severity == "error"
|
|
225
|
+
issue.attribute = data["attribute"]
|
|
226
|
+
issue.value = data["value"]&.to_s
|
|
227
|
+
end
|
|
228
|
+
issue.line = diagnostic.location&.line
|
|
229
|
+
issue
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# The lens_core-free projection: fill the summaries straight from the
|
|
233
|
+
# validation result, mirroring project_canonical's output shape.
|
|
234
|
+
def self.project_validation_result(report, result)
|
|
235
|
+
{
|
|
236
|
+
"error" => [report.errors, result.errors],
|
|
237
|
+
"warning" => [report.warnings, result.warnings],
|
|
238
|
+
}.each do |severity, (summary, issues)|
|
|
239
|
+
summary.total_count = issues.length
|
|
240
|
+
issues.group_by(&:requirement_id).each do |id, list|
|
|
241
|
+
summary.by_requirement[id] = list.length
|
|
242
|
+
end
|
|
243
|
+
summary.issues = issues.map do |error|
|
|
244
|
+
issue = ConformanceIssue.new
|
|
245
|
+
issue.type = severity
|
|
246
|
+
issue.requirement_id = error.requirement_id
|
|
247
|
+
issue.message = error.message
|
|
248
|
+
issue.element = error.element_name
|
|
249
|
+
if severity == "error"
|
|
250
|
+
issue.attribute = error.data[:attribute] if error.respond_to?(:data) && error.data
|
|
251
|
+
issue.value = error.data[:value]&.to_s if error.respond_to?(:data) && error.data
|
|
252
|
+
end
|
|
253
|
+
issue.line = error.line if error.respond_to?(:line)
|
|
254
|
+
issue
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
private_class_method :project_canonical, :issue_from,
|
|
259
|
+
:project_validation_result
|
|
260
|
+
|
|
183
261
|
# Create report from svgcheck error output
|
|
184
262
|
def self.from_svgcheck_result(filename, error_content,
|
|
185
263
|
_output_content = nil)
|