svg_conform 0.2.4 → 0.2.7
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 +4 -1
- data/Gemfile +6 -3
- data/Gemfile.lock +211 -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 +4 -4
- 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 -4
- 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/external_checkers/svgcheck/parser.rb +1 -3
- 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/quality_metrics/configuration.rb +4 -2
- data/lib/svg_conform/references/base_reference.rb +153 -0
- data/lib/svg_conform/references/reference_manifest.rb +1 -2
- 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 +40 -9
- data/lib/svg_conform/validation/svg_1_1_document_conformance.rb +154 -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/yaml_io.rb +20 -0
- data/lib/svg_conform.rb +25 -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/fixtures/quality_metrics/aliases.yml +7 -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 +34 -0
- data/spec/svg_conform/profiles/metanorma_profile_spec.rb +48 -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/quality_metrics/configuration_spec.rb +82 -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/requirements/text_as_path_requirement_spec.rb +9 -5
- 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/spec/svg_conform/yaml_io_spec.rb +41 -0
- data/svg_conform.gemspec +4 -2
- metadata +120 -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,34 @@
|
|
|
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 "declares yeptris as a runtime dependency" do
|
|
18
|
+
dependency = specification.runtime_dependencies.find { |d| d.name == "yeptris" }
|
|
19
|
+
|
|
20
|
+
expect(dependency).not_to be_nil
|
|
21
|
+
expect(dependency.requirement).to eq(Gem::Requirement.new("~> 0.6.36"))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "loads the Psych-compatible yeptris API" do
|
|
25
|
+
require "yeptris/psych"
|
|
26
|
+
|
|
27
|
+
expect(defined?(Yeptris::Psych)).to be_truthy
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "exposes the lens_core availability probe" do
|
|
31
|
+
expect(SvgConform).to respond_to(:lens_core_available?)
|
|
32
|
+
expect(SvgConform.lens_core_available?).to be_truthy.or be_falsey
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
require "spec_helper"
|
|
4
4
|
|
|
5
|
+
# Container elements take the clip-path and mask presentation attributes
|
|
6
|
+
# per SVG (metanorma-pdfa#106).
|
|
7
|
+
METANORMA_PROFILE_CONTAINER_TAGS = %w[g a defs].freeze
|
|
8
|
+
METANORMA_PROFILE_CLIP_ATTRIBUTES = %w[clip-path mask].freeze
|
|
9
|
+
|
|
5
10
|
RSpec.describe "Metanorma Profile" do
|
|
6
11
|
let(:metanorma_profile_path) { "config/profiles/metanorma.yml" }
|
|
7
12
|
|
|
@@ -59,4 +64,47 @@ RSpec.describe "Metanorma Profile" do
|
|
|
59
64
|
expect(result).to respond_to(:valid?)
|
|
60
65
|
end
|
|
61
66
|
end
|
|
67
|
+
|
|
68
|
+
describe "clip-path and mask on container elements" do
|
|
69
|
+
# SVG applies the clip-path and mask properties to container elements and
|
|
70
|
+
# graphics elements alike; rejecting them on containers rejects
|
|
71
|
+
# schema-valid documents (metanorma-pdfa#106).
|
|
72
|
+
let(:profile) { SvgConform::Profile.load_from_file(metanorma_profile_path) }
|
|
73
|
+
let(:validator) { SvgConform::Validator.new(mode: :sax) }
|
|
74
|
+
|
|
75
|
+
def svg_with_clip_assets(inner)
|
|
76
|
+
<<~SVG
|
|
77
|
+
<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny" viewBox="0 0 100 100">
|
|
78
|
+
<defs>
|
|
79
|
+
<clipPath id="clip1">
|
|
80
|
+
<rect x="0" y="0" width="50" height="50"/>
|
|
81
|
+
</clipPath>
|
|
82
|
+
<mask id="mask1">
|
|
83
|
+
<rect x="0" y="0" width="50" height="50" fill="white"/>
|
|
84
|
+
</mask>
|
|
85
|
+
</defs>
|
|
86
|
+
#{inner}
|
|
87
|
+
</svg>
|
|
88
|
+
SVG
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
METANORMA_PROFILE_CONTAINER_TAGS.each do |tag|
|
|
92
|
+
METANORMA_PROFILE_CLIP_ATTRIBUTES.each do |attribute|
|
|
93
|
+
it "allows #{attribute} on #{tag}" do
|
|
94
|
+
svg = svg_with_clip_assets(
|
|
95
|
+
"<#{tag} #{attribute}=\"url(##{attribute == 'clip-path' ? 'clip1' : 'mask1'})\">" \
|
|
96
|
+
"<rect x=\"10\" y=\"10\" width=\"30\" height=\"30\" fill=\"red\"/>" \
|
|
97
|
+
"</#{tag}>",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
result = validator.validate(svg, profile: profile)
|
|
101
|
+
attribute_errors = result.errors.select do |e|
|
|
102
|
+
e.message.include?("'#{attribute}'")
|
|
103
|
+
end
|
|
104
|
+
expect(attribute_errors).to be_empty
|
|
105
|
+
expect(result).to be_valid
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
62
110
|
end
|