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,772 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "table_tennis"
4
+ require "paint"
5
+
6
+ module SvgConform
7
+ # Compares SvgConform validation reports with svgcheck reports
8
+ # and displays the differences in a formatted table
9
+ class ReportComparator
10
+ def initialize(profile: :svg_1_2_rfc,
11
+ reports_dir: "spec/fixtures/svgcheck_reports")
12
+ @profile = profile
13
+ @reports_dir = reports_dir
14
+ end
15
+
16
+ # Compare two reports and display the results in a table
17
+ def compare_reports(svg_conform_report, svgcheck_report, filename)
18
+ puts "\n#{Paint['=' * 80, :cyan]}"
19
+ puts Paint["📊 REPORT COMPARISON: #{filename}", :blue, :bold]
20
+ puts Paint["=" * 80, :cyan]
21
+
22
+ # Summary table
23
+ display_summary_table(svg_conform_report, svgcheck_report)
24
+
25
+ # Error mapping table
26
+ display_error_mapping_table(svg_conform_report, svgcheck_report)
27
+
28
+ # Generate comparison result for requirement mapping
29
+ result = generate_comparison_result(svg_conform_report, svgcheck_report,
30
+ filename)
31
+
32
+ # Display requirement-based mapping
33
+ display_requirement_mapping(result)
34
+ end
35
+
36
+ # Compare a single file and return structured results
37
+ def compare_single_file(filename)
38
+ validator = SvgConform::Validator.new
39
+
40
+ begin
41
+ # Extract just the basename if a full path was provided
42
+ basename = File.basename(filename)
43
+
44
+ # Load svgcheck report
45
+ svgcheck_report_path = File.join(@reports_dir,
46
+ "#{basename}.svgcheck.yaml")
47
+ return nil unless File.exist?(svgcheck_report_path)
48
+
49
+ # Use the new external checker parser
50
+ parser = SvgConform::ExternalCheckers::Svgcheck::Parser.new
51
+ error_content = File.read(svgcheck_report_path)
52
+ svgcheck_report = parser.parse(error_content, nil, filename: basename)
53
+
54
+ # Generate SvgConform report - use the original filename for the SVG file path
55
+ svg_file_path = filename.start_with?("svgcheck/") ? filename : "svgcheck/svgcheck/Tests/#{basename}"
56
+ return nil unless File.exist?(svg_file_path)
57
+
58
+ svg_content = File.read(svg_file_path)
59
+ validation_result = validator.validate(svg_content, profile: @profile)
60
+ svg_conform_report = SvgConform::ConformanceReport.from_svg_conform_result(basename, validation_result,
61
+ profile: @profile, use_svgcheck_mapping: true)
62
+
63
+ # Generate structured comparison result
64
+ generate_comparison_result(svg_conform_report, svgcheck_report,
65
+ basename)
66
+ rescue StandardError => e
67
+ puts "❌ Error processing #{filename}: #{e.message}"
68
+ nil
69
+ end
70
+ end
71
+
72
+ # Display single file comparison result using the table formatting
73
+ def display_single_file_result(result)
74
+ return unless result
75
+
76
+ filename = result[:filename]
77
+
78
+ puts "\n#{Paint['=' * 80, :cyan]}"
79
+ puts Paint["📊 REPORT COMPARISON: #{filename}", :blue, :bold]
80
+ puts Paint["=" * 80, :cyan]
81
+
82
+ # Summary table
83
+ headers = ["Metric", "SvgConform", "svgcheck", "Match?"]
84
+ rows = [
85
+ ["Valid", result[:svg_conform_valid], result[:svgcheck_valid],
86
+ result[:valid_match] ? "✓" : "✗"],
87
+ ["Total Errors", result[:svg_conform_errors], result[:svgcheck_errors],
88
+ result[:errors_match] ? "✓" : "✗"],
89
+ ["Error Types", result[:svg_conform_error_types], result[:svgcheck_error_types],
90
+ result[:error_types_match] ? "✓" : "✗"],
91
+ ]
92
+
93
+ puts "\nSUMMARY:"
94
+ render_gh_style_table(headers, rows)
95
+
96
+ # Semantic error mapping table
97
+ if result[:semantic_mapping]&.any?
98
+ headers = ["Error Type", "SvgConform Count", "svgcheck Count", "Status"]
99
+ rows = []
100
+
101
+ result[:semantic_mapping].each do |error_type, counts|
102
+ svg_count = counts[:svg_conform]
103
+ svgcheck_count = counts[:svgcheck]
104
+ status = if svg_count == svgcheck_count && svg_count.positive?
105
+ "✓ MATCH"
106
+ elsif svg_count == svgcheck_count && svg_count.zero?
107
+ "✓ BOTH NONE"
108
+ elsif svg_count > svgcheck_count
109
+ "⚠ MISMATCH"
110
+ elsif svg_count < svgcheck_count
111
+ "⚠ MISMATCH"
112
+ else
113
+ "? UNKNOWN"
114
+ end
115
+
116
+ rows << [error_type, svg_count, svgcheck_count, status]
117
+ end
118
+
119
+ # Determine overall coverage status
120
+ coverage_status = determine_coverage_status(result[:semantic_mapping])
121
+
122
+ puts "\nSEMANTIC ERROR MAPPING: #{coverage_status}"
123
+ render_gh_style_table(headers, rows)
124
+ end
125
+
126
+ # Display requirement-based mapping
127
+ display_requirement_mapping(result)
128
+ end
129
+
130
+ # Compare all test files and generate a comprehensive report
131
+ def compare_all_test_files
132
+ puts "\n#{Paint['=' * 100, :cyan]}"
133
+ puts Paint["🔍 COMPREHENSIVE SVGCHECK COMPATIBILITY ANALYSIS", :magenta,
134
+ :bold]
135
+ puts Paint["=" * 100, :cyan]
136
+
137
+ test_files = Dir.glob(File.join(@reports_dir,
138
+ "*.svgcheck.yaml")).map do |report_file|
139
+ File.basename(report_file, ".svgcheck.yaml")
140
+ end
141
+
142
+ results = []
143
+ validator = SvgConform::Validator.new
144
+
145
+ test_files.each do |filename|
146
+ next if filename == "rfc.xml" # Skip non-SVG files
147
+
148
+ begin
149
+ # Load svgcheck report
150
+ svgcheck_report_path = File.join(@reports_dir,
151
+ "#{filename}.svgcheck.yaml")
152
+ # Use the new external checker parser
153
+ parser = SvgConform::ExternalCheckers::Svgcheck::Parser.new
154
+ error_content = File.read(svgcheck_report_path)
155
+ svgcheck_report = parser.parse(error_content, nil, filename: filename)
156
+
157
+ # Generate SvgConform report
158
+ svg_file_path = "svgcheck/svgcheck/Tests/#{filename}"
159
+ if File.exist?(svg_file_path)
160
+ svg_content = File.read(svg_file_path)
161
+ validation_result = validator.validate(svg_content,
162
+ profile: @profile)
163
+ svg_conform_report = SvgConform::ConformanceReport.from_svg_conform_result(filename, validation_result,
164
+ profile: @profile, use_svgcheck_mapping: true)
165
+
166
+ # Compare reports
167
+ compare_reports(svg_conform_report, svgcheck_report, filename)
168
+
169
+ # Track results
170
+ results << {
171
+ filename: filename,
172
+ svg_conform_errors: svg_conform_report.errors.total_count,
173
+ svgcheck_errors: svgcheck_report.errors.total_count,
174
+ match: svg_conform_report.errors.total_count == svgcheck_report.errors.total_count,
175
+ }
176
+ else
177
+ puts "\n⚠ SVG file not found: #{svg_file_path}"
178
+ end
179
+ rescue StandardError => e
180
+ puts "\n❌ Error processing #{filename}: #{e.message}"
181
+ end
182
+ end
183
+
184
+ # Overall summary
185
+ display_overall_summary(results)
186
+ end
187
+
188
+ private
189
+
190
+ def generate_comparison_result(svg_conform_report, svgcheck_report,
191
+ filename)
192
+ # Group errors by semantic meaning for comparison
193
+ svg_conform_errors = group_errors_by_semantic_meaning(svg_conform_report.errors.issues)
194
+ svgcheck_errors = group_errors_by_semantic_meaning(svgcheck_report.errors.issues)
195
+
196
+ # Create semantic mapping
197
+ all_error_types = (svg_conform_errors.keys + svgcheck_errors.keys).uniq.sort
198
+ semantic_mapping = {}
199
+
200
+ all_error_types.each do |error_type|
201
+ svg_count = svg_conform_errors[error_type] || 0
202
+ svgcheck_count = svgcheck_errors[error_type] || 0
203
+ semantic_mapping[error_type] = {
204
+ svg_conform: svg_count,
205
+ svgcheck: svgcheck_count,
206
+ }
207
+ end
208
+
209
+ # Find message-level differences
210
+ svg_conform_messages = group_errors_by_message(svg_conform_report.errors.issues)
211
+ svgcheck_messages = group_errors_by_message(svgcheck_report.errors.issues)
212
+
213
+ extra_errors = []
214
+ missing_errors = []
215
+
216
+ svg_conform_messages.each do |message, count|
217
+ svgcheck_count = svgcheck_messages[message] || 0
218
+ extra_errors << message if count > svgcheck_count
219
+ end
220
+
221
+ svgcheck_messages.each do |message, count|
222
+ svg_conform_count = svg_conform_messages[message] || 0
223
+ missing_errors << message if count > svg_conform_count
224
+ end
225
+
226
+ {
227
+ filename: filename,
228
+ svg_conform_valid: svg_conform_report.valid,
229
+ svgcheck_valid: svgcheck_report.valid,
230
+ valid_match: svg_conform_report.valid == svgcheck_report.valid,
231
+ svg_conform_errors: svg_conform_report.errors.total_count,
232
+ svgcheck_errors: svgcheck_report.errors.total_count,
233
+ errors_match: svg_conform_report.errors.total_count == svgcheck_report.errors.total_count,
234
+ svg_conform_error_types: svg_conform_report.errors.by_requirement.keys.length,
235
+ svgcheck_error_types: svgcheck_report.errors.by_requirement.keys.length,
236
+ error_types_match: svg_conform_report.errors.by_requirement.keys.length == svgcheck_report.errors.by_requirement.keys.length,
237
+ semantic_mapping: semantic_mapping,
238
+ extra_errors: extra_errors,
239
+ missing_errors: missing_errors,
240
+ }
241
+ end
242
+
243
+ def display_summary_table(svg_conform_report, svgcheck_report)
244
+ headers = ["Metric", "SvgConform", "svgcheck", "Match?"]
245
+ rows = [
246
+ ["Valid", svg_conform_report.valid, svgcheck_report.valid,
247
+ match_status(svg_conform_report.valid, svgcheck_report.valid)],
248
+ ["Total Errors", svg_conform_report.errors.total_count, svgcheck_report.errors.total_count,
249
+ match_status(svg_conform_report.errors.total_count, svgcheck_report.errors.total_count)],
250
+ ["Error Types", svg_conform_report.errors.by_requirement.keys.length,
251
+ svgcheck_report.errors.by_requirement.keys.length, match_status(svg_conform_report.errors.by_requirement.keys.length, svgcheck_report.errors.by_requirement.keys.length)],
252
+ ]
253
+
254
+ puts "\nSUMMARY:"
255
+ render_gh_style_table(headers, rows)
256
+ end
257
+
258
+ def display_error_mapping_table(svg_conform_report, svgcheck_report)
259
+ # Group errors by semantic meaning for comparison
260
+ svg_conform_errors = group_errors_by_semantic_meaning(svg_conform_report.errors.issues)
261
+ svgcheck_errors = group_errors_by_semantic_meaning(svgcheck_report.errors.issues)
262
+
263
+ # Create mapping table
264
+ headers = ["Error Type", "SvgConform Count", "svgcheck Count", "Status"]
265
+ rows = []
266
+
267
+ # All unique error types
268
+ all_error_types = (svg_conform_errors.keys + svgcheck_errors.keys).uniq.sort
269
+
270
+ all_error_types.each do |error_type|
271
+ svg_count = svg_conform_errors[error_type] || 0
272
+ svgcheck_count = svgcheck_errors[error_type] || 0
273
+ status = if svg_count == svgcheck_count && svg_count.positive?
274
+ "✓ MATCH"
275
+ elsif svg_count == svgcheck_count && svg_count.zero?
276
+ "✓ BOTH NONE"
277
+ elsif svg_count > svgcheck_count
278
+ "⚠ EXTRA"
279
+ elsif svg_count < svgcheck_count
280
+ "⚠ MISSING"
281
+ else
282
+ "? UNKNOWN"
283
+ end
284
+
285
+ rows << [error_type, svg_count, svgcheck_count, status]
286
+ end
287
+
288
+ puts "\nSEMANTIC ERROR MAPPING:"
289
+ render_gh_style_table(headers, rows)
290
+ end
291
+
292
+ def group_errors_by_message(errors)
293
+ errors.group_by(&:message).transform_values(&:length)
294
+ end
295
+
296
+ # Group errors by their semantic meaning rather than exact message text
297
+ def group_errors_by_semantic_meaning(errors)
298
+ errors.group_by do |error|
299
+ extract_semantic_meaning(error)
300
+ end.transform_values(&:length)
301
+ end
302
+
303
+ # Extract semantic meaning from an error for comparison
304
+ # Uses the configuration-driven approach from SvgcheckCompatibilityEngine
305
+ def extract_semantic_meaning(error)
306
+ # Use the compatibility engine for consistent semantic extraction
307
+ engine = SvgConform::ExternalCheckers::Svgcheck::CompatibilityEngine.new
308
+ engine.extract_semantic_meaning(error)
309
+ end
310
+
311
+ # Helper methods to extract semantic components from error messages
312
+ def extract_attribute_from_message(message)
313
+ # Extract attribute name from various message formats
314
+ case message
315
+ when /attribute ['"]([^'"]+)['"]/
316
+ ::Regexp.last_match(1)
317
+ when /in attribute ['"]([^'"]+)['"]/
318
+ ::Regexp.last_match(1)
319
+ when /property ['"]([^'"]+)['"]/
320
+ ::Regexp.last_match(1)
321
+ else
322
+ "unknown"
323
+ end
324
+ end
325
+
326
+ def extract_value_from_message(message)
327
+ # Extract value from various message formats
328
+ if message =~ /value ['"]([^'"]+)['"]/
329
+ ::Regexp.last_match(1)
330
+ elsif message =~ /Color ['"]([^'"]+)['"]/
331
+ ::Regexp.last_match(1)
332
+ else
333
+ "unknown"
334
+ end
335
+ end
336
+
337
+ def extract_element_from_message(message)
338
+ # Extract element name from various message formats
339
+ if message =~ /element ['"]([^'"]+)['"]/
340
+ ::Regexp.last_match(1)
341
+ elsif message =~ /Element ['"]([^'"]+)['"]/
342
+ ::Regexp.last_match(1)
343
+ else
344
+ "unknown"
345
+ end
346
+ end
347
+
348
+ def extract_element_from_context(message)
349
+ # Extract element name from context like "not allowed on element 'g'"
350
+ if message =~ /on element ['"]([^'"]+)['"]/
351
+ ::Regexp.last_match(1)
352
+ elsif message =~ /element ['"]([^'"]+)['"]/
353
+ ::Regexp.last_match(1)
354
+ else
355
+ "unknown"
356
+ end
357
+ end
358
+
359
+ def extract_style_property_from_message(message)
360
+ # Extract style property name from style promotion messages
361
+ if message =~ /Style property ['"]([^'"]+)['"]/
362
+ ::Regexp.last_match(1)
363
+ else
364
+ "unknown"
365
+ end
366
+ end
367
+
368
+ def match_status(value1, value2)
369
+ value1 == value2 ? "✓" : "✗"
370
+ end
371
+
372
+ # Render table using table_tennis with proper formatting and color scale
373
+ def render_gh_style_table(headers, rows)
374
+ # Convert to hash format that TableTennis expects
375
+ table_data = []
376
+
377
+ rows.each do |row|
378
+ row_hash = {}
379
+ headers.each_with_index do |header, index|
380
+ row_hash[header.to_sym] = row[index]
381
+ end
382
+ table_data << row_hash
383
+ end
384
+
385
+ # Determine color scale options based on table content
386
+ color_scale_options = determine_color_scale_options(headers, rows)
387
+
388
+ # Create TableTennis table with options
389
+ options = {
390
+ color: true,
391
+ separators: true,
392
+ **color_scale_options,
393
+ }
394
+
395
+ puts TableTennis.new(table_data, options)
396
+ end
397
+
398
+ # Determine appropriate color scale options for TableTennis
399
+ def determine_color_scale_options(headers, _rows)
400
+ # Analyze the table content to determine the best color scale
401
+ if headers.include?("Match?") || headers.include?("Status")
402
+ # For status tables, use mark to highlight matches/mismatches
403
+ {
404
+ mark: lambda { |row|
405
+ status_col = headers.index("Match?") || headers.index("Status")
406
+ return false unless status_col
407
+
408
+ status_value = row[status_col].to_s
409
+ status_value.include?("✓") || status_value.include?("MATCH")
410
+ },
411
+ zebra: true,
412
+ }
413
+ elsif headers.any? { |h| h.include?("Count") || h.include?("Errors") }
414
+ # For numeric tables, use color scales on count columns
415
+ count_columns = headers.each_with_index.select do |h, _i|
416
+ h.include?("Count") || h.include?("Errors")
417
+ end.map(&:last)
418
+ if count_columns.any?
419
+ # Use the first count column for color scaling
420
+ column_name = headers[count_columns.first].downcase.gsub(/\s+/,
421
+ "_").to_sym
422
+ {
423
+ color_scales: column_name,
424
+ zebra: true,
425
+ }
426
+ else
427
+ { zebra: true }
428
+ end
429
+ else
430
+ # Default options
431
+ { zebra: true }
432
+ end
433
+ end
434
+
435
+ # Apply color scale to individual cells
436
+ def apply_color_scale(cell, col_index, _row_index, color_scale)
437
+ cell_str = cell.to_s
438
+
439
+ case color_scale[:type]
440
+ when :status
441
+ apply_status_color_scale(cell_str)
442
+ when :numeric
443
+ apply_numeric_color_scale(cell_str, col_index)
444
+ else
445
+ apply_default_color_scale(cell_str)
446
+ end
447
+ end
448
+
449
+ # Apply status-based color scale
450
+ def apply_status_color_scale(cell_str)
451
+ if cell_str.include?("✓") || cell_str.include?("MATCH")
452
+ Paint[cell_str, :green]
453
+ elsif cell_str.include?("✗") || cell_str.include?("MISMATCH")
454
+ Paint[cell_str, :red]
455
+ elsif cell_str.include?("⚠") || cell_str.include?("EXTRA") || cell_str.include?("MISSING")
456
+ Paint[cell_str, :yellow]
457
+ elsif cell_str.include?("BOTH NONE")
458
+ Paint[cell_str, :cyan]
459
+ else
460
+ cell_str
461
+ end
462
+ end
463
+
464
+ # Apply numeric-based color scale
465
+ def apply_numeric_color_scale(cell_str, _col_index)
466
+ # Only apply to numeric columns
467
+ return cell_str unless cell_str.match?(/^\d+$/)
468
+
469
+ value = cell_str.to_i
470
+
471
+ if value.zero?
472
+ Paint[cell_str, :cyan]
473
+ elsif value <= 5
474
+ Paint[cell_str, :green]
475
+ elsif value <= 20
476
+ Paint[cell_str, :yellow]
477
+ else
478
+ Paint[cell_str, :red]
479
+ end
480
+ end
481
+
482
+ # Apply default color scale
483
+ def apply_default_color_scale(cell_str)
484
+ # Basic highlighting for common patterns
485
+ if cell_str.include?("true") || cell_str.include?("false")
486
+ if cell_str.include?("true")
487
+ Paint[cell_str,
488
+ :green]
489
+ else
490
+ Paint[cell_str, :red]
491
+ end
492
+ elsif cell_str.match?(/^\d+%$/) # Percentage
493
+ percentage = cell_str.to_f
494
+ if percentage >= 90
495
+ Paint[cell_str, :green]
496
+ elsif percentage >= 70
497
+ Paint[cell_str, :yellow]
498
+ else
499
+ Paint[cell_str, :red]
500
+ end
501
+ else
502
+ cell_str
503
+ end
504
+ end
505
+
506
+ # Display requirement-based error mapping with colors
507
+ def display_requirement_mapping(result)
508
+ # Get the original reports to access detailed error information
509
+ filename = result[:filename]
510
+
511
+ begin
512
+ # Load reports again to get detailed error information
513
+ svgcheck_report_path = File.join(@reports_dir,
514
+ "#{filename}.svgcheck.yaml")
515
+ # Use the new external checker parser
516
+ parser = SvgConform::ExternalCheckers::Svgcheck::Parser.new
517
+ error_content = File.read(svgcheck_report_path)
518
+ svgcheck_report = parser.parse(error_content, nil, filename: filename)
519
+
520
+ # Generate SvgConform report
521
+ validator = SvgConform::Validator.new
522
+ svg_file_path = "svgcheck/svgcheck/Tests/#{filename}"
523
+ svg_content = File.read(svg_file_path)
524
+ validation_result = validator.validate(svg_content, profile: @profile)
525
+ svg_conform_report = SvgConform::ConformanceReport.from_svg_conform_result(filename, validation_result,
526
+ profile: @profile, use_svgcheck_mapping: true)
527
+
528
+ # Group errors by requirement
529
+ svg_conform_by_requirement = group_errors_by_requirement(svg_conform_report.errors.issues)
530
+ svgcheck_by_requirement = group_errors_by_requirement(svgcheck_report.errors.issues)
531
+
532
+ # Identify unmapped svgcheck errors
533
+ unmapped_svgcheck_errors = identify_unmapped_svgcheck_errors(
534
+ svgcheck_report, svg_conform_report
535
+ )
536
+
537
+ # Get all requirements that have errors in either system
538
+ all_requirements = (svg_conform_by_requirement.keys + svgcheck_by_requirement.keys).uniq.sort
539
+
540
+ if all_requirements.any?
541
+ puts "\n#{Paint['🔍 REQUIREMENT MAPPING:', :blue, :bold]}"
542
+
543
+ all_requirements.each do |requirement|
544
+ svg_errors = svg_conform_by_requirement[requirement] || []
545
+ svgcheck_errors = svgcheck_by_requirement[requirement] || []
546
+
547
+ # Display requirement header
548
+ requirement_name = format_requirement_name(requirement)
549
+ puts "\n #{Paint["🔍 #{requirement_name}:", :cyan, :bold]}"
550
+
551
+ # Display SvgConform errors
552
+ if svg_errors.any?
553
+ svg_errors.each do |error|
554
+ puts " #{Paint['•', :blue]} #{Paint[error.message, :blue]}"
555
+ end
556
+ else
557
+ puts " #{Paint['• (no errors)', :gray]}"
558
+ end
559
+
560
+ # Display mapped svgcheck errors
561
+ if svgcheck_errors.any?
562
+ svgcheck_errors.each do |error|
563
+ puts " #{Paint['↳ [svgcheck]',
564
+ :magenta]} #{Paint[error.message, :magenta]}"
565
+ end
566
+ else
567
+ puts " #{Paint['↳ [svgcheck] (no errors)', :gray]}"
568
+ end
569
+ end
570
+ else
571
+ puts "\n#{Paint['🎉 ✓ NO ERRORS IN EITHER SYSTEM! 🎉', :green, :bold]}"
572
+ end
573
+
574
+ # Display unmapped svgcheck errors in RED
575
+ if unmapped_svgcheck_errors.any?
576
+ puts "\n#{Paint['🚨 UNMAPPED SVGCHECK ERRORS:', :red, :bold]}"
577
+ puts Paint["These svgcheck errors do not correspond to any SvgConform requirement:",
578
+ :red]
579
+ unmapped_svgcheck_errors.each do |error|
580
+ puts " #{Paint['⚠️ [svgcheck]', :red,
581
+ :bold]} #{Paint[error.message, :red]}"
582
+ end
583
+ end
584
+ rescue StandardError
585
+ # Fallback to the old display if we can't load detailed reports
586
+ if result[:extra_errors]&.any?
587
+ puts "\n#{Paint['⚠️ EXTRA ERRORS IN SVGCONFORM:', :yellow, :bold]}"
588
+ result[:extra_errors].each do |error|
589
+ puts " • #{error}"
590
+ end
591
+ end
592
+
593
+ if result[:missing_errors]&.any?
594
+ puts "\n#{Paint['❌ MISSING ERRORS IN SVGCONFORM:', :red, :bold]}"
595
+ result[:missing_errors].each do |error|
596
+ puts " • #{error}"
597
+ end
598
+ end
599
+
600
+ return unless result[:extra_errors]&.empty? && result[:missing_errors]&.empty?
601
+
602
+ puts "\n#{Paint['🎉 ✓ ERROR MESSAGES MATCH PERFECTLY! 🎉', :green,
603
+ :bold]}"
604
+ end
605
+ end
606
+
607
+ # Group errors by requirement ID, using compatibility engine for svgcheck errors
608
+ def group_errors_by_requirement(errors)
609
+ # Check if these are svgcheck errors that need remapping
610
+ engine = SvgConform::ExternalCheckers::Svgcheck::CompatibilityEngine.new
611
+
612
+ grouped = {}
613
+ errors.each do |error|
614
+ # For svgcheck errors, try to remap using the compatibility engine
615
+ requirement_id = if error.respond_to?(:requirement_id)
616
+ # Try to remap using the compatibility engine for all svgcheck errors
617
+ mapped_req = engine.map_requirement_for_svgcheck(error)
618
+ mapped_req || error.requirement_id
619
+ else
620
+ error.requirement_id
621
+ end
622
+
623
+ grouped[requirement_id] ||= []
624
+ grouped[requirement_id] << error
625
+ end
626
+
627
+ grouped
628
+ end
629
+
630
+ # Identify svgcheck errors that don't map to any SvgConform requirement
631
+ def identify_unmapped_svgcheck_errors(svgcheck_report, _svg_conform_report)
632
+ unmapped_errors = []
633
+
634
+ # Find svgcheck errors that are marked as "unmapped" by the parser
635
+ svgcheck_report.errors.issues.each do |svgcheck_error|
636
+ # Check if this error was categorized as "unmapped" by the parser
637
+ unmapped_errors << svgcheck_error if svgcheck_error.requirement_id == "unmapped"
638
+ end
639
+
640
+ unmapped_errors
641
+ end
642
+
643
+ # Format requirement name for display
644
+ def format_requirement_name(requirement_id)
645
+ case requirement_id
646
+ when "color_restrictions"
647
+ "color_restrictions requirement"
648
+ when "viewbox_required"
649
+ "viewbox_required requirement"
650
+ when "viewbox"
651
+ "viewbox requirement"
652
+ when "namespace"
653
+ "namespace requirement"
654
+ when "namespace_validation"
655
+ "namespace_validation requirement"
656
+ when "allowed_elements"
657
+ "allowed_elements requirement"
658
+ when "font_family"
659
+ "font_family requirement"
660
+ when "no_external_css"
661
+ "no_external_css requirement"
662
+ when "namespace_attributes"
663
+ "namespace_attributes requirement"
664
+ when "style_promotion"
665
+ "style_promotion requirement"
666
+ when "invalid_id_references"
667
+ "invalid_id_references requirement"
668
+ when "forbidden_content"
669
+ "forbidden_content requirement"
670
+ when "property_value"
671
+ "property_value requirement"
672
+ when "link_validation"
673
+ "link_validation requirement"
674
+ when "id_reference"
675
+ "id_reference requirement"
676
+ when "style_syntax"
677
+ "style_syntax requirement"
678
+ when "style_validation"
679
+ "style_validation requirement"
680
+ when "datatype_validation"
681
+ "datatype_validation requirement"
682
+ when "required_attribute"
683
+ "required_attribute requirement"
684
+ when "unmapped"
685
+ "🚨 UNMAPPED SVGCHECK ERROR"
686
+ else
687
+ "🚨 UNKNOWN REQUIREMENT: #{requirement_id}"
688
+ end
689
+ end
690
+
691
+ # Determine coverage status based on semantic mapping
692
+ def determine_coverage_status(semantic_mapping)
693
+ return "🤷 NO DATA" unless semantic_mapping&.any?
694
+
695
+ svg_conform_errors = semantic_mapping.values.sum do |counts|
696
+ counts[:svg_conform]
697
+ end
698
+ svgcheck_errors = semantic_mapping.values.sum do |counts|
699
+ counts[:svgcheck]
700
+ end
701
+
702
+ # Check if SvgConform covers all svgcheck errors
703
+ all_svgcheck_covered = semantic_mapping.all? do |_error_type, counts|
704
+ svg_count = counts[:svg_conform]
705
+ svgcheck_count = counts[:svgcheck]
706
+
707
+ # SvgConform covers this error type if it has at least as many errors as svgcheck
708
+ svg_count >= svgcheck_count
709
+ end
710
+
711
+ # Check if it's a perfect match (100% both ways)
712
+ perfect_match = semantic_mapping.all? do |_error_type, counts|
713
+ counts[:svg_conform] == counts[:svgcheck]
714
+ end
715
+
716
+ if perfect_match && svg_conform_errors.positive? && svgcheck_errors.positive?
717
+ "🎯 FULL MATCH"
718
+ elsif all_svgcheck_covered && svgcheck_errors.positive?
719
+ "✅ COVERED"
720
+ else
721
+ "⚠️ MISMATCH"
722
+ end
723
+ end
724
+
725
+ def display_overall_summary(results)
726
+ puts "\n#{Paint['=' * 100, :cyan]}"
727
+ puts Paint["📈 OVERALL COMPATIBILITY SUMMARY", :yellow, :bold]
728
+ puts Paint["=" * 100, :cyan]
729
+
730
+ headers = ["File", "SvgConform Errors", "svgcheck Errors", "Match?"]
731
+ rows = []
732
+
733
+ results.each do |result|
734
+ status = result[:match] ? "✓" : "✗"
735
+ rows << [
736
+ result[:filename],
737
+ result[:svg_conform_errors],
738
+ result[:svgcheck_errors],
739
+ status,
740
+ ]
741
+ end
742
+
743
+ # Add totals
744
+ total_files = results.length
745
+ matching_files = results.count { |r| r[:match] }
746
+ match_percentage = total_files.positive? ? (matching_files.to_f / total_files * 100).round(1) : 0
747
+
748
+ rows << ["---", "---", "---", "---"]
749
+ rows << ["TOTAL", "#{total_files} files", "#{matching_files} matches",
750
+ "#{match_percentage}%"]
751
+
752
+ render_gh_style_table(headers, rows)
753
+
754
+ puts "\n#{Paint['📊 COMPATIBILITY METRICS:', :blue, :bold]}"
755
+ puts " 🗂️ Total files processed: #{Paint[total_files.to_s, :cyan,
756
+ :bold]}"
757
+ puts " ✅ Files with matching error counts: #{Paint[matching_files.to_s,
758
+ :green, :bold]}"
759
+
760
+ # Color-code the percentage based on value
761
+ percentage_color = if match_percentage >= 90
762
+ :green
763
+ elsif match_percentage >= 70
764
+ :yellow
765
+ else
766
+ :red
767
+ end
768
+ puts " 📈 Compatibility percentage: #{Paint["#{match_percentage}%",
769
+ percentage_color, :bold]}"
770
+ end
771
+ end
772
+ end