svg_conform 0.1.0

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 (335) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rake.yml +15 -0
  3. data/.github/workflows/release.yml +23 -0
  4. data/.github/workflows/svgcheck-compatibility.yml +135 -0
  5. data/.gitignore +12 -0
  6. data/.rspec +3 -0
  7. data/.rubocop.yml +13 -0
  8. data/.rubocop_todo.yml +183 -0
  9. data/CODE_OF_CONDUCT.md +132 -0
  10. data/Gemfile +14 -0
  11. data/README.adoc +1384 -0
  12. data/Rakefile +15 -0
  13. data/config/profiles/base.yml +34 -0
  14. data/config/profiles/lucid_fix.yml +37 -0
  15. data/config/profiles/metanorma.yml +212 -0
  16. data/config/profiles/no_external_css.yml +20 -0
  17. data/config/profiles/svg_1_2_rfc.yml +284 -0
  18. data/config/profiles/svg_1_2_rfc_with_rdf.yml +145 -0
  19. data/config/svgcheck_mapping.yml +180 -0
  20. data/docs/profiles.adoc +547 -0
  21. data/docs/rdf_metadata_support.adoc +212 -0
  22. data/docs/remediation.adoc +732 -0
  23. data/docs/requirements.adoc +709 -0
  24. data/examples/demo.rb +116 -0
  25. data/examples/requirements_demo.rb +240 -0
  26. data/exe/svg_conform +7 -0
  27. data/lib/svg_conform/batch_report.rb +70 -0
  28. data/lib/svg_conform/cli.rb +107 -0
  29. data/lib/svg_conform/commands/check.rb +390 -0
  30. data/lib/svg_conform/commands/profiles.rb +118 -0
  31. data/lib/svg_conform/commands/svgcheck.rb +90 -0
  32. data/lib/svg_conform/commands/svgcheck_compare.rb +92 -0
  33. data/lib/svg_conform/commands/svgcheck_compatibility.rb +43 -0
  34. data/lib/svg_conform/commands/svgcheck_generate.rb +262 -0
  35. data/lib/svg_conform/compatibility/analysis_context.rb +68 -0
  36. data/lib/svg_conform/compatibility/comparison_result.rb +147 -0
  37. data/lib/svg_conform/compatibility/compatibility_analyzer.rb +85 -0
  38. data/lib/svg_conform/compatibility/file_processor.rb +109 -0
  39. data/lib/svg_conform/compatibility/pattern_discovery.rb +319 -0
  40. data/lib/svg_conform/compatibility/report_formatter.rb +359 -0
  41. data/lib/svg_conform/compatibility/svg_analysis_engine.rb +316 -0
  42. data/lib/svg_conform/compatibility/validity_analysis.rb +252 -0
  43. data/lib/svg_conform/compatibility/xml_analysis_engine.rb +198 -0
  44. data/lib/svg_conform/compatibility_analyzer.rb +285 -0
  45. data/lib/svg_conform/conformance_report.rb +267 -0
  46. data/lib/svg_conform/constants.rb +199 -0
  47. data/lib/svg_conform/css_color.rb +262 -0
  48. data/lib/svg_conform/document.rb +203 -0
  49. data/lib/svg_conform/external_checkers/svgcheck/compatibility_engine.rb +166 -0
  50. data/lib/svg_conform/external_checkers/svgcheck/output_generator.rb +101 -0
  51. data/lib/svg_conform/external_checkers/svgcheck/parser.rb +200 -0
  52. data/lib/svg_conform/external_checkers/svgcheck/report_comparator.rb +175 -0
  53. data/lib/svg_conform/external_checkers/svgcheck/report_generator.rb +82 -0
  54. data/lib/svg_conform/external_checkers/svgcheck/validation_pipeline.rb +249 -0
  55. data/lib/svg_conform/external_checkers/svgcheck.rb +56 -0
  56. data/lib/svg_conform/external_checkers.rb +34 -0
  57. data/lib/svg_conform/fixer.rb +56 -0
  58. data/lib/svg_conform/profile.rb +164 -0
  59. data/lib/svg_conform/profiles.rb +60 -0
  60. data/lib/svg_conform/remediation_engine.rb +92 -0
  61. data/lib/svg_conform/remediation_result.rb +36 -0
  62. data/lib/svg_conform/remediation_runner.rb +225 -0
  63. data/lib/svg_conform/remediations/base_remediation.rb +165 -0
  64. data/lib/svg_conform/remediations/color_remediation.rb +226 -0
  65. data/lib/svg_conform/remediations/font_embedding_remediation.rb +145 -0
  66. data/lib/svg_conform/remediations/font_remediation.rb +122 -0
  67. data/lib/svg_conform/remediations/image_embedding_remediation.rb +154 -0
  68. data/lib/svg_conform/remediations/invalid_id_references_remediation.rb +129 -0
  69. data/lib/svg_conform/remediations/namespace_attribute_remediation.rb +244 -0
  70. data/lib/svg_conform/remediations/namespace_remediation.rb +151 -0
  71. data/lib/svg_conform/remediations/no_external_css_remediation.rb +192 -0
  72. data/lib/svg_conform/remediations/style_promotion_remediation.rb +93 -0
  73. data/lib/svg_conform/remediations/viewbox_remediation.rb +127 -0
  74. data/lib/svg_conform/remediations.rb +40 -0
  75. data/lib/svg_conform/report_comparator.rb +772 -0
  76. data/lib/svg_conform/requirements/allowed_elements_requirement.rb +367 -0
  77. data/lib/svg_conform/requirements/base_requirement.rb +98 -0
  78. data/lib/svg_conform/requirements/color_restrictions_requirement.rb +126 -0
  79. data/lib/svg_conform/requirements/element_requirement_config.rb +75 -0
  80. data/lib/svg_conform/requirements/font_family_requirement.rb +133 -0
  81. data/lib/svg_conform/requirements/forbidden_content_requirement.rb +60 -0
  82. data/lib/svg_conform/requirements/id_reference_requirement.rb +133 -0
  83. data/lib/svg_conform/requirements/invalid_id_references_requirement.rb +132 -0
  84. data/lib/svg_conform/requirements/link_validation_requirement.rb +55 -0
  85. data/lib/svg_conform/requirements/namespace_attributes_requirement.rb +211 -0
  86. data/lib/svg_conform/requirements/namespace_requirement.rb +294 -0
  87. data/lib/svg_conform/requirements/no_external_css_requirement.rb +132 -0
  88. data/lib/svg_conform/requirements/no_external_fonts_requirement.rb +121 -0
  89. data/lib/svg_conform/requirements/no_external_images_requirement.rb +91 -0
  90. data/lib/svg_conform/requirements/style_promotion_requirement.rb +72 -0
  91. data/lib/svg_conform/requirements/style_requirement.rb +226 -0
  92. data/lib/svg_conform/requirements/viewbox_required_requirement.rb +96 -0
  93. data/lib/svg_conform/requirements.rb +49 -0
  94. data/lib/svg_conform/semantic_comparator.rb +829 -0
  95. data/lib/svg_conform/validation_context.rb +408 -0
  96. data/lib/svg_conform/validation_result.rb +146 -0
  97. data/lib/svg_conform/validator.rb +91 -0
  98. data/lib/svg_conform/version.rb +5 -0
  99. data/lib/svg_conform.rb +68 -0
  100. data/lib/tasks/fixtures.rake +321 -0
  101. data/lib/tasks/svgcheck.rake +111 -0
  102. data/reference-docs/SVG-1.2-RFC.rnc.txt +1676 -0
  103. data/reference-docs/Scalable Vector Graphics (SVG) 1.1 (Second Edition).html +40764 -0
  104. data/reference-docs/Scalable Vector Graphics (SVG) Tiny 1.2 Specification.html +44591 -0
  105. data/reference-docs/rfc7996.txt +2971 -0
  106. data/sig/svg_conform.rbs +4 -0
  107. data/spec/fixtures/allowed_elements/inputs/basic_violations.svg +21 -0
  108. data/spec/fixtures/allowed_elements/repair/basic_violations.svg +18 -0
  109. data/spec/fixtures/color_restrictions/inputs/basic_violations.svg +23 -0
  110. data/spec/fixtures/color_restrictions/repair/basic_violations.svg +23 -0
  111. data/spec/fixtures/comprehensive/inputs/multiple_violations.svg +21 -0
  112. data/spec/fixtures/comprehensive/repair/multiple_violations.svg +16 -0
  113. data/spec/fixtures/font_family/inputs/basic_violations.svg +17 -0
  114. data/spec/fixtures/font_family/repair/basic_violations.svg +17 -0
  115. data/spec/fixtures/forbidden_content/inputs/basic_violations.svg +27 -0
  116. data/spec/fixtures/forbidden_content/repair/basic_violations.svg +19 -0
  117. data/spec/fixtures/id_reference/inputs/basic_violations.svg +17 -0
  118. data/spec/fixtures/id_reference/repair/basic_violations.svg +15 -0
  119. data/spec/fixtures/link_validation/inputs/basic_violations.svg +20 -0
  120. data/spec/fixtures/link_validation/repair/basic_violations.svg +16 -0
  121. data/spec/fixtures/lucid/inputs/simple.svg +67 -0
  122. data/spec/fixtures/lucid/repair/simple.svg +63 -0
  123. data/spec/fixtures/namespace/inputs/basic_violations.svg +20 -0
  124. data/spec/fixtures/namespace/repair/basic_violations.svg +17 -0
  125. data/spec/fixtures/namespace_attributes/inputs/basic_violations.svg +16 -0
  126. data/spec/fixtures/namespace_attributes/repair/basic_violations.svg +15 -0
  127. data/spec/fixtures/no_external_css/inputs/basic_violations.svg +20 -0
  128. data/spec/fixtures/no_external_css/repair/basic_violations.svg +18 -0
  129. data/spec/fixtures/style/inputs/basic_violations.svg +22 -0
  130. data/spec/fixtures/style/repair/basic_violations.svg +22 -0
  131. data/spec/fixtures/style_promotion/inputs/basic_test.svg +15 -0
  132. data/spec/fixtures/style_promotion/repair/basic_test.svg +15 -0
  133. data/spec/fixtures/svg_1_2_rfc/inputs/allowed_elements_violations.svg +18 -0
  134. data/spec/fixtures/svg_1_2_rfc/inputs/color_restrictions_violations.svg +23 -0
  135. data/spec/fixtures/svgcheck/check/DrawBerry-sample-2.svg.code +1 -0
  136. data/spec/fixtures/svgcheck/check/DrawBerry-sample-2.svg.err +0 -0
  137. data/spec/fixtures/svgcheck/check/DrawBerry-sample-2.svg.out +23 -0
  138. data/spec/fixtures/svgcheck/check/IETF-test.svg.code +1 -0
  139. data/spec/fixtures/svgcheck/check/IETF-test.svg.err +0 -0
  140. data/spec/fixtures/svgcheck/check/IETF-test.svg.out +20 -0
  141. data/spec/fixtures/svgcheck/check/circle.svg.code +1 -0
  142. data/spec/fixtures/svgcheck/check/circle.svg.err +0 -0
  143. data/spec/fixtures/svgcheck/check/circle.svg.out +2 -0
  144. data/spec/fixtures/svgcheck/check/colors.svg.code +1 -0
  145. data/spec/fixtures/svgcheck/check/colors.svg.err +0 -0
  146. data/spec/fixtures/svgcheck/check/colors.svg.out +13 -0
  147. data/spec/fixtures/svgcheck/check/dia-sample-svg.svg.code +1 -0
  148. data/spec/fixtures/svgcheck/check/dia-sample-svg.svg.err +0 -0
  149. data/spec/fixtures/svgcheck/check/dia-sample-svg.svg.out +76 -0
  150. data/spec/fixtures/svgcheck/check/example-dot.svg.code +1 -0
  151. data/spec/fixtures/svgcheck/check/example-dot.svg.err +0 -0
  152. data/spec/fixtures/svgcheck/check/example-dot.svg.out +11 -0
  153. data/spec/fixtures/svgcheck/check/full-tiny.svg.code +1 -0
  154. data/spec/fixtures/svgcheck/check/full-tiny.svg.err +0 -0
  155. data/spec/fixtures/svgcheck/check/full-tiny.svg.out +5835 -0
  156. data/spec/fixtures/svgcheck/check/good.svg.code +1 -0
  157. data/spec/fixtures/svgcheck/check/good.svg.err +0 -0
  158. data/spec/fixtures/svgcheck/check/good.svg.out +1 -0
  159. data/spec/fixtures/svgcheck/check/httpbis-proxy20-fig6.svg.code +1 -0
  160. data/spec/fixtures/svgcheck/check/httpbis-proxy20-fig6.svg.err +0 -0
  161. data/spec/fixtures/svgcheck/check/httpbis-proxy20-fig6.svg.out +5 -0
  162. data/spec/fixtures/svgcheck/check/malformed.svg.code +1 -0
  163. data/spec/fixtures/svgcheck/check/malformed.svg.err +0 -0
  164. data/spec/fixtures/svgcheck/check/malformed.svg.out +8 -0
  165. data/spec/fixtures/svgcheck/check/rfc-svg.svg.code +1 -0
  166. data/spec/fixtures/svgcheck/check/rfc-svg.svg.err +0 -0
  167. data/spec/fixtures/svgcheck/check/rfc-svg.svg.out +1 -0
  168. data/spec/fixtures/svgcheck/check/rfc.xml.code +1 -0
  169. data/spec/fixtures/svgcheck/check/rfc.xml.err +0 -0
  170. data/spec/fixtures/svgcheck/check/rfc.xml.out +2 -0
  171. data/spec/fixtures/svgcheck/check/rgb.svg.code +1 -0
  172. data/spec/fixtures/svgcheck/check/rgb.svg.err +0 -0
  173. data/spec/fixtures/svgcheck/check/rgb.svg.out +9 -0
  174. data/spec/fixtures/svgcheck/check/svg-wordle.svg.code +1 -0
  175. data/spec/fixtures/svgcheck/check/svg-wordle.svg.err +0 -0
  176. data/spec/fixtures/svgcheck/check/svg-wordle.svg.out +508 -0
  177. data/spec/fixtures/svgcheck/check/threshold.svg.code +1 -0
  178. data/spec/fixtures/svgcheck/check/threshold.svg.err +0 -0
  179. data/spec/fixtures/svgcheck/check/threshold.svg.out +20 -0
  180. data/spec/fixtures/svgcheck/check/utf8.svg.code +1 -0
  181. data/spec/fixtures/svgcheck/check/utf8.svg.err +0 -0
  182. data/spec/fixtures/svgcheck/check/utf8.svg.out +162 -0
  183. data/spec/fixtures/svgcheck/check/viewBox-both.svg.code +1 -0
  184. data/spec/fixtures/svgcheck/check/viewBox-both.svg.err +0 -0
  185. data/spec/fixtures/svgcheck/check/viewBox-both.svg.out +4 -0
  186. data/spec/fixtures/svgcheck/check/viewBox-height.svg.code +1 -0
  187. data/spec/fixtures/svgcheck/check/viewBox-height.svg.err +0 -0
  188. data/spec/fixtures/svgcheck/check/viewBox-height.svg.out +3 -0
  189. data/spec/fixtures/svgcheck/check/viewBox-none.svg.code +1 -0
  190. data/spec/fixtures/svgcheck/check/viewBox-none.svg.err +0 -0
  191. data/spec/fixtures/svgcheck/check/viewBox-none.svg.out +3 -0
  192. data/spec/fixtures/svgcheck/check/viewBox-width.svg.code +1 -0
  193. data/spec/fixtures/svgcheck/check/viewBox-width.svg.err +0 -0
  194. data/spec/fixtures/svgcheck/check/viewBox-width.svg.out +3 -0
  195. data/spec/fixtures/svgcheck/inputs/DrawBerry-sample-2.svg +28 -0
  196. data/spec/fixtures/svgcheck/inputs/IETF-test.svg +28 -0
  197. data/spec/fixtures/svgcheck/inputs/circle.svg +3 -0
  198. data/spec/fixtures/svgcheck/inputs/colors.svg +18 -0
  199. data/spec/fixtures/svgcheck/inputs/dia-sample-svg.svg +47 -0
  200. data/spec/fixtures/svgcheck/inputs/example-dot.svg +75 -0
  201. data/spec/fixtures/svgcheck/inputs/full-tiny.svg +16194 -0
  202. data/spec/fixtures/svgcheck/inputs/good.svg +19 -0
  203. data/spec/fixtures/svgcheck/inputs/httpbis-proxy20-fig6.svg +2 -0
  204. data/spec/fixtures/svgcheck/inputs/malformed.svg +11 -0
  205. data/spec/fixtures/svgcheck/inputs/rfc-svg.svg +1028 -0
  206. data/spec/fixtures/svgcheck/inputs/rfc.xml +37 -0
  207. data/spec/fixtures/svgcheck/inputs/rgb.svg +9 -0
  208. data/spec/fixtures/svgcheck/inputs/svg-wordle.svg +330 -0
  209. data/spec/fixtures/svgcheck/inputs/threshold.svg +26 -0
  210. data/spec/fixtures/svgcheck/inputs/utf8.svg +448 -0
  211. data/spec/fixtures/svgcheck/inputs/viewBox-both.svg +3 -0
  212. data/spec/fixtures/svgcheck/inputs/viewBox-height.svg +3 -0
  213. data/spec/fixtures/svgcheck/inputs/viewBox-none.svg +3 -0
  214. data/spec/fixtures/svgcheck/inputs/viewBox-width.svg +3 -0
  215. data/spec/fixtures/svgcheck/repair/DrawBerry-sample-2.svg.code +1 -0
  216. data/spec/fixtures/svgcheck/repair/DrawBerry-sample-2.svg.err +0 -0
  217. data/spec/fixtures/svgcheck/repair/DrawBerry-sample-2.svg.file +0 -0
  218. data/spec/fixtures/svgcheck/repair/DrawBerry-sample-2.svg.out +23 -0
  219. data/spec/fixtures/svgcheck/repair/IETF-test.svg.code +1 -0
  220. data/spec/fixtures/svgcheck/repair/IETF-test.svg.err +0 -0
  221. data/spec/fixtures/svgcheck/repair/IETF-test.svg.file +29 -0
  222. data/spec/fixtures/svgcheck/repair/IETF-test.svg.out +20 -0
  223. data/spec/fixtures/svgcheck/repair/circle.svg.code +1 -0
  224. data/spec/fixtures/svgcheck/repair/circle.svg.err +0 -0
  225. data/spec/fixtures/svgcheck/repair/circle.svg.file +4 -0
  226. data/spec/fixtures/svgcheck/repair/circle.svg.out +2 -0
  227. data/spec/fixtures/svgcheck/repair/colors.svg.code +1 -0
  228. data/spec/fixtures/svgcheck/repair/colors.svg.err +0 -0
  229. data/spec/fixtures/svgcheck/repair/colors.svg.file +19 -0
  230. data/spec/fixtures/svgcheck/repair/colors.svg.out +13 -0
  231. data/spec/fixtures/svgcheck/repair/dia-sample-svg.svg.code +1 -0
  232. data/spec/fixtures/svgcheck/repair/dia-sample-svg.svg.err +0 -0
  233. data/spec/fixtures/svgcheck/repair/dia-sample-svg.svg.file +47 -0
  234. data/spec/fixtures/svgcheck/repair/dia-sample-svg.svg.out +76 -0
  235. data/spec/fixtures/svgcheck/repair/example-dot.svg.code +1 -0
  236. data/spec/fixtures/svgcheck/repair/example-dot.svg.err +0 -0
  237. data/spec/fixtures/svgcheck/repair/example-dot.svg.file +69 -0
  238. data/spec/fixtures/svgcheck/repair/example-dot.svg.out +11 -0
  239. data/spec/fixtures/svgcheck/repair/good.svg.code +1 -0
  240. data/spec/fixtures/svgcheck/repair/good.svg.err +0 -0
  241. data/spec/fixtures/svgcheck/repair/good.svg.file +0 -0
  242. data/spec/fixtures/svgcheck/repair/good.svg.out +1 -0
  243. data/spec/fixtures/svgcheck/repair/httpbis-proxy20-fig6.svg.code +1 -0
  244. data/spec/fixtures/svgcheck/repair/httpbis-proxy20-fig6.svg.err +0 -0
  245. data/spec/fixtures/svgcheck/repair/httpbis-proxy20-fig6.svg.file +73 -0
  246. data/spec/fixtures/svgcheck/repair/httpbis-proxy20-fig6.svg.out +5 -0
  247. data/spec/fixtures/svgcheck/repair/malformed.svg.code +1 -0
  248. data/spec/fixtures/svgcheck/repair/malformed.svg.err +0 -0
  249. data/spec/fixtures/svgcheck/repair/malformed.svg.file +9 -0
  250. data/spec/fixtures/svgcheck/repair/malformed.svg.out +8 -0
  251. data/spec/fixtures/svgcheck/repair/rfc-svg.svg.code +1 -0
  252. data/spec/fixtures/svgcheck/repair/rfc-svg.svg.err +0 -0
  253. data/spec/fixtures/svgcheck/repair/rfc-svg.svg.file +0 -0
  254. data/spec/fixtures/svgcheck/repair/rfc-svg.svg.out +1 -0
  255. data/spec/fixtures/svgcheck/repair/rfc.xml.code +1 -0
  256. data/spec/fixtures/svgcheck/repair/rfc.xml.err +0 -0
  257. data/spec/fixtures/svgcheck/repair/rfc.xml.file +37 -0
  258. data/spec/fixtures/svgcheck/repair/rfc.xml.out +2 -0
  259. data/spec/fixtures/svgcheck/repair/rgb.svg.code +1 -0
  260. data/spec/fixtures/svgcheck/repair/rgb.svg.err +0 -0
  261. data/spec/fixtures/svgcheck/repair/rgb.svg.file +10 -0
  262. data/spec/fixtures/svgcheck/repair/rgb.svg.out +9 -0
  263. data/spec/fixtures/svgcheck/repair/svg-wordle.svg.code +1 -0
  264. data/spec/fixtures/svgcheck/repair/svg-wordle.svg.err +0 -0
  265. data/spec/fixtures/svgcheck/repair/svg-wordle.svg.file +112 -0
  266. data/spec/fixtures/svgcheck/repair/svg-wordle.svg.out +508 -0
  267. data/spec/fixtures/svgcheck/repair/threshold.svg.code +1 -0
  268. data/spec/fixtures/svgcheck/repair/threshold.svg.err +0 -0
  269. data/spec/fixtures/svgcheck/repair/threshold.svg.file +27 -0
  270. data/spec/fixtures/svgcheck/repair/threshold.svg.out +20 -0
  271. data/spec/fixtures/svgcheck/repair/utf8.svg.code +1 -0
  272. data/spec/fixtures/svgcheck/repair/utf8.svg.err +0 -0
  273. data/spec/fixtures/svgcheck/repair/utf8.svg.file +381 -0
  274. data/spec/fixtures/svgcheck/repair/utf8.svg.out +162 -0
  275. data/spec/fixtures/svgcheck/repair/viewBox-both.svg.code +1 -0
  276. data/spec/fixtures/svgcheck/repair/viewBox-both.svg.err +0 -0
  277. data/spec/fixtures/svgcheck/repair/viewBox-both.svg.file +4 -0
  278. data/spec/fixtures/svgcheck/repair/viewBox-both.svg.out +4 -0
  279. data/spec/fixtures/svgcheck/repair/viewBox-height.svg.code +1 -0
  280. data/spec/fixtures/svgcheck/repair/viewBox-height.svg.err +0 -0
  281. data/spec/fixtures/svgcheck/repair/viewBox-height.svg.file +4 -0
  282. data/spec/fixtures/svgcheck/repair/viewBox-height.svg.out +3 -0
  283. data/spec/fixtures/svgcheck/repair/viewBox-none.svg.code +1 -0
  284. data/spec/fixtures/svgcheck/repair/viewBox-none.svg.err +0 -0
  285. data/spec/fixtures/svgcheck/repair/viewBox-none.svg.file +4 -0
  286. data/spec/fixtures/svgcheck/repair/viewBox-none.svg.out +3 -0
  287. data/spec/fixtures/svgcheck/repair/viewBox-width.svg.code +1 -0
  288. data/spec/fixtures/svgcheck/repair/viewBox-width.svg.err +0 -0
  289. data/spec/fixtures/svgcheck/repair/viewBox-width.svg.file +4 -0
  290. data/spec/fixtures/svgcheck/repair/viewBox-width.svg.out +3 -0
  291. data/spec/fixtures/viewbox_required/inputs/missing_viewbox.svg +10 -0
  292. data/spec/fixtures/viewbox_required/repair/missing_viewbox.svg +10 -0
  293. data/spec/spec_helper.rb +16 -0
  294. data/spec/svg_conform/batch_report_spec.rb +99 -0
  295. data/spec/svg_conform/commands/check_command_spec.rb +90 -0
  296. data/spec/svg_conform/commands/profiles_command_spec.rb +20 -0
  297. data/spec/svg_conform/commands/svgcheck_compare_command_spec.rb +13 -0
  298. data/spec/svg_conform/commands/svgcheck_compatibility_command_spec.rb +13 -0
  299. data/spec/svg_conform/commands/svgcheck_generate_command_spec.rb +14 -0
  300. data/spec/svg_conform/profiles/base_profile_spec.rb +42 -0
  301. data/spec/svg_conform/profiles/lucid_fix_profile_spec.rb +46 -0
  302. data/spec/svg_conform/profiles/lucid_profile_spec.rb +84 -0
  303. data/spec/svg_conform/profiles/metanorma_profile_spec.rb +62 -0
  304. data/spec/svg_conform/profiles/no_external_css_profile_spec.rb +66 -0
  305. data/spec/svg_conform/profiles/svg_1_2_rfc_profile_spec.rb +200 -0
  306. data/spec/svg_conform/profiles/svg_1_2_rfc_with_rdf_profile_spec.rb +81 -0
  307. data/spec/svg_conform/remediations/color_remediation_spec.rb +95 -0
  308. data/spec/svg_conform/remediations/font_embedding_remediation_spec.rb +20 -0
  309. data/spec/svg_conform/remediations/font_remediation_spec.rb +95 -0
  310. data/spec/svg_conform/remediations/image_embedding_remediation_spec.rb +20 -0
  311. data/spec/svg_conform/remediations/invalid_id_references_remediation_spec.rb +97 -0
  312. data/spec/svg_conform/remediations/namespace_attribute_remediation_spec.rb +97 -0
  313. data/spec/svg_conform/remediations/namespace_remediation_spec.rb +95 -0
  314. data/spec/svg_conform/remediations/no_external_css_remediation_spec.rb +97 -0
  315. data/spec/svg_conform/remediations/style_promotion_remediation_spec.rb +97 -0
  316. data/spec/svg_conform/remediations/viewbox_remediation_spec.rb +95 -0
  317. data/spec/svg_conform/requirements/allowed_elements_requirement_spec.rb +118 -0
  318. data/spec/svg_conform/requirements/color_restrictions_requirement_spec.rb +168 -0
  319. data/spec/svg_conform/requirements/font_family_requirement_spec.rb +188 -0
  320. data/spec/svg_conform/requirements/forbidden_content_requirement_spec.rb +195 -0
  321. data/spec/svg_conform/requirements/id_reference_requirement_spec.rb +78 -0
  322. data/spec/svg_conform/requirements/invalid_id_references_requirement_spec.rb +78 -0
  323. data/spec/svg_conform/requirements/link_validation_requirement_spec.rb +78 -0
  324. data/spec/svg_conform/requirements/namespace_attributes_requirement_spec.rb +86 -0
  325. data/spec/svg_conform/requirements/namespace_requirement_spec.rb +184 -0
  326. data/spec/svg_conform/requirements/no_external_css_requirement_spec.rb +78 -0
  327. data/spec/svg_conform/requirements/no_external_fonts_requirement_spec.rb +20 -0
  328. data/spec/svg_conform/requirements/no_external_images_requirement_spec.rb +20 -0
  329. data/spec/svg_conform/requirements/style_promotion_requirement_spec.rb +78 -0
  330. data/spec/svg_conform/requirements/style_requirement_spec.rb +76 -0
  331. data/spec/svg_conform/requirements/viewbox_required_requirement_spec.rb +165 -0
  332. data/spec/svg_conform_spec.rb +32 -0
  333. data/spec/svgcheck_compatibility_spec.rb +355 -0
  334. data/svg_conform.gemspec +35 -0
  335. metadata +436 -0
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SvgConform
4
+ # Engine for applying remediations to SVG documents based on validation failures
5
+ class RemediationEngine
6
+ attr_reader :profile, :results
7
+
8
+ def initialize(profile)
9
+ @profile = profile
10
+ @results = []
11
+ end
12
+
13
+ # Apply remediations for failed requirements
14
+ def apply_remediations(document, validation_result)
15
+ @results.clear
16
+
17
+ # Group failed requirements by their remediations
18
+ remediation_groups = group_requirements_by_remediations(validation_result.failed_requirements)
19
+
20
+ # Apply each remediation only once, even if it targets multiple failed requirements
21
+ remediation_groups.each do |remediation, failed_requirements|
22
+ result = remediation.execute(document, failed_requirements)
23
+ @results << result
24
+ end
25
+
26
+ @results
27
+ end
28
+
29
+ # Get remediations that would apply to given failed requirements
30
+ def applicable_remediations(failed_requirements)
31
+ failed_requirement_ids = failed_requirements.map do |req|
32
+ req.requirement_id || req.rule&.id
33
+ end
34
+
35
+ @profile.remediations.select do |remediation|
36
+ remediation.targets.any? do |req_id|
37
+ failed_requirement_ids.include?(req_id)
38
+ end
39
+ end
40
+ end
41
+
42
+ # Check if any remediations are available for the profile
43
+ def has_remediations?
44
+ @profile.remediation_count.positive?
45
+ end
46
+
47
+ # Get summary of remediation results
48
+ def summary
49
+ return "No remediations applied" if @results.empty?
50
+
51
+ successful = @results.count(&:success?)
52
+ failed = @results.count(&:failure?)
53
+ total_changes = @results.sum(&:changes_count)
54
+
55
+ "Applied #{successful} remediations successfully, #{failed} failed. Total changes: #{total_changes}"
56
+ end
57
+
58
+ # Get successful remediation results
59
+ def successful_results
60
+ @results.select(&:success?)
61
+ end
62
+
63
+ # Get failed remediation results
64
+ def failed_results
65
+ @results.select(&:failure?)
66
+ end
67
+
68
+ # Get total number of changes made
69
+ def total_changes
70
+ @results.sum(&:changes_count)
71
+ end
72
+
73
+ private
74
+
75
+ # Group failed requirements by the remediations that target them
76
+ def group_requirements_by_remediations(failed_requirements)
77
+ groups = {}
78
+
79
+ @profile.remediations.each do |remediation|
80
+ # Find failed requirements that this remediation targets
81
+ targeted_failures = failed_requirements.select do |failure|
82
+ remediation.targets.include?(failure.requirement_id)
83
+ end
84
+
85
+ # Only include remediations that have applicable failures
86
+ groups[remediation] = targeted_failures unless targeted_failures.empty?
87
+ end
88
+
89
+ groups
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SvgConform
4
+ # Result object for remediation operations
5
+ class RemediationResult
6
+ attr_reader :remediation_id, :success, :failed_requirements, :message,
7
+ :changes_made, :error
8
+
9
+ def initialize(remediation_id:, success:, failed_requirements:,
10
+ message: nil, changes_made: [], error: nil)
11
+ @remediation_id = remediation_id
12
+ @success = success
13
+ @failed_requirements = failed_requirements
14
+ @message = message
15
+ @changes_made = changes_made || []
16
+ @error = error
17
+ end
18
+
19
+ def success?
20
+ @success
21
+ end
22
+
23
+ def failure?
24
+ !@success
25
+ end
26
+
27
+ def changes_count
28
+ @changes_made.size
29
+ end
30
+
31
+ def to_s
32
+ status = success? ? "SUCCESS" : "FAILURE"
33
+ "#{@remediation_id}: #{status} - #{@message}"
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "validator"
4
+ require_relative "remediation_engine"
5
+ require_relative "document"
6
+
7
+ module SvgConform
8
+ # Runner for applying SvgConform remediations to SVG content
9
+ class RemediationRunner
10
+ attr_reader :profile, :options
11
+
12
+ def initialize(profile: :svg_1_2_rfc, options: {})
13
+ @profile = resolve_profile(profile)
14
+ @options = {
15
+ verbose: false,
16
+ strict: false,
17
+ }.merge(options)
18
+ end
19
+
20
+ # Run validation and remediation on SVG content
21
+ def run_remediation(svg_content, filename: nil)
22
+ # Parse the SVG content
23
+ document = SvgConform::Document.from_content(svg_content)
24
+
25
+ # Perform initial validation
26
+ initial_validation = @profile.validate(document)
27
+
28
+ # Create remediation engine
29
+ remediation_engine = SvgConform::RemediationEngine.new(@profile)
30
+
31
+ # Apply remediations if there are failures
32
+ remediation_results = []
33
+ if initial_validation.failed_requirements.any?
34
+ remediation_results = remediation_engine.apply_remediations(document,
35
+ initial_validation)
36
+ end
37
+
38
+ # Perform final validation after remediations
39
+ final_validation = @profile.validate(document)
40
+
41
+ # Create result object
42
+ RemediationRunResult.new(
43
+ filename: filename,
44
+ original_content: svg_content,
45
+ remediated_content: document.to_xml,
46
+ initial_validation: initial_validation,
47
+ final_validation: final_validation,
48
+ remediation_results: remediation_results,
49
+ remediation_engine: remediation_engine,
50
+ )
51
+ end
52
+
53
+ # Run remediation on a file
54
+ def run_remediation_file(file_path)
55
+ raise "File not found: #{file_path}" unless File.exist?(file_path)
56
+
57
+ svg_content = File.read(file_path)
58
+ filename = File.basename(file_path)
59
+
60
+ run_remediation(svg_content, filename: filename)
61
+ end
62
+
63
+ # Run remediation on multiple files
64
+ def run_remediation_batch(file_paths)
65
+ results = {}
66
+
67
+ file_paths.each do |file_path|
68
+ results[file_path] = run_remediation_file(file_path)
69
+ rescue StandardError => e
70
+ results[file_path] = create_error_result(file_path, e)
71
+ end
72
+
73
+ results
74
+ end
75
+
76
+ # Check if profile has any remediations available
77
+ def has_remediations?
78
+ @profile.remediation_count.positive?
79
+ end
80
+
81
+ # Get available remediations for the profile
82
+ def available_remediations
83
+ @profile.remediations
84
+ end
85
+
86
+ private
87
+
88
+ def resolve_profile(profile)
89
+ case profile
90
+ when Symbol, String
91
+ SvgConform::Profiles.get(profile) || raise("Unknown profile: #{profile}")
92
+ when SvgConform::Profile
93
+ profile
94
+ else
95
+ raise "Invalid profile type: #{profile.class}"
96
+ end
97
+ end
98
+
99
+ def create_error_result(file_path, error)
100
+ RemediationRunResult.new(
101
+ filename: File.basename(file_path),
102
+ original_content: nil,
103
+ remediated_content: nil,
104
+ initial_validation: nil,
105
+ final_validation: nil,
106
+ remediation_results: [],
107
+ remediation_engine: nil,
108
+ error: error,
109
+ )
110
+ end
111
+ end
112
+
113
+ # Result object for overall remediation run (not individual remediation result)
114
+ class RemediationRunResult
115
+ attr_reader :filename, :original_content, :remediated_content,
116
+ :initial_validation, :final_validation, :remediation_results,
117
+ :remediation_engine, :error
118
+
119
+ def initialize(filename:, original_content:, remediated_content:,
120
+ initial_validation:, final_validation:, remediation_results:,
121
+ remediation_engine:, error: nil)
122
+ @filename = filename
123
+ @original_content = original_content
124
+ @remediated_content = remediated_content
125
+ @initial_validation = initial_validation
126
+ @final_validation = final_validation
127
+ @remediation_results = remediation_results
128
+ @remediation_engine = remediation_engine
129
+ @error = error
130
+ end
131
+
132
+ # Check if remediation was successful
133
+ def success?
134
+ @error.nil? && @final_validation&.valid?
135
+ end
136
+
137
+ # Check if there was an error
138
+ def error?
139
+ !@error.nil?
140
+ end
141
+
142
+ # Get the number of issues fixed
143
+ def issues_fixed
144
+ return 0 if @error || @initial_validation.nil? || @final_validation.nil?
145
+
146
+ initial_errors = @initial_validation.failed_requirements.length
147
+ final_errors = @final_validation.failed_requirements.length
148
+
149
+ [initial_errors - final_errors, 0].max
150
+ end
151
+
152
+ # Get the number of remediations applied
153
+ def remediations_applied
154
+ @remediation_results&.length || 0
155
+ end
156
+
157
+ # Get successful remediations
158
+ def successful_remediations
159
+ return [] if @remediation_results.nil?
160
+
161
+ @remediation_results.select(&:success?)
162
+ end
163
+
164
+ # Get failed remediations
165
+ def failed_remediations
166
+ return [] if @remediation_results.nil?
167
+
168
+ @remediation_results.select(&:failure?)
169
+ end
170
+
171
+ # Check if content was modified
172
+ def content_modified?
173
+ return false if @original_content.nil? || @remediated_content.nil?
174
+
175
+ normalize_content(@original_content) != normalize_content(@remediated_content)
176
+ end
177
+
178
+ # Get summary of changes
179
+ def changes_summary
180
+ return "Error occurred" if @error
181
+ return "No validation performed" if @initial_validation.nil?
182
+
183
+ if content_modified?
184
+ "#{issues_fixed} issues fixed, #{remediations_applied} remediations applied"
185
+ else
186
+ "No changes needed"
187
+ end
188
+ end
189
+
190
+ # Convert to hash for serialization
191
+ def to_h
192
+ {
193
+ filename: @filename,
194
+ success: success?,
195
+ error: @error&.message,
196
+ issues_fixed: issues_fixed,
197
+ remediations_applied: remediations_applied,
198
+ content_modified: content_modified?,
199
+ changes_summary: changes_summary,
200
+ initial_errors: @initial_validation&.failed_requirements&.length || 0,
201
+ final_errors: @final_validation&.failed_requirements&.length || 0,
202
+ remediation_details: @remediation_results&.map(&:to_h) || [],
203
+ }
204
+ end
205
+
206
+ # Generate a conformance report for the final state
207
+ def generate_conformance_report
208
+ return nil if @final_validation.nil? || @filename.nil?
209
+
210
+ SvgConform::ConformanceReport.from_svg_conform_result(
211
+ @filename,
212
+ @final_validation,
213
+ profile: @remediation_engine&.profile&.name || :unknown,
214
+ use_svgcheck_mapping: true,
215
+ )
216
+ end
217
+
218
+ private
219
+
220
+ def normalize_content(content)
221
+ # Normalize whitespace and formatting for comparison
222
+ content.gsub(/\s+/, " ").strip
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+ require_relative "../remediation_result"
5
+
6
+ module SvgConform
7
+ module Remediations
8
+ # Base class for all remediations using lutaml-model serialization
9
+ class BaseRemediation < Lutaml::Model::Serializable
10
+ attribute :id, :string
11
+ attribute :description, :string
12
+ attribute :targets, :string, collection: true
13
+ attribute :config, :hash
14
+ attribute :type, :string, polymorphic_class: true, default: -> {
15
+ self.class.name.split("::").last
16
+ }
17
+
18
+ yaml do
19
+ map "id", to: :id
20
+ map "description", to: :description
21
+ map "targets", to: :targets
22
+ map "config", to: :config
23
+ map "type", to: :type
24
+ end
25
+
26
+ def apply(document, context)
27
+ raise NotImplementedError, "Subclasses must implement apply"
28
+ end
29
+
30
+ # Check if this remediation should execute based on failed requirements
31
+ def should_execute?(failed_requirements)
32
+ failed_requirement_ids = failed_requirements.map(&:requirement_id)
33
+ targets.any? { |req_id| failed_requirement_ids.include?(req_id) }
34
+ end
35
+
36
+ # Execute the remediation and return a result
37
+ def execute(document, failed_requirements)
38
+ unless should_execute?(failed_requirements)
39
+ return RemediationResult.new(
40
+ remediation_id: @id,
41
+ success: false,
42
+ failed_requirements: [],
43
+ message: "Remediation not applicable",
44
+ )
45
+ end
46
+
47
+ begin
48
+ context = { failed_requirements: failed_requirements }
49
+ changes_made = apply(document, context)
50
+
51
+ RemediationResult.new(
52
+ remediation_id: @id,
53
+ success: true,
54
+ failed_requirements: failed_requirements,
55
+ message: "Remediation applied successfully",
56
+ changes_made: changes_made,
57
+ )
58
+ rescue StandardError => e
59
+ RemediationResult.new(
60
+ remediation_id: @id,
61
+ success: false,
62
+ failed_requirements: failed_requirements,
63
+ message: "Remediation failed: #{e.message}",
64
+ error: e,
65
+ )
66
+ end
67
+ end
68
+
69
+ def to_s
70
+ "#{id}: #{description} (targets: #{targets.join(', ')})"
71
+ end
72
+
73
+ protected
74
+
75
+ def element?(node)
76
+ node.respond_to?(:name) && node.name
77
+ end
78
+
79
+ def get_attribute(node, attr_name)
80
+ return nil unless node.respond_to?(:[])
81
+
82
+ node[attr_name]
83
+ end
84
+
85
+ def set_attribute(node, attr_name, value)
86
+ return unless node.respond_to?(:[]=)
87
+
88
+ node[attr_name] = value
89
+ end
90
+
91
+ def has_attribute?(node, attr_name)
92
+ return false unless node.respond_to?(:[])
93
+
94
+ !node[attr_name].nil?
95
+ end
96
+
97
+ def find_nodes(document, &)
98
+ nodes = []
99
+ traverse_nodes(document, nodes, &)
100
+ nodes
101
+ end
102
+
103
+ # Helper method to remove attribute
104
+ def remove_attribute(node, name)
105
+ if node.respond_to?(:remove_attribute)
106
+ node.remove_attribute(name)
107
+ true
108
+ elsif node.respond_to?(:[]=) && node.respond_to?(:[])
109
+ # Fallback for different implementations
110
+ node[name] = nil
111
+ true
112
+ else
113
+ false
114
+ end
115
+ end
116
+
117
+ # Helper method to remove node
118
+ def remove_node(node)
119
+ return false unless node.respond_to?(:remove)
120
+
121
+ node.remove
122
+ true
123
+ end
124
+
125
+ # Helper method to replace node
126
+ def replace_node(node, replacement)
127
+ return false unless node.respond_to?(:replace)
128
+
129
+ node.replace(replacement)
130
+ true
131
+ end
132
+
133
+ # Helper method to create comment node
134
+ def create_comment(document, text)
135
+ document.create_comment(text)
136
+ end
137
+
138
+ def log_change(type, message, node = nil)
139
+ {
140
+ type: type,
141
+ message: message,
142
+ node: node&.name || "unknown",
143
+ node_attributes: node.respond_to?(:attributes) ? node.attributes : nil,
144
+ timestamp: Time.now,
145
+ }
146
+ end
147
+
148
+ private
149
+
150
+ def traverse_nodes(node, nodes, &block)
151
+ return unless node
152
+
153
+ # Add node if it matches the block condition
154
+ nodes << node if yield(node)
155
+
156
+ # Traverse children if the node supports it
157
+ if node.respond_to?(:children)
158
+ node.children.each { |child| traverse_nodes(child, nodes, &block) }
159
+ elsif node.respond_to?(:each)
160
+ node.each { |child| traverse_nodes(child, nodes, &block) }
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end