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.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +1 -0
  3. data/.github/workflows/rake.yml +4 -1
  4. data/Gemfile +6 -3
  5. data/Gemfile.lock +211 -0
  6. data/TODO.issue-100/00-overview.md +135 -0
  7. data/TODO.issue-100/01-build-lens-core-v1.md +92 -0
  8. data/TODO.issue-100/02-adopt-lens-core-in-svg.md +123 -0
  9. data/TODO.issue-100/03-ship-svg-1-1-full-static.md +138 -0
  10. data/TODO.issue-100/04-ship-svg-2-secure-static.md +131 -0
  11. data/TODO.issue-100/05-freeze-candidate-and-handoff.md +108 -0
  12. data/TODO.issue-100/90-progress.md +65 -0
  13. data/config/profiles/metanorma.yml +4 -4
  14. data/config/profiles/svg_1_1_full_static.yml +69 -0
  15. data/config/profiles/svg_2_secure_static.yml +55 -0
  16. data/config/requirements/processing_features_static.yml +29 -0
  17. data/data/svg_1_1/manifest.yml +7 -0
  18. data/data/svg_1_1/svg11-flat-20110816.dtd +5664 -0
  19. data/docs/api_reference.adoc +19 -1
  20. data/docs/cli_guide.adoc +35 -45
  21. data/docs/diagnostics.adoc +73 -0
  22. data/docs/lens_core_handoff.adoc +84 -0
  23. data/docs/profiles.adoc +94 -1
  24. data/lib/svg_conform/cli.rb +3 -4
  25. data/lib/svg_conform/commands/check.rb +11 -60
  26. data/lib/svg_conform/conformance_report.rb +79 -1
  27. data/lib/svg_conform/diagnostics/lens_core_adapter.rb +200 -0
  28. data/lib/svg_conform/document.rb +12 -1
  29. data/lib/svg_conform/external_checkers/svgcheck/parser.rb +1 -3
  30. data/lib/svg_conform/node_helpers.rb +1 -1
  31. data/lib/svg_conform/profile.rb +11 -2
  32. data/lib/svg_conform/quality_metrics/complexity_calculator.rb +3 -3
  33. data/lib/svg_conform/quality_metrics/configuration.rb +4 -2
  34. data/lib/svg_conform/references/base_reference.rb +153 -0
  35. data/lib/svg_conform/references/reference_manifest.rb +1 -2
  36. data/lib/svg_conform/report_comparator.rb +4 -4
  37. data/lib/svg_conform/requirements/base_requirement.rb +6 -0
  38. data/lib/svg_conform/requirements/external_references_requirement.rb +234 -0
  39. data/lib/svg_conform/requirements/link_validation_requirement.rb +2 -0
  40. data/lib/svg_conform/requirements/processing_features_requirement.rb +175 -0
  41. data/lib/svg_conform/requirements/svg_1_1_document_requirement.rb +213 -0
  42. data/lib/svg_conform/requirements.rb +6 -0
  43. data/lib/svg_conform/sax_document.rb +8 -4
  44. data/lib/svg_conform/sax_validation_handler.rb +22 -1
  45. data/lib/svg_conform/validation/css2_core_grammar.rb +359 -0
  46. data/lib/svg_conform/validation/css_references.rb +122 -0
  47. data/lib/svg_conform/validation/error_tracker.rb +40 -9
  48. data/lib/svg_conform/validation/svg_1_1_document_conformance.rb +154 -0
  49. data/lib/svg_conform/validation_context.rb +25 -5
  50. data/lib/svg_conform/validation_result.rb +91 -16
  51. data/lib/svg_conform/version.rb +1 -1
  52. data/lib/svg_conform/yaml_io.rb +20 -0
  53. data/lib/svg_conform.rb +25 -0
  54. data/spec/fixtures/diagnostics/external-reference-info.svg +6 -0
  55. data/spec/fixtures/plantuml/README.adoc +11 -0
  56. data/spec/fixtures/plantuml/unit-type.svg +1 -0
  57. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/animation.svg +4 -0
  58. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/css-grammar.svg +4 -0
  59. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/dtd-attribute.svg +4 -0
  60. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/dtd-content-model.svg +4 -0
  61. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/interaction.svg +4 -0
  62. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/malformed-xml.svg +4 -0
  63. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/script.svg +5 -0
  64. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/wrong-namespace.svg +4 -0
  65. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/wrong-version.svg +4 -0
  66. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/xml-stylesheet.svg +5 -0
  67. data/spec/fixtures/profiles/svg_1_1_full_static/valid/css.svg +5 -0
  68. data/spec/fixtures/profiles/svg_1_1_full_static/valid/foreign-metadata.svg +6 -0
  69. data/spec/fixtures/profiles/svg_1_1_full_static/valid/minimal-full.svg +5 -0
  70. data/spec/fixtures/profiles/svg_1_1_full_static/valid/xml-stylesheet.svg +5 -0
  71. data/spec/fixtures/profiles/svg_2_secure_static/invalid/animation.svg +4 -0
  72. data/spec/fixtures/profiles/svg_2_secure_static/invalid/css-import.svg +4 -0
  73. data/spec/fixtures/profiles/svg_2_secure_static/invalid/css-url.svg +4 -0
  74. data/spec/fixtures/profiles/svg_2_secure_static/invalid/data-bad-png.svg +4 -0
  75. data/spec/fixtures/profiles/svg_2_secure_static/invalid/data-empty-png.svg +4 -0
  76. data/spec/fixtures/profiles/svg_2_secure_static/invalid/data-malformed-base64.svg +4 -0
  77. data/spec/fixtures/profiles/svg_2_secure_static/invalid/data-malformed-percent.svg +4 -0
  78. data/spec/fixtures/profiles/svg_2_secure_static/invalid/data-unsupported-type.svg +4 -0
  79. data/spec/fixtures/profiles/svg_2_secure_static/invalid/file-like-fragment.svg +4 -0
  80. data/spec/fixtures/profiles/svg_2_secure_static/invalid/http.svg +4 -0
  81. data/spec/fixtures/profiles/svg_2_secure_static/invalid/https.svg +4 -0
  82. data/spec/fixtures/profiles/svg_2_secure_static/invalid/interaction.svg +4 -0
  83. data/spec/fixtures/profiles/svg_2_secure_static/invalid/protocol-relative.svg +4 -0
  84. data/spec/fixtures/profiles/svg_2_secure_static/invalid/relative-file-fragment.svg +4 -0
  85. data/spec/fixtures/profiles/svg_2_secure_static/invalid/relative-file.svg +4 -0
  86. data/spec/fixtures/profiles/svg_2_secure_static/invalid/script.svg +4 -0
  87. data/spec/fixtures/profiles/svg_2_secure_static/invalid/svg-data-excessive-nesting.svg +4 -0
  88. data/spec/fixtures/profiles/svg_2_secure_static/invalid/svg-data-malformed-xml.svg +4 -0
  89. data/spec/fixtures/profiles/svg_2_secure_static/invalid/svg-data-nested-external.svg +4 -0
  90. data/spec/fixtures/profiles/svg_2_secure_static/invalid/urn.svg +4 -0
  91. data/spec/fixtures/profiles/svg_2_secure_static/invalid/xml-stylesheet.svg +5 -0
  92. data/spec/fixtures/profiles/svg_2_secure_static/valid/jpeg-base64.svg +4 -0
  93. data/spec/fixtures/profiles/svg_2_secure_static/valid/jpeg-percent.svg +4 -0
  94. data/spec/fixtures/profiles/svg_2_secure_static/valid/png-base64.svg +4 -0
  95. data/spec/fixtures/profiles/svg_2_secure_static/valid/png-percent.svg +4 -0
  96. data/spec/fixtures/profiles/svg_2_secure_static/valid/same-document-fragment.svg +4 -0
  97. data/spec/fixtures/profiles/svg_2_secure_static/valid/svg-base64.svg +4 -0
  98. data/spec/fixtures/profiles/svg_2_secure_static/valid/svg-percent.svg +4 -0
  99. data/spec/fixtures/quality_metrics/aliases.yml +7 -0
  100. data/spec/svg_conform/commands/check_command_spec.rb +22 -2
  101. data/spec/svg_conform/commands/profiles_command_spec.rb +18 -0
  102. data/spec/svg_conform/conformance_report_spec.rb +90 -0
  103. data/spec/svg_conform/diagnostics/lens_core_adapter_spec.rb +136 -0
  104. data/spec/svg_conform/gemspec_spec.rb +34 -0
  105. data/spec/svg_conform/profiles/metanorma_profile_spec.rb +48 -0
  106. data/spec/svg_conform/profiles/svg_1_1_full_static_profile_spec.rb +146 -0
  107. data/spec/svg_conform/profiles/svg_2_secure_static_profile_spec.rb +150 -0
  108. data/spec/svg_conform/profiles_plantuml_spec.rb +81 -0
  109. data/spec/svg_conform/quality_metrics/configuration_spec.rb +82 -0
  110. data/spec/svg_conform/references/data_uri_reference_spec.rb +97 -0
  111. data/spec/svg_conform/references/integration_spec.rb +30 -0
  112. data/spec/svg_conform/references/reference_classifier_spec.rb +17 -0
  113. data/spec/svg_conform/requirements/external_references_requirement_spec.rb +109 -0
  114. data/spec/svg_conform/requirements/link_validation_requirement_spec.rb +16 -0
  115. data/spec/svg_conform/requirements/processing_features_requirement_spec.rb +75 -0
  116. data/spec/svg_conform/requirements/svg_1_1_document_requirement_spec.rb +141 -0
  117. data/spec/svg_conform/requirements/text_as_path_requirement_spec.rb +9 -5
  118. data/spec/svg_conform/validation/css2_core_grammar_spec.rb +116 -0
  119. data/spec/svg_conform/validation/svg_1_1_document_conformance_spec.rb +144 -0
  120. data/spec/svg_conform/validation_context_source_plumbing_spec.rb +122 -0
  121. data/spec/svg_conform/validation_context_spec.rb +39 -0
  122. data/spec/svg_conform/validation_result_spec.rb +123 -0
  123. data/spec/svg_conform/yaml_io_spec.rb +41 -0
  124. data/svg_conform.gemspec +4 -2
  125. 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/png;base64,bm90IGEgcG5n"/>
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,"/>
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,%%%not-base64"/>
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,%GZ"/>
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:text/html;base64,PHA+eDwvcD4="/>
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
+ <use href="file:///etc/passwd#id"/>
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="http://example.com/a.png"/>
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="https://example.com/a.png"/>
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
+ <rect width="1" height="1" onclick="f()"/>
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="//example.com/a.png"/>
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
+ <use href="other.svg#id"/>
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="images/a.png"/>
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
+ <script>alert(1)</script>
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,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,PHN2Zz48Zz48L3N2Zz4="/>
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="urn:example:a"/>
4
+ </svg>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0"?>
2
+ <?xml-stylesheet type="text/css" href="other.css"?>
3
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="2.0" viewBox="0 0 10 10">
4
+ <rect width="1" height="1"/>
5
+ </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/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2Q=="/>
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/jpeg,%FF%D8%FF%E0%00%10%4A%46%49%46%00%01%01%00%00%01%00%01%00%00%FF%D9"/>
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>
@@ -0,0 +1,7 @@
1
+ remediable_types: &violation_types
2
+ - :reference_violation
3
+ severity_levels:
4
+ critical:
5
+ violation_types: *violation_types
6
+ default_weight: 5
7
+ max_penalty: 20
@@ -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(/SVG Validation Report/).to_stdout
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(/Validation Errors/).to_stdout
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