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
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="2.0" viewBox="0 0 10 10">
|
|
3
|
+
<image width="1" height="1" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxpbWFnZSBocmVmPSJkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUI0Yld4dWN6MGlhSFIwY0RvdkwzZDNkeTUzTXk1dmNtY3ZNakF3TUM5emRtY2lQanhwYldGblpTQm9jbVZtUFNKa1lYUmhPbWx0WVdkbEwzTjJaeXQ0Yld3N1ltRnpaVFkwTEZCSVRqSmFlVUkwWWxkNGRXTjZNR2xoU0ZJd1kwUnZka3d6WkROa2VUVXpUWGsxZG1OdFkzWk5ha0YzVFVNNWVtUnRZMmxRYW5od1lsZEdibHBUUW05amJWWnRVRk5LYTFsWVVtaFBiV3gwV1Zka2JFd3pUakphZVhRMFlsZDNOMWx0Um5wYVZGa3dURVpDU1ZScVNtRmxWVWt3V1d4a05HUlhUalpOUjJ4b1UwWkpkMWt3VW5aa2EzZDZXa1JPYTJWVVZYcFVXR3N4WkcxT2RGa3pXazVoYTBZelZGVk5OV1Z0VW5SWk1teFJZVzVvZDFsc1pFZGliSEJVVVcwNWFtSldXblJWUms1TFlURnNXVlZ0YUZCaVYzZ3dWMVprYTJKRmQzcFVha3BoWlZoUk1GbHNaRE5PTVd4MFVtNXdZVlpHYTNkVVJWcERVMVpTY1ZOdFJteFdWV3QzVjFkNGEwNUhVbGhVYWxwT1VqSjRiMVV3V2twa01XdDNWVzVhYTJFelpEWlhhMUpQWVRKV1ZWWlljRlZYUjNONFdrY3hUMlJHYTNwWGF6Vm9ZVEJaZWxaR1ZrNU9WMVowVlc1U1drMXRlRXRWTUZwM1pERmtjMkZIZEZKTmEydzJWMjV3YzFOdFJuSlNiVFZWVmxVMVExcFZWbk5TVmtwWVlrWkdhR0p0WnpGV01uaHJWREF4Um1KRmJHRlRSVXBvVlRCYVMyUnNWa2RVYTNCc1VsZDRkMVZYTURWWlZsbDVaVWhXV2xaWGFGTlVNVlp6WTFaS1dHSkZNV3hoYkVVd1ZrVlNUMVF3TVhOalJGcFBVa1JDY0ZSSWJ6QlBSWGQ2VkdwS1lXVnFVVGxKYVRnclVFTTVlbVJ0WXlzaUx6NDhMM04yWno0PSIvPjwvc3ZnPg=="/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="2.0" viewBox="0 0 10 10">
|
|
3
|
+
<image width="1" height="1" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48aW1hZ2UgeGxpbms6aHJlZj0iaHR0cDovL2V4YW1wbGUuY29tL2EucG5nIi8+PC9zdmc+"/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="2.0" viewBox="0 0 10 10">
|
|
3
|
+
<image width="1" height="1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC"/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="2.0" viewBox="0 0 10 10">
|
|
3
|
+
<image width="1" height="1" href="data:image/png,%89%50%4E%47%0D%0A%1A%0A%00%00%00%0D%49%48%44%52%00%00%00%01%00%00%00%01%08%02%00%00%00%90%77%53%DE%00%00%00%0C%49%44%41%54%78%9C%63%F8%CF%C0%00%00%03%01%01%00%C9%FE%92%EF%00%00%00%00%49%45%4E%44%AE%42%60%82"/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="2.0" viewBox="0 0 10 10">
|
|
3
|
+
<defs><linearGradient id="g"/><rect id="r" width="1" height="1"/></defs><use href="#r"/><use xlink:href="#r"/><rect width="1" height="1" fill="url(#g)" style="stroke:url(#g)"/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="2.0" viewBox="0 0 10 10">
|
|
3
|
+
<image width="1" height="1" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxIDEiPjxyZWN0IHdpZHRoPSIxIiBoZWlnaHQ9IjEiLz48L3N2Zz4="/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="2.0" viewBox="0 0 10 10">
|
|
3
|
+
<image width="1" height="1" href="data:image/svg+xml;charset=utf-8,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 1 1%22%3E%3Crect width=%221%22 height=%221%22/%3E%3C/svg%3E"/>
|
|
4
|
+
</svg>
|
|
@@ -29,7 +29,7 @@ RSpec.describe SvgConform::Commands::Check do
|
|
|
29
29
|
options = { profile: "svg_1_2_rfc", format: "table" }
|
|
30
30
|
command = described_class.new(tempfile.path, options)
|
|
31
31
|
|
|
32
|
-
expect { command.execute }.to output(/
|
|
32
|
+
expect { command.execute }.to output(/profile svg_1_2_rfc\nVALID|profile svg_1_2_rfc\nINVALID/).to_stdout
|
|
33
33
|
.and raise_error(SystemExit)
|
|
34
34
|
|
|
35
35
|
tempfile.unlink
|
|
@@ -43,7 +43,7 @@ RSpec.describe SvgConform::Commands::Check do
|
|
|
43
43
|
options = { profile: "svg_1_2_rfc", format: "table" }
|
|
44
44
|
command = described_class.new(tempfile.path, options)
|
|
45
45
|
|
|
46
|
-
expect { command.execute }.to output(/
|
|
46
|
+
expect { command.execute }.to output(/INVALID: .*\n\nerror /).to_stdout
|
|
47
47
|
.and raise_error(SystemExit)
|
|
48
48
|
|
|
49
49
|
tempfile.unlink
|
|
@@ -77,6 +77,26 @@ RSpec.describe SvgConform::Commands::Check do
|
|
|
77
77
|
tempfile.unlink
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
+
it "renders JSON from the canonical report, keeping the info diagnostic" do
|
|
81
|
+
fixture = File.expand_path("../../fixtures/diagnostics/external-reference-info.svg",
|
|
82
|
+
__dir__)
|
|
83
|
+
options = { profile: "svg_1_2_rfc", format: "json" }
|
|
84
|
+
command = described_class.new(fixture, options)
|
|
85
|
+
|
|
86
|
+
expect { command.execute }
|
|
87
|
+
.to output(/"infos": 1.*"requirement_id": "link_validation".*"severity": "info"|"severity": "info".*"requirement_id": "link_validation"/m)
|
|
88
|
+
.to_stdout.and raise_error(SystemExit) { |e| expect(e.status).to eq(0) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "renders the info diagnostic in the human (table) format" do
|
|
92
|
+
fixture = File.expand_path("../../fixtures/diagnostics/external-reference-info.svg",
|
|
93
|
+
__dir__)
|
|
94
|
+
command = described_class.new(fixture, { profile: "svg_1_2_rfc", format: "table" })
|
|
95
|
+
|
|
96
|
+
expect { command.execute }.to output(/info link_validation/).to_stdout
|
|
97
|
+
.and raise_error(SystemExit)
|
|
98
|
+
end
|
|
99
|
+
|
|
80
100
|
it "handles missing files gracefully" do
|
|
81
101
|
options = { profile: "svg_1_2_rfc", format: "table" }
|
|
82
102
|
command = described_class.new("nonexistent.svg", options)
|
|
@@ -16,5 +16,23 @@ RSpec.describe SvgConform::Commands::Profiles do
|
|
|
16
16
|
|
|
17
17
|
expect { command.execute }.to output(/Available SVG Profiles/).to_stdout
|
|
18
18
|
end
|
|
19
|
+
|
|
20
|
+
it "lists the SVG 1.1 Full static profile (name is truncated by the table)" do
|
|
21
|
+
command = described_class.new({})
|
|
22
|
+
|
|
23
|
+
expect { command.execute }.to output(/svg_1_1_full_.*SVG 1.1 Full/m).to_stdout
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "shows the svg_1_1_full_static requirements in verbose mode" do
|
|
27
|
+
command = described_class.new({ verbose: true })
|
|
28
|
+
|
|
29
|
+
expect { command.execute }
|
|
30
|
+
.to output(/Profile: svg_1_1_full_static.*Svg11DocumentRequirement/m).to_stdout
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "lists the opt-in svg_2_secure_static profile" do
|
|
34
|
+
expect { described_class.new({}).execute }
|
|
35
|
+
.to output(/svg_2_secure/).to_stdout
|
|
36
|
+
end
|
|
19
37
|
end
|
|
20
38
|
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe SvgConform::ConformanceReport do
|
|
6
|
+
let(:invalid_svg) { '<svg xmlns="http://www.w3.org/2000/svg"><rect width="5" height="5" fill="red"/></svg>' }
|
|
7
|
+
let(:result) { SvgConform::Validator.new.validate(invalid_svg, profile: :svg_1_2_rfc) }
|
|
8
|
+
|
|
9
|
+
describe ".from_svg_conform_result" do
|
|
10
|
+
it "does not include info diagnostics in the legacy channels" do
|
|
11
|
+
info = SvgConform::Validator.new.validate_file(
|
|
12
|
+
File.expand_path("../fixtures/diagnostics/external-reference-info.svg", __dir__),
|
|
13
|
+
profile: :svg_1_2_rfc,
|
|
14
|
+
)
|
|
15
|
+
report = described_class.from_svg_conform_result("i.svg", info)
|
|
16
|
+
|
|
17
|
+
expect(report.valid).to be(true)
|
|
18
|
+
expect(report.errors.total_count).to eq(0)
|
|
19
|
+
expect(report.warnings.total_count).to eq(0)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "still accepts use_svgcheck_mapping" do
|
|
23
|
+
report = described_class.from_svg_conform_result("t.svg", result, profile: :svg_1_2_rfc,
|
|
24
|
+
use_svgcheck_mapping: true)
|
|
25
|
+
|
|
26
|
+
expect(report).to be_a(described_class)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context "with lens_core installed" do
|
|
30
|
+
before do
|
|
31
|
+
unless SvgConform.lens_core_available?
|
|
32
|
+
skip "the optional lens_core gem is not installed"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "keeps its signature and legacy shape, projected from the canonical report" do
|
|
37
|
+
report = described_class.from_svg_conform_result("t.svg", result, profile: :svg_1_2_rfc)
|
|
38
|
+
canonical = result.to_lens_core_report
|
|
39
|
+
|
|
40
|
+
expect(report.tool).to eq("svg_conform")
|
|
41
|
+
expect(report.filename).to eq("t.svg")
|
|
42
|
+
expect(report.valid).to eq(canonical.valid)
|
|
43
|
+
expect(report.errors.total_count).to eq(canonical.summary["errors"])
|
|
44
|
+
expect(report.warnings.total_count).to eq(canonical.summary["warnings"])
|
|
45
|
+
expect(report.errors.issues.map(&:message))
|
|
46
|
+
.to eq(canonical.diagnostics.select(&:error?).map(&:message))
|
|
47
|
+
expect(report.errors.by_requirement.values.sum).to eq(report.errors.total_count)
|
|
48
|
+
expect(report.to_yaml).to include("errors:", "total_count:", "by_requirement:")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "does not mutate the canonical report" do
|
|
52
|
+
before = result.to_lens_core_report.to_h
|
|
53
|
+
described_class.from_svg_conform_result("t.svg", result, profile: :svg_1_2_rfc,
|
|
54
|
+
use_svgcheck_mapping: true)
|
|
55
|
+
|
|
56
|
+
expect(result.to_lens_core_report.to_h).to eq(before)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
context "without lens_core" do
|
|
61
|
+
before do
|
|
62
|
+
if SvgConform.lens_core_available?
|
|
63
|
+
skip "lens_core is installed; the direct projection is not in effect"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "projects the validation result directly" do
|
|
68
|
+
report = described_class.from_svg_conform_result("t.svg", result, profile: :svg_1_2_rfc)
|
|
69
|
+
|
|
70
|
+
expect(report.tool).to eq("svg_conform")
|
|
71
|
+
expect(report.filename).to eq("t.svg")
|
|
72
|
+
expect(report.valid).to be(false)
|
|
73
|
+
expect(report.errors.total_count).to eq(result.errors.length)
|
|
74
|
+
expect(report.errors.by_requirement.values.sum).to eq(report.errors.total_count)
|
|
75
|
+
expect(report.errors.issues.map(&:requirement_id).sort)
|
|
76
|
+
.to eq(result.errors.map(&:requirement_id).sort)
|
|
77
|
+
expect(report.to_yaml).to include("errors:", "total_count:", "by_requirement:")
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe ".from_svgcheck_result" do
|
|
83
|
+
it "remains callable and reports valid for empty output" do
|
|
84
|
+
report = described_class.from_svgcheck_result("t.svg", "")
|
|
85
|
+
|
|
86
|
+
expect(report.tool).to eq("svgcheck")
|
|
87
|
+
expect(report.valid).to be(true)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe SvgConform::Diagnostics::LensCoreAdapter do
|
|
6
|
+
let(:requirement) do
|
|
7
|
+
SvgConform::Requirements::ViewboxRequiredRequirement.new(
|
|
8
|
+
id: "viewbox_required", description: "d",
|
|
9
|
+
citation_uri: "https://example.com/spec#viewbox",
|
|
10
|
+
citation_label: "Spec viewBox"
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
let(:profile) do
|
|
14
|
+
SvgConform::Profile.new(
|
|
15
|
+
name: "demo", specification_uri: "https://example.com/spec",
|
|
16
|
+
specification_label: "Demo Spec", requirements: [requirement]
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
let(:node) { Struct.new(:line, :column, :name).new(3, 5, "svg") }
|
|
20
|
+
let(:context) { SvgConform::ValidationContext.new(nil, profile) }
|
|
21
|
+
let(:result) { SvgConform::ValidationResult.new(nil, profile, context) }
|
|
22
|
+
|
|
23
|
+
def add_all(data: {})
|
|
24
|
+
context.add_error(node: node, message: "bad error", requirement_id: "viewbox_required",
|
|
25
|
+
data: data)
|
|
26
|
+
context.add_error(node: node, message: "bad validity", requirement_id: "id_reference",
|
|
27
|
+
severity: :validity_error)
|
|
28
|
+
context.add_warning(rule: Struct.new(:id).new("warn_rule"), node: node, message: "a warning")
|
|
29
|
+
context.add_notice(rule: nil, node: node, message: "an info", requirement_id: "some_notice")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe ".call" do
|
|
33
|
+
it "maps all four channels to one schema-valid report in channel order" do
|
|
34
|
+
add_all
|
|
35
|
+
report = described_class.call(result, filename: "f.svg")
|
|
36
|
+
|
|
37
|
+
expect(report).to be_a(LensCore::Report)
|
|
38
|
+
expect(report.valid).to be(false)
|
|
39
|
+
expect(report.summary).to eq("errors" => 2, "warnings" => 1, "infos" => 1)
|
|
40
|
+
expect(report.diagnostics.map { |d| [d.severity, d.requirement_id] }).to eq(
|
|
41
|
+
[%w[error viewbox_required], %w[error id_reference],
|
|
42
|
+
%w[warning warn_rule], %w[info some_notice]],
|
|
43
|
+
)
|
|
44
|
+
expect(report.diagnostics[1].kind).to eq("validity")
|
|
45
|
+
expect(report.diagnostics[3].kind).to eq("notice")
|
|
46
|
+
expect(report.extensions["file"]).to eq("f.svg")
|
|
47
|
+
|
|
48
|
+
reparsed = LensCore::Parsers::Json.parse(LensCore::Renderers::Json.render(report))
|
|
49
|
+
expect(reparsed).to eq(report)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "keeps validity based on errors and validity errors only" do
|
|
53
|
+
context.add_warning(rule: "w", node: node, message: "w")
|
|
54
|
+
context.add_notice(rule: nil, node: node, message: "i", requirement_id: "x")
|
|
55
|
+
|
|
56
|
+
expect(described_class.call(result).valid).to be(true)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "carries location, remediation fields and explicit IDs" do
|
|
60
|
+
add_all
|
|
61
|
+
error = described_class.call(result).diagnostics.first
|
|
62
|
+
|
|
63
|
+
expect(error.location.line).to eq(3)
|
|
64
|
+
expect(error.location.column).to eq(5)
|
|
65
|
+
expect(error.remediation).to eq("Add missing viewBox attribute")
|
|
66
|
+
expect(error.context).to include("element" => "svg", "fixable" => false,
|
|
67
|
+
"remediation_type" => "add")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "maps source-level line and column data to an XML location" do
|
|
71
|
+
context.add_error(node: nil, message: "css", requirement_id: "viewbox_required",
|
|
72
|
+
data: { line: 7, column: 2 })
|
|
73
|
+
context.add_error(node: nil, message: "bad", requirement_id: "viewbox_required",
|
|
74
|
+
data: { line: 0, column: "x" })
|
|
75
|
+
context.add_error(node: node, message: "node", requirement_id: "viewbox_required",
|
|
76
|
+
data: { line: 99 })
|
|
77
|
+
first, second, third = described_class.call(result).diagnostics
|
|
78
|
+
|
|
79
|
+
expect(first.location.to_h).to include("line" => 7, "column" => 2)
|
|
80
|
+
expect(second.location).to be_nil
|
|
81
|
+
expect(third.location.line).to eq(3)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "turns profile and requirement citations into LensCore citations" do
|
|
85
|
+
add_all
|
|
86
|
+
report = described_class.call(result)
|
|
87
|
+
|
|
88
|
+
expect(report.profile.citations.map(&:to_h)).to eq(
|
|
89
|
+
[{ "uri" => "https://example.com/spec", "label" => "Demo Spec" }],
|
|
90
|
+
)
|
|
91
|
+
expect(report.diagnostics.first.citations.map(&:uri)).to eq(["https://example.com/spec#viewbox"])
|
|
92
|
+
expect(report.diagnostics[2].citations).to eq([])
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "only includes a timestamp when supplied" do
|
|
96
|
+
add_all
|
|
97
|
+
expect(described_class.call(result).timestamp).to be_nil
|
|
98
|
+
expect(described_class.call(result, timestamp: "2026-10-23T00:00:00Z").to_h["timestamp"])
|
|
99
|
+
.to eq("2026-10-23T00:00:00Z")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "normalizes symbols in issue data to JSON primitives" do
|
|
103
|
+
add_all(data: { attribute: :fill, nested: [1, { ok: true }] })
|
|
104
|
+
data = described_class.call(result).diagnostics.first.context["data"]
|
|
105
|
+
|
|
106
|
+
expect(data).to eq("attribute" => "fill", "nested" => [1, { "ok" => true }])
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "fails deterministically for JSON-unsafe issue data" do
|
|
110
|
+
add_all(data: { thing: Object.new })
|
|
111
|
+
|
|
112
|
+
expect { described_class.call(result) }
|
|
113
|
+
.to raise_error(SvgConform::Diagnostics::UnsupportedContextError,
|
|
114
|
+
/unsupported value of type Object at data\.thing/)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe "profile loading" do
|
|
119
|
+
it "keeps citations through YAML loading" do
|
|
120
|
+
loaded = SvgConform::Profile.from_yaml(<<~YAML)
|
|
121
|
+
name: yaml_demo
|
|
122
|
+
specification_uri: https://example.com/s
|
|
123
|
+
specification_label: S
|
|
124
|
+
requirements:
|
|
125
|
+
- id: viewbox_required
|
|
126
|
+
type: ViewboxRequiredRequirement
|
|
127
|
+
citation_uri: https://example.com/s#v
|
|
128
|
+
citation_label: V
|
|
129
|
+
YAML
|
|
130
|
+
|
|
131
|
+
expect(loaded.specification_uri).to eq("https://example.com/s")
|
|
132
|
+
expect(loaded.get_requirement("viewbox_required").citation_uri).to eq("https://example.com/s#v")
|
|
133
|
+
expect(loaded.get_requirement("viewbox_required").citation_label).to eq("V")
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "svg_conform.gemspec" do # rubocop:disable RSpec/DescribeClass
|
|
6
|
+
let(:specification) { Gem::Specification.load("svg_conform.gemspec") }
|
|
7
|
+
|
|
8
|
+
it "does not gate installation on the unpublished lens_core gem" do
|
|
9
|
+
# lens_core is optional: the canonical diagnostics renderer rides it
|
|
10
|
+
# when installed, and the plain structural serializers answer when
|
|
11
|
+
# not. A runtime dependency on the unpublished gem made 0.2.5
|
|
12
|
+
# uninstallable for every consumer.
|
|
13
|
+
expect(specification.runtime_dependencies.map(&:name)).not_to include("lens_core")
|
|
14
|
+
expect(File.read("lib/svg_conform.rb")).to include("rescue LoadError")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "exposes the lens_core availability probe" do
|
|
18
|
+
expect(SvgConform).to respond_to(:lens_core_available?)
|
|
19
|
+
expect(SvgConform.lens_core_available?).to be_truthy.or be_falsey
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "svg_conform/profile_compiler"
|
|
5
|
+
|
|
6
|
+
# Path is fixed by the card; the cop expects svg11_ instead of svg_1_1_.
|
|
7
|
+
RSpec.describe "svg_1_1_full_static profile" do # rubocop:disable RSpec/DescribeClass
|
|
8
|
+
let(:profile) { SvgConform::Profiles.get("svg_1_1_full_static") }
|
|
9
|
+
let(:fixtures) { File.expand_path("../../fixtures/profiles/svg_1_1_full_static", __dir__) }
|
|
10
|
+
let(:matrix) do
|
|
11
|
+
{
|
|
12
|
+
"svg11.document.xml" => "https://www.w3.org/TR/2011/REC-SVG11-20110816/conform.html#ConformingSVGDocuments",
|
|
13
|
+
"svg11.document.root" => "https://www.w3.org/TR/2011/REC-SVG11-20110816/struct.html#SVGElement",
|
|
14
|
+
"svg11.document.version" => "https://www.w3.org/TR/2011/REC-SVG11-20110816/struct.html#SVGElementVersionAttribute",
|
|
15
|
+
"svg11.document.grammar" => "https://www.w3.org/TR/2011/REC-SVG11-20110816/conform.html#ConformingSVGDocuments",
|
|
16
|
+
"svg11.document.css2" => "https://www.w3.org/TR/2008/REC-CSS2-20080411/grammar.html",
|
|
17
|
+
"svg11.document.xml_stylesheet" => "https://www.w3.org/TR/1999/REC-xml-stylesheet-19990629/",
|
|
18
|
+
"svg11.processing.script" => "https://www.w3.org/TR/2011/REC-SVG11-20110816/conform.html#ConformingSVGViewers",
|
|
19
|
+
"svg11.processing.animation" => "https://www.w3.org/TR/2011/REC-SVG11-20110816/conform.html#ConformingSVGViewers",
|
|
20
|
+
"svg11.processing.interaction" => "https://www.w3.org/TR/2011/REC-SVG11-20110816/conform.html#ConformingSVGViewers",
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
let(:invalid_cases) do
|
|
24
|
+
{
|
|
25
|
+
"malformed-xml" => "svg11.document.xml",
|
|
26
|
+
"wrong-namespace" => "svg11.document.root",
|
|
27
|
+
"wrong-version" => "svg11.document.version",
|
|
28
|
+
"dtd-content-model" => "svg11.document.grammar",
|
|
29
|
+
"dtd-attribute" => "svg11.document.grammar",
|
|
30
|
+
"css-grammar" => "svg11.document.css2",
|
|
31
|
+
"xml-stylesheet" => "svg11.document.xml_stylesheet",
|
|
32
|
+
"script" => "svg11.processing.script",
|
|
33
|
+
"animation" => "svg11.processing.animation",
|
|
34
|
+
"interaction" => "svg11.processing.interaction",
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def fixture(kind, name)
|
|
39
|
+
File.join(fixtures, kind, "#{name}.svg")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def sax_errors(path)
|
|
43
|
+
SvgConform::SaxDocument.from_file(path).validate_with_profile(profile).errors
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def dom_errors(path)
|
|
47
|
+
profile.validate(SvgConform::Document.from_file(path)).errors
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def ids(errors)
|
|
51
|
+
errors.map(&:requirement_id).uniq.sort - ["parse_error"]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "is listed, opt-in and does not change the default profile" do
|
|
55
|
+
expect(SvgConform::Profiles.available_profiles).to include(:svg_1_1_full_static)
|
|
56
|
+
expect(profile.name).to eq("svg_1_1_full_static")
|
|
57
|
+
expect(profile.specification_uri)
|
|
58
|
+
.to eq("https://www.w3.org/TR/2011/REC-SVG11-20110816/")
|
|
59
|
+
expect(SvgConform::Validator.new.method(:validate).parameters)
|
|
60
|
+
.to include(%i[key profile])
|
|
61
|
+
expect(SvgConform::Validator.instance_method(:validate_file)
|
|
62
|
+
.parameters).to include(%i[key profile])
|
|
63
|
+
expect(profile.remediations.to_a).to be_empty
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "covers the nine stable ids with their normative citations" do
|
|
67
|
+
declared = profile.requirements.flat_map do |req|
|
|
68
|
+
req.respond_to?(:categories) ? req.categories.map { |c| [c.id, c.citation] } : [[req.id, req.citation_uri]]
|
|
69
|
+
end
|
|
70
|
+
expect(declared.to_h).to eq(matrix)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "builds through the existing class map and compiler" do
|
|
74
|
+
expect(SvgConform::Profile::REQUIREMENTS_CLASS_MAP)
|
|
75
|
+
.to include("Svg11DocumentRequirement", "ProcessingFeaturesRequirement")
|
|
76
|
+
compiled = SvgConform::ProfileCompiler.compile("svg_1_1_full_static")
|
|
77
|
+
expect(compiled.requirements.map { |req| req.class.name.split("::").last }.uniq)
|
|
78
|
+
.to eq(%w[Svg11DocumentRequirement ProcessingFeaturesRequirement])
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
%w[minimal-full css xml-stylesheet foreign-metadata].each do |name|
|
|
82
|
+
it "accepts valid/#{name}.svg identically in DOM and SAX" do
|
|
83
|
+
path = fixture("valid", name)
|
|
84
|
+
expect(dom_errors(path)).to be_empty
|
|
85
|
+
expect(sax_errors(path)).to be_empty
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "rejects each invalid fixture with the expected id in DOM and SAX" do
|
|
90
|
+
invalid_cases.each do |name, id|
|
|
91
|
+
path = fixture("invalid", name)
|
|
92
|
+
sax = ids(sax_errors(path))
|
|
93
|
+
expect(sax).to include(id), name
|
|
94
|
+
if name == "malformed-xml"
|
|
95
|
+
# DOM cannot build a document from malformed XML at all.
|
|
96
|
+
expect { SvgConform::Document.from_file(path) }
|
|
97
|
+
.to raise_error(SvgConform::ParseError)
|
|
98
|
+
else
|
|
99
|
+
expect(ids(dom_errors(path))).to eq(sax), name
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "gives every error a normative citation, through issues and LensCore" do
|
|
105
|
+
invalid_cases.each_key do |name|
|
|
106
|
+
result = SvgConform::SaxDocument.from_file(fixture("invalid", name)).validate_with_profile(profile)
|
|
107
|
+
cited = result.errors.reject { |e| e.requirement_id == "parse_error" }
|
|
108
|
+
expect(cited).not_to be_empty
|
|
109
|
+
cited.each do |error|
|
|
110
|
+
expect(matrix.values).to include(error.data[:citation]), "#{name} #{error.requirement_id}"
|
|
111
|
+
end
|
|
112
|
+
report = SvgConform::Diagnostics::LensCoreAdapter.call(result)
|
|
113
|
+
report.diagnostics.each do |diag|
|
|
114
|
+
next if diag.requirement_id == "parse_error"
|
|
115
|
+
|
|
116
|
+
expect(diag.citations.map(&:uri) & matrix.values).not_to be_empty
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "validates a file input and a string input identically" do
|
|
122
|
+
path = fixture("invalid", "wrong-version")
|
|
123
|
+
from_string = SvgConform::SaxDocument.from_content(File.read(path))
|
|
124
|
+
.validate_with_profile(profile)
|
|
125
|
+
expect(ids(from_string.errors)).to eq(ids(sax_errors(path)))
|
|
126
|
+
dom_string = profile.validate(SvgConform::Document.from_content(File.read(path)))
|
|
127
|
+
expect(ids(dom_string.errors)).to eq(ids(from_string.errors))
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "does not claim Tiny or Basic and offers no remediation" do
|
|
131
|
+
expect(profile.description).not_to match(/tiny|basic/i)
|
|
132
|
+
expect(profile.remediation_count).to eq(0)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "runs through the CLI check command and prints a JSON report" do
|
|
136
|
+
command = SvgConform::Commands::Check.new(
|
|
137
|
+
fixture("valid", "minimal-full"),
|
|
138
|
+
{ profile: "svg_1_1_full_static", format: "json" },
|
|
139
|
+
)
|
|
140
|
+
expect do
|
|
141
|
+
expect { command.execute }.to raise_error(SystemExit) do |error|
|
|
142
|
+
expect(error.status).to eq(0)
|
|
143
|
+
end
|
|
144
|
+
end.to output(/"id": "svg_1_1_full_static".*"valid": true/m).to_stdout
|
|
145
|
+
end
|
|
146
|
+
end
|