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,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SvgConform
4
+ module ExternalCheckers
5
+ module Svgcheck
6
+ # Compatibility engine for matching svg_conform behavior to svgcheck
7
+ class CompatibilityEngine
8
+ def initialize
9
+ # Load any compatibility-specific configuration
10
+ end
11
+
12
+ # Check if file should be treated as unparseable by svgcheck
13
+ def should_mimic_parse_failure?(_filename, _validation_result)
14
+ # Implement parse failure detection logic based on svgcheck behavior
15
+ # For now, return false - this would need to be enhanced based on actual svgcheck behavior
16
+ false
17
+ end
18
+
19
+ # Check if validity errors should be included as regular errors (for svgcheck compatibility)
20
+ def should_include_validity_errors?(validation_result, filename)
21
+ # Include validity errors if both width and height are present
22
+ # This matches svgcheck's behavior from the mapping config
23
+ return false unless validation_result.validity_errors.any?
24
+
25
+ # Check for specific datatype condition mentioned in mapping config
26
+ has_width_and_height_datatype_condition?(validation_result, filename)
27
+ end
28
+
29
+ # Filter errors for svgcheck compatibility
30
+ def filter_errors_for_svgcheck(errors, _filename, _validation_result)
31
+ filtered_errors = []
32
+
33
+ errors.each do |error|
34
+ # Skip namespace_validation errors only for RDF/metadata namespaces
35
+ # svgcheck skips these silently, but reports other namespace errors
36
+ # Only skip RDF-related namespace errors
37
+ if (error.requirement_id == "namespace_validation") && rdf_namespace_error?(error)
38
+ next
39
+ end
40
+
41
+ # Map svg_conform requirement IDs to svgcheck-compatible ones
42
+ mapped_req_id = map_requirement_id(error.requirement_id)
43
+ next if mapped_req_id.nil? # Skip unmapped requirements
44
+
45
+ filtered_errors << [error, mapped_req_id]
46
+ end
47
+
48
+ filtered_errors
49
+ end
50
+
51
+ private
52
+
53
+ def rdf_namespace_error?(error)
54
+ return false unless error.respond_to?(:message)
55
+
56
+ # List of RDF/metadata namespaces that svgcheck silently skips
57
+ rdf_namespaces = [
58
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
59
+ "http://creativecommons.org/ns#",
60
+ "http://purl.org/dc/elements/1.1/",
61
+ "http://purl.org/dc/dcmitype/",
62
+ "http://www.w3.org/2000/01/rdf-schema#",
63
+ ]
64
+
65
+ rdf_namespaces.any? { |ns| error.message.include?(ns) }
66
+ end
67
+
68
+ def has_width_and_height_datatype_condition?(validation_result,
69
+ _filename)
70
+ # This would need to be implemented based on the specific logic
71
+ # mentioned in the mapping config's "datatype_condition": "both_width_and_height_present"
72
+ # For now, return true if there are validity errors
73
+ validation_result.validity_errors.any?
74
+ end
75
+
76
+ def map_requirement_id(requirement_id)
77
+ # Map svg_conform requirement IDs to svgcheck-compatible requirement IDs
78
+ mapping = {
79
+ "allowed_elements" => "allowed_elements",
80
+ # forbidden_content not mapped - it's redundant with allowed_elements checks
81
+ "namespace_attributes" => "namespace_attributes",
82
+ "namespace_validation" => "namespace_attributes", # Map to closest equivalent
83
+ "font_family" => "style", # svgcheck reports font-family issues as style errors
84
+ "color_restrictions" => "color_restrictions",
85
+ "viewbox_required" => "viewbox_required",
86
+ "style" => "style", # Direct mapping for StyleRequirement
87
+ "style_promotion" => "style",
88
+ "style_syntax" => "style",
89
+ "style_validation" => "style",
90
+ "property_value" => "style",
91
+ "required_attribute" => "viewbox_required",
92
+ "namespace" => "namespace_attributes",
93
+ }
94
+
95
+ # Return nil for unmapped requirements (like invalid_id_references)
96
+ mapping[requirement_id.to_s]
97
+ end
98
+
99
+ # Extract semantic meaning from error for comparison
100
+ def extract_semantic_meaning(error)
101
+ # Ensure we have a valid error object
102
+ return "unknown" unless error.respond_to?(:message) && error.respond_to?(:requirement_id)
103
+
104
+ # Try to create a semantic key based on the error type and context
105
+ requirement = error.requirement_id
106
+ message = error.message
107
+
108
+ # Handle different types of errors semantically
109
+ case requirement
110
+ when "color_restrictions"
111
+ # Extract color and attribute for semantic grouping
112
+ if message&.include?("doesn't allow") && (match = message.match(/attribute ['"]([^'"]+)['"].*value ['"]([^'"]+)['"]/))
113
+ attribute = match[1]
114
+ value = match[2]
115
+ "color_restriction:#{attribute}:#{value}"
116
+ else
117
+ requirement
118
+ end
119
+ when "font_family"
120
+ # Group font family errors by attribute and font
121
+ if message&.include?("font-family") && (match = message.match(/font-family.*['"]([^'"]+)['"]/))
122
+ font = match[1]
123
+ "font_restriction:#{font}"
124
+ else
125
+ requirement
126
+ end
127
+ when "allowed_elements"
128
+ # Group by element type
129
+ if message&.include?("not allowed") && (match = message.match(/Element ['"]([^'"]+)['"]/))
130
+ element = match[1]
131
+ "element_not_allowed:#{element}"
132
+ elsif message&.include?("does not allow the attribute") && (match = message.match(/element ['"]([^'"]+)['"].*attribute ['"]([^'"]+)['"]/))
133
+ element = match[1]
134
+ attribute = match[2]
135
+ "invalid_attribute:#{element}:#{attribute}"
136
+ else
137
+ requirement
138
+ end
139
+ when "namespace_attributes"
140
+ # Group by namespace issues
141
+ if message&.include?("namespace") && (match = message.match(/namespace ['"]([^'"]+)['"]/))
142
+ namespace = match[1]
143
+ "namespace_issue:#{namespace}"
144
+ else
145
+ requirement
146
+ end
147
+ when "viewbox_required"
148
+ # ViewBox is generally consistent
149
+ "viewbox_required"
150
+ when "style"
151
+ # Group style issues by property
152
+ if message&.include?("style") && (match = message.match(/property ['"]([^'"]+)['"]/))
153
+ property = match[1]
154
+ "style_issue:#{property}"
155
+ else
156
+ requirement
157
+ end
158
+ else
159
+ # Fallback to requirement_id for other cases
160
+ requirement || "unknown"
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "fileutils"
5
+
6
+ module SvgConform
7
+ module ExternalCheckers
8
+ module Svgcheck
9
+ # Generates svgcheck outputs for comparison testing
10
+ class OutputGenerator
11
+ attr_reader :svgcheck_exec, :svgcheck_path
12
+
13
+ def initialize(svgcheck_exec: "python3",
14
+ svgcheck_path: "svgcheck/svgcheck")
15
+ @svgcheck_exec = svgcheck_exec
16
+ @svgcheck_path = svgcheck_path
17
+ end
18
+
19
+ # Generate svgcheck outputs for a given input file
20
+ def generate(input_file, mode: :both)
21
+ results = {}
22
+
23
+ case mode
24
+ when :check
25
+ results[:check] = generate_mode_output(input_file, :check)
26
+ when :repair
27
+ results[:repair] = generate_mode_output(input_file, :repair)
28
+ when :both
29
+ results[:check] = generate_mode_output(input_file, :check)
30
+ results[:repair] = generate_mode_output(input_file, :repair)
31
+ else
32
+ raise ArgumentError,
33
+ "Invalid mode: #{mode}. Must be :check, :repair, or :both"
34
+ end
35
+
36
+ results
37
+ end
38
+
39
+ private
40
+
41
+ def generate_mode_output(input_file, mode)
42
+ unless available?
43
+ return {
44
+ success: false,
45
+ error: "svgcheck not available (#{@svgcheck_exec} or #{@svgcheck_path} not found)",
46
+ }
47
+ end
48
+
49
+ begin
50
+ # Change to svgcheck directory to run the module
51
+ original_dir = Dir.pwd
52
+ Dir.chdir(@svgcheck_path)
53
+
54
+ # Build command based on mode
55
+ relative_input = File.join("Tests", File.basename(input_file))
56
+ cmd = if mode == :repair
57
+ [@svgcheck_exec, "run.py", "--repair", relative_input]
58
+ else
59
+ [@svgcheck_exec, "run.py", relative_input] # check mode - no --repair flag
60
+ end
61
+
62
+ stdout, stderr, status = Open3.capture3(*cmd)
63
+
64
+ # Return to original directory
65
+ Dir.chdir(original_dir)
66
+
67
+ {
68
+ success: true,
69
+ stdout: stdout,
70
+ stderr: stderr,
71
+ exit_code: status.exitstatus,
72
+ mode: mode,
73
+ }
74
+ rescue StandardError => e
75
+ # Make sure we return to original directory even on error
76
+ Dir.chdir(original_dir) if Dir.pwd != original_dir
77
+
78
+ {
79
+ success: false,
80
+ error: "Error running svgcheck in #{mode} mode: #{e.message}",
81
+ }
82
+ end
83
+ end
84
+
85
+ def available?
86
+ command_available?(@svgcheck_exec) && Dir.exist?(@svgcheck_path)
87
+ end
88
+
89
+ # SECURITY: Prevent command injection by using array form of system()
90
+ # GitHub CodeQL: Uncontrolled command line (CWE-078)
91
+ # This affects svgcheck comparison commands (development use only)
92
+ def command_available?(command)
93
+ # Use array form to prevent shell injection
94
+ system("which", command, out: File::NULL, err: File::NULL) ||
95
+ system("where", command, out: File::NULL, err: File::NULL) || # Windows
96
+ File.executable?(command) # Direct path check
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module SvgConform
6
+ module ExternalCheckers
7
+ module Svgcheck
8
+ # Enhanced svgcheck output parser that uses mapping configuration
9
+ class Parser
10
+ attr_reader :mapping_config
11
+
12
+ def initialize(mapping_config_path = nil)
13
+ @mapping_config_path = mapping_config_path || "config/svgcheck_mapping.yml"
14
+ load_mapping_config
15
+ end
16
+
17
+ # Parse svgcheck output into a ConformanceReport
18
+ def parse(output_content, _error_content = nil, filename: nil)
19
+ return create_empty_report(filename) if output_content.nil? || output_content.strip.empty?
20
+
21
+ errors = parse_error_lines(output_content)
22
+ create_report_from_errors(errors, filename)
23
+ end
24
+
25
+ private
26
+
27
+ def load_mapping_config
28
+ @mapping_config = if File.exist?(@mapping_config_path)
29
+ YAML.load_file(@mapping_config_path)
30
+ else
31
+ # Fallback to empty config if file doesn't exist
32
+ {
33
+ "message_patterns" => [],
34
+ "requirement_mappings" => {},
35
+ "skip_patterns" => [],
36
+ "special_rules" => {},
37
+ }
38
+ end
39
+ end
40
+
41
+ def parse_error_lines(content)
42
+ errors = []
43
+
44
+ content.each_line do |line|
45
+ line = line.strip
46
+ next if line.empty?
47
+ next if should_skip_line?(line)
48
+
49
+ # Try to match against message patterns
50
+ matched_error = match_against_patterns(line)
51
+ errors << if matched_error
52
+ matched_error
53
+ else
54
+ # Fallback to basic error structure for unmapped errors
55
+ create_unmapped_error(line)
56
+ end
57
+ end
58
+
59
+ errors
60
+ end
61
+
62
+ def should_skip_line?(line)
63
+ return false unless @mapping_config["skip_patterns"]
64
+
65
+ @mapping_config["skip_patterns"].each do |skip_pattern|
66
+ pattern = skip_pattern["pattern"]
67
+ flags = skip_pattern["flags"]
68
+
69
+ regex_flags = 0
70
+ regex_flags |= Regexp::IGNORECASE if flags&.include?("i")
71
+
72
+ if line.match?(Regexp.new(pattern, regex_flags))
73
+ return true
74
+ end
75
+ end
76
+
77
+ false
78
+ end
79
+
80
+ def match_against_patterns(line)
81
+ return nil unless @mapping_config["message_patterns"]
82
+
83
+ @mapping_config["message_patterns"].each do |pattern_config|
84
+ pattern = pattern_config["pattern"]
85
+ requirement = pattern_config["requirement"]
86
+ semantic_key = pattern_config["semantic_key"]
87
+
88
+ match = line.match(Regexp.new(pattern))
89
+ next unless match
90
+
91
+ # Skip patterns that map to null requirement (unmapped errors)
92
+ return nil if requirement.nil?
93
+
94
+ return create_mapped_error(line, requirement, semantic_key, match)
95
+ end
96
+
97
+ nil
98
+ end
99
+
100
+ def create_mapped_error(line, requirement_id, semantic_key, match)
101
+ # Substitute regex groups in semantic key
102
+ processed_semantic_key = semantic_key
103
+ if semantic_key && match.captures.any?
104
+ match.captures.each_with_index do |capture, index|
105
+ processed_semantic_key = processed_semantic_key.gsub(
106
+ "${#{index + 1}}", capture || ""
107
+ )
108
+ end
109
+ end
110
+
111
+ # Apply requirement_mappings to normalize requirement IDs
112
+ normalized_requirement_id = apply_requirement_mapping(requirement_id)
113
+
114
+ issue = SvgConform::ConformanceIssue.new
115
+ issue.type = "error"
116
+ issue.requirement_id = normalized_requirement_id
117
+ issue.message = line
118
+
119
+ # Extract semantic information from the match
120
+ if (match.captures.length >= 1) && match.captures[0]
121
+ issue.attribute = match.captures[0]
122
+ end
123
+ if (match.captures.length >= 2) && match.captures[1]
124
+ issue.value = match.captures[1]
125
+ end
126
+ if (match.captures.length >= 3) && match.captures[2]
127
+ issue.element = match.captures[2]
128
+ end
129
+
130
+ issue
131
+ end
132
+
133
+ def apply_requirement_mapping(requirement_id)
134
+ return requirement_id unless @mapping_config["requirement_mappings"]
135
+
136
+ # Apply requirement_mappings to normalize requirement IDs
137
+ mappings = @mapping_config["requirement_mappings"]
138
+ mappings[requirement_id.to_s] || requirement_id
139
+ end
140
+
141
+ def create_unmapped_error(line)
142
+ issue = SvgConform::ConformanceIssue.new
143
+ issue.type = "error"
144
+ issue.requirement_id = "unmapped_svgcheck_error"
145
+ issue.message = line
146
+ issue
147
+ end
148
+
149
+ def create_report_from_errors(errors, filename)
150
+ report = SvgConform::ConformanceReport.new
151
+ report.filename = filename
152
+ report.tool = "svgcheck"
153
+ report.timestamp = Time.now.iso8601
154
+ report.valid = errors.empty?
155
+
156
+ # Initialize summaries
157
+ report.errors = SvgConform::IssueSummary.new
158
+ report.errors.total_count = errors.length
159
+ report.errors.by_requirement = {}
160
+ report.errors.issues = errors
161
+
162
+ report.warnings = SvgConform::IssueSummary.new
163
+ report.warnings.total_count = 0
164
+ report.warnings.by_requirement = {}
165
+ report.warnings.issues = []
166
+
167
+ # Group errors by requirement
168
+ if errors.any?
169
+ error_groups = errors.group_by(&:requirement_id)
170
+ error_groups.each do |req_id, req_errors|
171
+ report.errors.by_requirement[req_id] = req_errors.length
172
+ end
173
+ end
174
+
175
+ report
176
+ end
177
+
178
+ def create_empty_report(filename)
179
+ report = SvgConform::ConformanceReport.new
180
+ report.filename = filename
181
+ report.tool = "svgcheck"
182
+ report.timestamp = Time.now.iso8601
183
+ report.valid = true
184
+
185
+ report.errors = SvgConform::IssueSummary.new
186
+ report.errors.total_count = 0
187
+ report.errors.by_requirement = {}
188
+ report.errors.issues = []
189
+
190
+ report.warnings = SvgConform::IssueSummary.new
191
+ report.warnings.total_count = 0
192
+ report.warnings.by_requirement = {}
193
+ report.warnings.issues = []
194
+
195
+ report
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../conformance_report"
4
+ require_relative "../../validator"
5
+ require_relative "../../profiles"
6
+
7
+ module SvgConform
8
+ module ExternalCheckers
9
+ module Svgcheck
10
+ # Sophisticated report comparison mechanism for svg_conform vs svgcheck
11
+ class ReportComparator
12
+ attr_reader :test_files_dir, :svgcheck_outputs_dir
13
+
14
+ def initialize(test_files_dir: "svgcheck/svgcheck/Tests",
15
+ svgcheck_outputs_dir: "spec/fixtures/svgcheck")
16
+ @test_files_dir = test_files_dir
17
+ @svgcheck_outputs_dir = svgcheck_outputs_dir
18
+ end
19
+
20
+ # Compare all test files between svg_conform and svgcheck
21
+ def compare_all_test_files
22
+ test_files = find_test_files
23
+
24
+ results = {
25
+ total_files: test_files.length,
26
+ matching: 0,
27
+ different: 0,
28
+ errors: 0,
29
+ differences: {},
30
+ }
31
+
32
+ test_files.each do |filename|
33
+ comparison = compare_file(filename)
34
+
35
+ if comparison[:identical]
36
+ results[:matching] += 1
37
+ else
38
+ results[:different] += 1
39
+ results[:differences][filename] = comparison
40
+ end
41
+ rescue StandardError => e
42
+ results[:errors] += 1
43
+ results[:differences][filename] = {
44
+ error: e.message,
45
+ summary: "Error during comparison: #{e.message}",
46
+ }
47
+ end
48
+
49
+ results
50
+ end
51
+
52
+ # Compare a single file between svg_conform and svgcheck
53
+ def compare_file(filename)
54
+ svg_conform_report = generate_svg_conform_report(filename)
55
+ svgcheck_report = load_svgcheck_report(filename)
56
+
57
+ unless svgcheck_report
58
+ return { error: "No svgcheck report found",
59
+ identical: false }
60
+ end
61
+
62
+ # Perform detailed comparison
63
+ comparison = svg_conform_report.compare_with(svgcheck_report)
64
+
65
+ # Add semantic comparison
66
+ semantic_comparison = perform_semantic_comparison(svg_conform_report,
67
+ svgcheck_report)
68
+ comparison[:semantic] = semantic_comparison
69
+
70
+ comparison
71
+ end
72
+
73
+ private
74
+
75
+ def find_test_files
76
+ return [] unless Dir.exist?(@test_files_dir)
77
+
78
+ pattern = File.join(@test_files_dir, "*.{svg,xml}")
79
+ Dir.glob(pattern).map { |path| File.basename(path) }.sort
80
+ end
81
+
82
+ def generate_svg_conform_report(filename)
83
+ input_file = File.join(@test_files_dir, filename)
84
+ return nil unless File.exist?(input_file)
85
+
86
+ # Load svg_1_2_rfc profile
87
+ profile = SvgConform::Profiles.load_profile(:svg_1_2_rfc)
88
+
89
+ # Validate using svg_conform
90
+ validator = SvgConform::Validator.new(profile)
91
+ validation_result = validator.validate_file(input_file)
92
+
93
+ # Create report with svgcheck mapping for compatibility
94
+ ConformanceReport.from_svg_conform_result(
95
+ filename,
96
+ validation_result,
97
+ profile: "svg_1_2_rfc",
98
+ use_svgcheck_mapping: true,
99
+ )
100
+ end
101
+
102
+ def load_svgcheck_report(filename)
103
+ base_name = File.basename(filename, ".*")
104
+ extension = File.extname(filename)
105
+
106
+ # Try to load from check mode first, then repair mode
107
+ check_error_file = File.join(@svgcheck_outputs_dir, "check",
108
+ "#{base_name}#{extension}.out")
109
+ repair_error_file = File.join(@svgcheck_outputs_dir, "repair",
110
+ "#{base_name}#{extension}.out")
111
+
112
+ error_file = if File.exist?(check_error_file)
113
+ check_error_file
114
+ elsif File.exist?(repair_error_file)
115
+ repair_error_file
116
+ else
117
+ return nil
118
+ end
119
+
120
+ error_content = File.read(error_file)
121
+ ConformanceReport.from_svgcheck_result(filename, error_content)
122
+ end
123
+
124
+ def perform_semantic_comparison(svg_conform_report, svgcheck_report)
125
+ semantic_issues = []
126
+
127
+ # Compare semantic equivalence of errors
128
+ svg_conform_errors = group_errors_semantically(svg_conform_report.errors.issues)
129
+ svgcheck_errors = group_errors_semantically(svgcheck_report.errors.issues)
130
+
131
+ # Find semantic mismatches
132
+ all_semantic_keys = (svg_conform_errors.keys + svgcheck_errors.keys).uniq
133
+
134
+ all_semantic_keys.each do |semantic_key|
135
+ svg_count = svg_conform_errors[semantic_key]&.length || 0
136
+ svgcheck_count = svgcheck_errors[semantic_key]&.length || 0
137
+
138
+ next if svg_count == svgcheck_count
139
+
140
+ semantic_issues << {
141
+ semantic_key: semantic_key,
142
+ svg_conform_count: svg_count,
143
+ svgcheck_count: svgcheck_count,
144
+ difference: svg_count - svgcheck_count,
145
+ }
146
+ end
147
+
148
+ {
149
+ total_semantic_groups: all_semantic_keys.length,
150
+ matching_groups: all_semantic_keys.length - semantic_issues.length,
151
+ mismatched_groups: semantic_issues.length,
152
+ issues: semantic_issues,
153
+ }
154
+ end
155
+
156
+ def group_errors_semantically(errors)
157
+ errors.group_by do |error|
158
+ # Create semantic key based on requirement and core issue
159
+ requirement = error.requirement_id
160
+ attribute = error.attribute
161
+ element = error.element
162
+
163
+ if attribute && element
164
+ "#{requirement}:#{element}:#{attribute}"
165
+ elsif element
166
+ "#{requirement}:#{element}"
167
+ else
168
+ requirement
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end