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.
- checksums.yaml +7 -0
- data/.github/workflows/rake.yml +15 -0
- data/.github/workflows/release.yml +23 -0
- data/.github/workflows/svgcheck-compatibility.yml +135 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/.rubocop_todo.yml +183 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Gemfile +14 -0
- data/README.adoc +1384 -0
- data/Rakefile +15 -0
- data/config/profiles/base.yml +34 -0
- data/config/profiles/lucid_fix.yml +37 -0
- data/config/profiles/metanorma.yml +212 -0
- data/config/profiles/no_external_css.yml +20 -0
- data/config/profiles/svg_1_2_rfc.yml +284 -0
- data/config/profiles/svg_1_2_rfc_with_rdf.yml +145 -0
- data/config/svgcheck_mapping.yml +180 -0
- data/docs/profiles.adoc +547 -0
- data/docs/rdf_metadata_support.adoc +212 -0
- data/docs/remediation.adoc +732 -0
- data/docs/requirements.adoc +709 -0
- data/examples/demo.rb +116 -0
- data/examples/requirements_demo.rb +240 -0
- data/exe/svg_conform +7 -0
- data/lib/svg_conform/batch_report.rb +70 -0
- data/lib/svg_conform/cli.rb +107 -0
- data/lib/svg_conform/commands/check.rb +390 -0
- data/lib/svg_conform/commands/profiles.rb +118 -0
- data/lib/svg_conform/commands/svgcheck.rb +90 -0
- data/lib/svg_conform/commands/svgcheck_compare.rb +92 -0
- data/lib/svg_conform/commands/svgcheck_compatibility.rb +43 -0
- data/lib/svg_conform/commands/svgcheck_generate.rb +262 -0
- data/lib/svg_conform/compatibility/analysis_context.rb +68 -0
- data/lib/svg_conform/compatibility/comparison_result.rb +147 -0
- data/lib/svg_conform/compatibility/compatibility_analyzer.rb +85 -0
- data/lib/svg_conform/compatibility/file_processor.rb +109 -0
- data/lib/svg_conform/compatibility/pattern_discovery.rb +319 -0
- data/lib/svg_conform/compatibility/report_formatter.rb +359 -0
- data/lib/svg_conform/compatibility/svg_analysis_engine.rb +316 -0
- data/lib/svg_conform/compatibility/validity_analysis.rb +252 -0
- data/lib/svg_conform/compatibility/xml_analysis_engine.rb +198 -0
- data/lib/svg_conform/compatibility_analyzer.rb +285 -0
- data/lib/svg_conform/conformance_report.rb +267 -0
- data/lib/svg_conform/constants.rb +199 -0
- data/lib/svg_conform/css_color.rb +262 -0
- data/lib/svg_conform/document.rb +203 -0
- data/lib/svg_conform/external_checkers/svgcheck/compatibility_engine.rb +166 -0
- data/lib/svg_conform/external_checkers/svgcheck/output_generator.rb +101 -0
- data/lib/svg_conform/external_checkers/svgcheck/parser.rb +200 -0
- data/lib/svg_conform/external_checkers/svgcheck/report_comparator.rb +175 -0
- data/lib/svg_conform/external_checkers/svgcheck/report_generator.rb +82 -0
- data/lib/svg_conform/external_checkers/svgcheck/validation_pipeline.rb +249 -0
- data/lib/svg_conform/external_checkers/svgcheck.rb +56 -0
- data/lib/svg_conform/external_checkers.rb +34 -0
- data/lib/svg_conform/fixer.rb +56 -0
- data/lib/svg_conform/profile.rb +164 -0
- data/lib/svg_conform/profiles.rb +60 -0
- data/lib/svg_conform/remediation_engine.rb +92 -0
- data/lib/svg_conform/remediation_result.rb +36 -0
- data/lib/svg_conform/remediation_runner.rb +225 -0
- data/lib/svg_conform/remediations/base_remediation.rb +165 -0
- data/lib/svg_conform/remediations/color_remediation.rb +226 -0
- data/lib/svg_conform/remediations/font_embedding_remediation.rb +145 -0
- data/lib/svg_conform/remediations/font_remediation.rb +122 -0
- data/lib/svg_conform/remediations/image_embedding_remediation.rb +154 -0
- data/lib/svg_conform/remediations/invalid_id_references_remediation.rb +129 -0
- data/lib/svg_conform/remediations/namespace_attribute_remediation.rb +244 -0
- data/lib/svg_conform/remediations/namespace_remediation.rb +151 -0
- data/lib/svg_conform/remediations/no_external_css_remediation.rb +192 -0
- data/lib/svg_conform/remediations/style_promotion_remediation.rb +93 -0
- data/lib/svg_conform/remediations/viewbox_remediation.rb +127 -0
- data/lib/svg_conform/remediations.rb +40 -0
- data/lib/svg_conform/report_comparator.rb +772 -0
- data/lib/svg_conform/requirements/allowed_elements_requirement.rb +367 -0
- data/lib/svg_conform/requirements/base_requirement.rb +98 -0
- data/lib/svg_conform/requirements/color_restrictions_requirement.rb +126 -0
- data/lib/svg_conform/requirements/element_requirement_config.rb +75 -0
- data/lib/svg_conform/requirements/font_family_requirement.rb +133 -0
- data/lib/svg_conform/requirements/forbidden_content_requirement.rb +60 -0
- data/lib/svg_conform/requirements/id_reference_requirement.rb +133 -0
- data/lib/svg_conform/requirements/invalid_id_references_requirement.rb +132 -0
- data/lib/svg_conform/requirements/link_validation_requirement.rb +55 -0
- data/lib/svg_conform/requirements/namespace_attributes_requirement.rb +211 -0
- data/lib/svg_conform/requirements/namespace_requirement.rb +294 -0
- data/lib/svg_conform/requirements/no_external_css_requirement.rb +132 -0
- data/lib/svg_conform/requirements/no_external_fonts_requirement.rb +121 -0
- data/lib/svg_conform/requirements/no_external_images_requirement.rb +91 -0
- data/lib/svg_conform/requirements/style_promotion_requirement.rb +72 -0
- data/lib/svg_conform/requirements/style_requirement.rb +226 -0
- data/lib/svg_conform/requirements/viewbox_required_requirement.rb +96 -0
- data/lib/svg_conform/requirements.rb +49 -0
- data/lib/svg_conform/semantic_comparator.rb +829 -0
- data/lib/svg_conform/validation_context.rb +408 -0
- data/lib/svg_conform/validation_result.rb +146 -0
- data/lib/svg_conform/validator.rb +91 -0
- data/lib/svg_conform/version.rb +5 -0
- data/lib/svg_conform.rb +68 -0
- data/lib/tasks/fixtures.rake +321 -0
- data/lib/tasks/svgcheck.rake +111 -0
- data/reference-docs/SVG-1.2-RFC.rnc.txt +1676 -0
- data/reference-docs/Scalable Vector Graphics (SVG) 1.1 (Second Edition).html +40764 -0
- data/reference-docs/Scalable Vector Graphics (SVG) Tiny 1.2 Specification.html +44591 -0
- data/reference-docs/rfc7996.txt +2971 -0
- data/sig/svg_conform.rbs +4 -0
- data/spec/fixtures/allowed_elements/inputs/basic_violations.svg +21 -0
- data/spec/fixtures/allowed_elements/repair/basic_violations.svg +18 -0
- data/spec/fixtures/color_restrictions/inputs/basic_violations.svg +23 -0
- data/spec/fixtures/color_restrictions/repair/basic_violations.svg +23 -0
- data/spec/fixtures/comprehensive/inputs/multiple_violations.svg +21 -0
- data/spec/fixtures/comprehensive/repair/multiple_violations.svg +16 -0
- data/spec/fixtures/font_family/inputs/basic_violations.svg +17 -0
- data/spec/fixtures/font_family/repair/basic_violations.svg +17 -0
- data/spec/fixtures/forbidden_content/inputs/basic_violations.svg +27 -0
- data/spec/fixtures/forbidden_content/repair/basic_violations.svg +19 -0
- data/spec/fixtures/id_reference/inputs/basic_violations.svg +17 -0
- data/spec/fixtures/id_reference/repair/basic_violations.svg +15 -0
- data/spec/fixtures/link_validation/inputs/basic_violations.svg +20 -0
- data/spec/fixtures/link_validation/repair/basic_violations.svg +16 -0
- data/spec/fixtures/lucid/inputs/simple.svg +67 -0
- data/spec/fixtures/lucid/repair/simple.svg +63 -0
- data/spec/fixtures/namespace/inputs/basic_violations.svg +20 -0
- data/spec/fixtures/namespace/repair/basic_violations.svg +17 -0
- data/spec/fixtures/namespace_attributes/inputs/basic_violations.svg +16 -0
- data/spec/fixtures/namespace_attributes/repair/basic_violations.svg +15 -0
- data/spec/fixtures/no_external_css/inputs/basic_violations.svg +20 -0
- data/spec/fixtures/no_external_css/repair/basic_violations.svg +18 -0
- data/spec/fixtures/style/inputs/basic_violations.svg +22 -0
- data/spec/fixtures/style/repair/basic_violations.svg +22 -0
- data/spec/fixtures/style_promotion/inputs/basic_test.svg +15 -0
- data/spec/fixtures/style_promotion/repair/basic_test.svg +15 -0
- data/spec/fixtures/svg_1_2_rfc/inputs/allowed_elements_violations.svg +18 -0
- data/spec/fixtures/svg_1_2_rfc/inputs/color_restrictions_violations.svg +23 -0
- data/spec/fixtures/svgcheck/check/DrawBerry-sample-2.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/DrawBerry-sample-2.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/DrawBerry-sample-2.svg.out +23 -0
- data/spec/fixtures/svgcheck/check/IETF-test.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/IETF-test.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/IETF-test.svg.out +20 -0
- data/spec/fixtures/svgcheck/check/circle.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/circle.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/circle.svg.out +2 -0
- data/spec/fixtures/svgcheck/check/colors.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/colors.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/colors.svg.out +13 -0
- data/spec/fixtures/svgcheck/check/dia-sample-svg.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/dia-sample-svg.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/dia-sample-svg.svg.out +76 -0
- data/spec/fixtures/svgcheck/check/example-dot.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/example-dot.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/example-dot.svg.out +11 -0
- data/spec/fixtures/svgcheck/check/full-tiny.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/full-tiny.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/full-tiny.svg.out +5835 -0
- data/spec/fixtures/svgcheck/check/good.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/good.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/good.svg.out +1 -0
- data/spec/fixtures/svgcheck/check/httpbis-proxy20-fig6.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/httpbis-proxy20-fig6.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/httpbis-proxy20-fig6.svg.out +5 -0
- data/spec/fixtures/svgcheck/check/malformed.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/malformed.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/malformed.svg.out +8 -0
- data/spec/fixtures/svgcheck/check/rfc-svg.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/rfc-svg.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/rfc-svg.svg.out +1 -0
- data/spec/fixtures/svgcheck/check/rfc.xml.code +1 -0
- data/spec/fixtures/svgcheck/check/rfc.xml.err +0 -0
- data/spec/fixtures/svgcheck/check/rfc.xml.out +2 -0
- data/spec/fixtures/svgcheck/check/rgb.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/rgb.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/rgb.svg.out +9 -0
- data/spec/fixtures/svgcheck/check/svg-wordle.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/svg-wordle.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/svg-wordle.svg.out +508 -0
- data/spec/fixtures/svgcheck/check/threshold.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/threshold.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/threshold.svg.out +20 -0
- data/spec/fixtures/svgcheck/check/utf8.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/utf8.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/utf8.svg.out +162 -0
- data/spec/fixtures/svgcheck/check/viewBox-both.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/viewBox-both.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/viewBox-both.svg.out +4 -0
- data/spec/fixtures/svgcheck/check/viewBox-height.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/viewBox-height.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/viewBox-height.svg.out +3 -0
- data/spec/fixtures/svgcheck/check/viewBox-none.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/viewBox-none.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/viewBox-none.svg.out +3 -0
- data/spec/fixtures/svgcheck/check/viewBox-width.svg.code +1 -0
- data/spec/fixtures/svgcheck/check/viewBox-width.svg.err +0 -0
- data/spec/fixtures/svgcheck/check/viewBox-width.svg.out +3 -0
- data/spec/fixtures/svgcheck/inputs/DrawBerry-sample-2.svg +28 -0
- data/spec/fixtures/svgcheck/inputs/IETF-test.svg +28 -0
- data/spec/fixtures/svgcheck/inputs/circle.svg +3 -0
- data/spec/fixtures/svgcheck/inputs/colors.svg +18 -0
- data/spec/fixtures/svgcheck/inputs/dia-sample-svg.svg +47 -0
- data/spec/fixtures/svgcheck/inputs/example-dot.svg +75 -0
- data/spec/fixtures/svgcheck/inputs/full-tiny.svg +16194 -0
- data/spec/fixtures/svgcheck/inputs/good.svg +19 -0
- data/spec/fixtures/svgcheck/inputs/httpbis-proxy20-fig6.svg +2 -0
- data/spec/fixtures/svgcheck/inputs/malformed.svg +11 -0
- data/spec/fixtures/svgcheck/inputs/rfc-svg.svg +1028 -0
- data/spec/fixtures/svgcheck/inputs/rfc.xml +37 -0
- data/spec/fixtures/svgcheck/inputs/rgb.svg +9 -0
- data/spec/fixtures/svgcheck/inputs/svg-wordle.svg +330 -0
- data/spec/fixtures/svgcheck/inputs/threshold.svg +26 -0
- data/spec/fixtures/svgcheck/inputs/utf8.svg +448 -0
- data/spec/fixtures/svgcheck/inputs/viewBox-both.svg +3 -0
- data/spec/fixtures/svgcheck/inputs/viewBox-height.svg +3 -0
- data/spec/fixtures/svgcheck/inputs/viewBox-none.svg +3 -0
- data/spec/fixtures/svgcheck/inputs/viewBox-width.svg +3 -0
- data/spec/fixtures/svgcheck/repair/DrawBerry-sample-2.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/DrawBerry-sample-2.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/DrawBerry-sample-2.svg.file +0 -0
- data/spec/fixtures/svgcheck/repair/DrawBerry-sample-2.svg.out +23 -0
- data/spec/fixtures/svgcheck/repair/IETF-test.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/IETF-test.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/IETF-test.svg.file +29 -0
- data/spec/fixtures/svgcheck/repair/IETF-test.svg.out +20 -0
- data/spec/fixtures/svgcheck/repair/circle.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/circle.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/circle.svg.file +4 -0
- data/spec/fixtures/svgcheck/repair/circle.svg.out +2 -0
- data/spec/fixtures/svgcheck/repair/colors.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/colors.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/colors.svg.file +19 -0
- data/spec/fixtures/svgcheck/repair/colors.svg.out +13 -0
- data/spec/fixtures/svgcheck/repair/dia-sample-svg.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/dia-sample-svg.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/dia-sample-svg.svg.file +47 -0
- data/spec/fixtures/svgcheck/repair/dia-sample-svg.svg.out +76 -0
- data/spec/fixtures/svgcheck/repair/example-dot.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/example-dot.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/example-dot.svg.file +69 -0
- data/spec/fixtures/svgcheck/repair/example-dot.svg.out +11 -0
- data/spec/fixtures/svgcheck/repair/good.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/good.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/good.svg.file +0 -0
- data/spec/fixtures/svgcheck/repair/good.svg.out +1 -0
- data/spec/fixtures/svgcheck/repair/httpbis-proxy20-fig6.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/httpbis-proxy20-fig6.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/httpbis-proxy20-fig6.svg.file +73 -0
- data/spec/fixtures/svgcheck/repair/httpbis-proxy20-fig6.svg.out +5 -0
- data/spec/fixtures/svgcheck/repair/malformed.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/malformed.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/malformed.svg.file +9 -0
- data/spec/fixtures/svgcheck/repair/malformed.svg.out +8 -0
- data/spec/fixtures/svgcheck/repair/rfc-svg.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/rfc-svg.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/rfc-svg.svg.file +0 -0
- data/spec/fixtures/svgcheck/repair/rfc-svg.svg.out +1 -0
- data/spec/fixtures/svgcheck/repair/rfc.xml.code +1 -0
- data/spec/fixtures/svgcheck/repair/rfc.xml.err +0 -0
- data/spec/fixtures/svgcheck/repair/rfc.xml.file +37 -0
- data/spec/fixtures/svgcheck/repair/rfc.xml.out +2 -0
- data/spec/fixtures/svgcheck/repair/rgb.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/rgb.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/rgb.svg.file +10 -0
- data/spec/fixtures/svgcheck/repair/rgb.svg.out +9 -0
- data/spec/fixtures/svgcheck/repair/svg-wordle.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/svg-wordle.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/svg-wordle.svg.file +112 -0
- data/spec/fixtures/svgcheck/repair/svg-wordle.svg.out +508 -0
- data/spec/fixtures/svgcheck/repair/threshold.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/threshold.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/threshold.svg.file +27 -0
- data/spec/fixtures/svgcheck/repair/threshold.svg.out +20 -0
- data/spec/fixtures/svgcheck/repair/utf8.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/utf8.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/utf8.svg.file +381 -0
- data/spec/fixtures/svgcheck/repair/utf8.svg.out +162 -0
- data/spec/fixtures/svgcheck/repair/viewBox-both.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/viewBox-both.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/viewBox-both.svg.file +4 -0
- data/spec/fixtures/svgcheck/repair/viewBox-both.svg.out +4 -0
- data/spec/fixtures/svgcheck/repair/viewBox-height.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/viewBox-height.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/viewBox-height.svg.file +4 -0
- data/spec/fixtures/svgcheck/repair/viewBox-height.svg.out +3 -0
- data/spec/fixtures/svgcheck/repair/viewBox-none.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/viewBox-none.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/viewBox-none.svg.file +4 -0
- data/spec/fixtures/svgcheck/repair/viewBox-none.svg.out +3 -0
- data/spec/fixtures/svgcheck/repair/viewBox-width.svg.code +1 -0
- data/spec/fixtures/svgcheck/repair/viewBox-width.svg.err +0 -0
- data/spec/fixtures/svgcheck/repair/viewBox-width.svg.file +4 -0
- data/spec/fixtures/svgcheck/repair/viewBox-width.svg.out +3 -0
- data/spec/fixtures/viewbox_required/inputs/missing_viewbox.svg +10 -0
- data/spec/fixtures/viewbox_required/repair/missing_viewbox.svg +10 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/svg_conform/batch_report_spec.rb +99 -0
- data/spec/svg_conform/commands/check_command_spec.rb +90 -0
- data/spec/svg_conform/commands/profiles_command_spec.rb +20 -0
- data/spec/svg_conform/commands/svgcheck_compare_command_spec.rb +13 -0
- data/spec/svg_conform/commands/svgcheck_compatibility_command_spec.rb +13 -0
- data/spec/svg_conform/commands/svgcheck_generate_command_spec.rb +14 -0
- data/spec/svg_conform/profiles/base_profile_spec.rb +42 -0
- data/spec/svg_conform/profiles/lucid_fix_profile_spec.rb +46 -0
- data/spec/svg_conform/profiles/lucid_profile_spec.rb +84 -0
- data/spec/svg_conform/profiles/metanorma_profile_spec.rb +62 -0
- data/spec/svg_conform/profiles/no_external_css_profile_spec.rb +66 -0
- data/spec/svg_conform/profiles/svg_1_2_rfc_profile_spec.rb +200 -0
- data/spec/svg_conform/profiles/svg_1_2_rfc_with_rdf_profile_spec.rb +81 -0
- data/spec/svg_conform/remediations/color_remediation_spec.rb +95 -0
- data/spec/svg_conform/remediations/font_embedding_remediation_spec.rb +20 -0
- data/spec/svg_conform/remediations/font_remediation_spec.rb +95 -0
- data/spec/svg_conform/remediations/image_embedding_remediation_spec.rb +20 -0
- data/spec/svg_conform/remediations/invalid_id_references_remediation_spec.rb +97 -0
- data/spec/svg_conform/remediations/namespace_attribute_remediation_spec.rb +97 -0
- data/spec/svg_conform/remediations/namespace_remediation_spec.rb +95 -0
- data/spec/svg_conform/remediations/no_external_css_remediation_spec.rb +97 -0
- data/spec/svg_conform/remediations/style_promotion_remediation_spec.rb +97 -0
- data/spec/svg_conform/remediations/viewbox_remediation_spec.rb +95 -0
- data/spec/svg_conform/requirements/allowed_elements_requirement_spec.rb +118 -0
- data/spec/svg_conform/requirements/color_restrictions_requirement_spec.rb +168 -0
- data/spec/svg_conform/requirements/font_family_requirement_spec.rb +188 -0
- data/spec/svg_conform/requirements/forbidden_content_requirement_spec.rb +195 -0
- data/spec/svg_conform/requirements/id_reference_requirement_spec.rb +78 -0
- data/spec/svg_conform/requirements/invalid_id_references_requirement_spec.rb +78 -0
- data/spec/svg_conform/requirements/link_validation_requirement_spec.rb +78 -0
- data/spec/svg_conform/requirements/namespace_attributes_requirement_spec.rb +86 -0
- data/spec/svg_conform/requirements/namespace_requirement_spec.rb +184 -0
- data/spec/svg_conform/requirements/no_external_css_requirement_spec.rb +78 -0
- data/spec/svg_conform/requirements/no_external_fonts_requirement_spec.rb +20 -0
- data/spec/svg_conform/requirements/no_external_images_requirement_spec.rb +20 -0
- data/spec/svg_conform/requirements/style_promotion_requirement_spec.rb +78 -0
- data/spec/svg_conform/requirements/style_requirement_spec.rb +76 -0
- data/spec/svg_conform/requirements/viewbox_required_requirement_spec.rb +165 -0
- data/spec/svg_conform_spec.rb +32 -0
- data/spec/svgcheck_compatibility_spec.rb +355 -0
- data/svg_conform.gemspec +35 -0
- metadata +436 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "parser"
|
|
4
|
+
require_relative "../../conformance_report"
|
|
5
|
+
|
|
6
|
+
module SvgConform
|
|
7
|
+
module ExternalCheckers
|
|
8
|
+
module Svgcheck
|
|
9
|
+
# Generates ConformanceReport objects from svgcheck outputs
|
|
10
|
+
class ReportGenerator
|
|
11
|
+
def initialize(parser: nil)
|
|
12
|
+
@parser = parser || Parser.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Generate a report from svgcheck output files
|
|
16
|
+
def generate_from_files(filename, output_file: nil, error_file: nil,
|
|
17
|
+
code_file: nil)
|
|
18
|
+
output_content = output_file && File.exist?(output_file) ? File.read(output_file) : nil
|
|
19
|
+
error_content = error_file && File.exist?(error_file) ? File.read(error_file) : nil
|
|
20
|
+
exit_code = code_file && File.exist?(code_file) ? File.read(code_file).strip.to_i : 0
|
|
21
|
+
|
|
22
|
+
generate_from_content(filename, output_content, error_content,
|
|
23
|
+
exit_code)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Generate a report from svgcheck output content
|
|
27
|
+
def generate_from_content(filename, output_content,
|
|
28
|
+
error_content = nil, exit_code = 0)
|
|
29
|
+
# svgcheck writes validation messages to stderr, not stdout
|
|
30
|
+
# So we use error_content as the main content to parse
|
|
31
|
+
main_content = error_content || output_content
|
|
32
|
+
|
|
33
|
+
report = @parser.parse(main_content, nil, filename: filename)
|
|
34
|
+
|
|
35
|
+
# Set additional metadata
|
|
36
|
+
report.profile = "svg_1_2_rfc" # svgcheck is specifically for SVG 1.2 RFC
|
|
37
|
+
report.version = detect_svgcheck_version
|
|
38
|
+
|
|
39
|
+
# Use exit code to determine validity if needed
|
|
40
|
+
if exit_code != 0 && report.valid
|
|
41
|
+
report.valid = false
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
report
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Generate reports for both check and repair modes
|
|
48
|
+
def generate_comparative_reports(filename, check_outputs: nil,
|
|
49
|
+
repair_outputs: nil)
|
|
50
|
+
reports = {}
|
|
51
|
+
|
|
52
|
+
if check_outputs
|
|
53
|
+
reports[:check] = generate_from_content(
|
|
54
|
+
filename,
|
|
55
|
+
check_outputs[:stdout],
|
|
56
|
+
check_outputs[:stderr],
|
|
57
|
+
check_outputs[:exit_code],
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if repair_outputs
|
|
62
|
+
reports[:repair] = generate_from_content(
|
|
63
|
+
filename,
|
|
64
|
+
repair_outputs[:stdout],
|
|
65
|
+
repair_outputs[:stderr],
|
|
66
|
+
repair_outputs[:exit_code],
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
reports
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def detect_svgcheck_version
|
|
76
|
+
# Try to detect svgcheck version - placeholder for now
|
|
77
|
+
"unknown"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "output_generator"
|
|
4
|
+
require_relative "report_generator"
|
|
5
|
+
require_relative "report_comparator"
|
|
6
|
+
require_relative "../../validator"
|
|
7
|
+
require_relative "../../profiles"
|
|
8
|
+
|
|
9
|
+
module SvgConform
|
|
10
|
+
module ExternalCheckers
|
|
11
|
+
module Svgcheck
|
|
12
|
+
# Pipeline to ensure svg_conform outputs are compatible with svgcheck
|
|
13
|
+
class ValidationPipeline
|
|
14
|
+
attr_reader :test_files_dir, :output_dir
|
|
15
|
+
|
|
16
|
+
def initialize(test_files_dir: "svgcheck/svgcheck/Tests",
|
|
17
|
+
output_dir: "spec/fixtures/svgcheck")
|
|
18
|
+
@test_files_dir = test_files_dir
|
|
19
|
+
@output_dir = output_dir
|
|
20
|
+
@generator = OutputGenerator.new
|
|
21
|
+
@report_generator = ReportGenerator.new
|
|
22
|
+
@comparator = ReportComparator.new(test_files_dir: test_files_dir,
|
|
23
|
+
svgcheck_outputs_dir: output_dir)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Run complete validation pipeline
|
|
27
|
+
def run_validation_pipeline(mode: :both)
|
|
28
|
+
results = {
|
|
29
|
+
stage: "complete_pipeline",
|
|
30
|
+
total_files: 0,
|
|
31
|
+
successful_generations: 0,
|
|
32
|
+
successful_comparisons: 0,
|
|
33
|
+
failed_generations: 0,
|
|
34
|
+
failed_comparisons: 0,
|
|
35
|
+
compatibility_issues: [],
|
|
36
|
+
summary: {},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
test_files = find_test_files
|
|
40
|
+
results[:total_files] = test_files.length
|
|
41
|
+
|
|
42
|
+
puts "🚀 Starting validation pipeline for #{test_files.length} files..."
|
|
43
|
+
|
|
44
|
+
# Stage 1: Generate svgcheck outputs
|
|
45
|
+
puts "\n📋 Stage 1: Generating svgcheck outputs..."
|
|
46
|
+
generation_results = generate_outputs_for_files(test_files, mode)
|
|
47
|
+
results[:successful_generations] = generation_results[:successful]
|
|
48
|
+
results[:failed_generations] = generation_results[:failed]
|
|
49
|
+
|
|
50
|
+
# Stage 2: Compare reports
|
|
51
|
+
puts "\n📋 Stage 2: Comparing svg_conform vs svgcheck reports..."
|
|
52
|
+
comparison_results = compare_all_reports
|
|
53
|
+
results[:successful_comparisons] = comparison_results[:matching]
|
|
54
|
+
results[:failed_comparisons] = comparison_results[:different]
|
|
55
|
+
results[:compatibility_issues] = comparison_results[:differences]
|
|
56
|
+
|
|
57
|
+
# Stage 3: Generate summary
|
|
58
|
+
results[:summary] = generate_pipeline_summary(results)
|
|
59
|
+
|
|
60
|
+
results
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Validate output compatibility for a single file
|
|
64
|
+
def validate_file_compatibility(filename)
|
|
65
|
+
result = {
|
|
66
|
+
filename: filename,
|
|
67
|
+
svg_conform_validation: nil,
|
|
68
|
+
svgcheck_outputs: nil,
|
|
69
|
+
comparison: nil,
|
|
70
|
+
compatible: false,
|
|
71
|
+
issues: [],
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
begin
|
|
75
|
+
# Generate svg_conform validation
|
|
76
|
+
result[:svg_conform_validation] =
|
|
77
|
+
generate_svg_conform_validation(filename)
|
|
78
|
+
|
|
79
|
+
# Generate svgcheck outputs
|
|
80
|
+
result[:svgcheck_outputs] =
|
|
81
|
+
@generator.generate(filename, mode: :both)
|
|
82
|
+
|
|
83
|
+
# Compare outputs
|
|
84
|
+
if result[:svgcheck_outputs].values.all? { |r| r[:success] }
|
|
85
|
+
result[:comparison] = @comparator.compare_file(filename)
|
|
86
|
+
result[:compatible] = result[:comparison][:identical]
|
|
87
|
+
|
|
88
|
+
unless result[:compatible]
|
|
89
|
+
result[:issues] =
|
|
90
|
+
extract_compatibility_issues(result[:comparison])
|
|
91
|
+
end
|
|
92
|
+
else
|
|
93
|
+
result[:issues] << "Failed to generate svgcheck outputs"
|
|
94
|
+
end
|
|
95
|
+
rescue StandardError => e
|
|
96
|
+
result[:issues] << "Error during validation: #{e.message}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
result
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Generate comprehensive compatibility report
|
|
103
|
+
def generate_compatibility_report
|
|
104
|
+
pipeline_results = run_validation_pipeline
|
|
105
|
+
|
|
106
|
+
{
|
|
107
|
+
timestamp: Time.now.iso8601,
|
|
108
|
+
pipeline_version: SvgConform::VERSION,
|
|
109
|
+
total_files: pipeline_results[:total_files],
|
|
110
|
+
compatibility_score: calculate_compatibility_score(pipeline_results),
|
|
111
|
+
results: pipeline_results,
|
|
112
|
+
recommendations: generate_recommendations(pipeline_results),
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
def find_test_files
|
|
119
|
+
return [] unless Dir.exist?(@test_files_dir)
|
|
120
|
+
|
|
121
|
+
pattern = File.join(@test_files_dir, "*.{svg,xml}")
|
|
122
|
+
Dir.glob(pattern).map { |path| File.basename(path) }.sort
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def generate_outputs_for_files(test_files, mode)
|
|
126
|
+
results = { successful: 0, failed: 0, errors: [] }
|
|
127
|
+
|
|
128
|
+
test_files.each do |filename|
|
|
129
|
+
outputs = @generator.generate(filename, mode: mode)
|
|
130
|
+
|
|
131
|
+
if outputs.values.all? { |result| result[:success] }
|
|
132
|
+
results[:successful] += 1
|
|
133
|
+
puts " ✅ #{filename}"
|
|
134
|
+
else
|
|
135
|
+
results[:failed] += 1
|
|
136
|
+
puts " ❌ #{filename}"
|
|
137
|
+
results[:errors] << { filename: filename,
|
|
138
|
+
error: "Generation failed" }
|
|
139
|
+
end
|
|
140
|
+
rescue StandardError => e
|
|
141
|
+
results[:failed] += 1
|
|
142
|
+
puts " ❌ #{filename}: #{e.message}"
|
|
143
|
+
results[:errors] << { filename: filename, error: e.message }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
results
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def compare_all_reports
|
|
150
|
+
@comparator.compare_all_test_files
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def generate_svg_conform_validation(filename)
|
|
154
|
+
input_file = File.join(@test_files_dir, filename)
|
|
155
|
+
return nil unless File.exist?(input_file)
|
|
156
|
+
|
|
157
|
+
profile = SvgConform::Profiles.load_profile(:svg_1_2_rfc)
|
|
158
|
+
validator = SvgConform::Validator.new(profile)
|
|
159
|
+
validator.validate_file(input_file)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def extract_compatibility_issues(comparison)
|
|
163
|
+
issues = []
|
|
164
|
+
|
|
165
|
+
# Add basic comparison issues
|
|
166
|
+
comparison[:differences]&.each do |diff|
|
|
167
|
+
issues << "Report difference: #{diff}"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Add semantic comparison issues
|
|
171
|
+
if comparison[:semantic] && comparison[:semantic][:issues].any?
|
|
172
|
+
comparison[:semantic][:issues].each do |semantic_issue|
|
|
173
|
+
issues << "Semantic mismatch: #{semantic_issue[:semantic_key]} " \
|
|
174
|
+
"(svg_conform: #{semantic_issue[:svg_conform_count]}, " \
|
|
175
|
+
"svgcheck: #{semantic_issue[:svgcheck_count]})"
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
issues
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def calculate_compatibility_score(pipeline_results)
|
|
183
|
+
return 0.0 if pipeline_results[:total_files].zero?
|
|
184
|
+
|
|
185
|
+
successful_files = pipeline_results[:successful_comparisons]
|
|
186
|
+
total_files = pipeline_results[:total_files]
|
|
187
|
+
|
|
188
|
+
(successful_files.to_f / total_files * 100).round(2)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def generate_recommendations(pipeline_results)
|
|
192
|
+
recommendations = []
|
|
193
|
+
|
|
194
|
+
# Generation issues
|
|
195
|
+
if pipeline_results[:failed_generations].positive?
|
|
196
|
+
recommendations << {
|
|
197
|
+
category: "generation",
|
|
198
|
+
priority: "high",
|
|
199
|
+
message: "#{pipeline_results[:failed_generations]} files failed svgcheck output generation. " \
|
|
200
|
+
"Check svgcheck installation and test file accessibility.",
|
|
201
|
+
}
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Compatibility issues
|
|
205
|
+
if pipeline_results[:failed_comparisons].positive?
|
|
206
|
+
recommendations << {
|
|
207
|
+
category: "compatibility",
|
|
208
|
+
priority: "high",
|
|
209
|
+
message: "#{pipeline_results[:failed_comparisons]} files show compatibility issues. " \
|
|
210
|
+
"Review profile configuration and requirement mappings.",
|
|
211
|
+
}
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Profile issues
|
|
215
|
+
compatibility_score = calculate_compatibility_score(pipeline_results)
|
|
216
|
+
if compatibility_score < 90.0
|
|
217
|
+
recommendations << {
|
|
218
|
+
category: "profile",
|
|
219
|
+
priority: "medium",
|
|
220
|
+
message: "Compatibility score is #{compatibility_score}%. " \
|
|
221
|
+
"Consider reviewing svg_1_2_rfc profile configuration against svgcheck source.",
|
|
222
|
+
}
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
recommendations
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def generate_pipeline_summary(results)
|
|
229
|
+
{
|
|
230
|
+
compatibility_score: calculate_compatibility_score(results),
|
|
231
|
+
generation_success_rate: calculate_success_rate(
|
|
232
|
+
results[:successful_generations], results[:total_files]
|
|
233
|
+
),
|
|
234
|
+
comparison_success_rate: calculate_success_rate(
|
|
235
|
+
results[:successful_comparisons], results[:total_files]
|
|
236
|
+
),
|
|
237
|
+
total_issues: results[:compatibility_issues].length,
|
|
238
|
+
}
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def calculate_success_rate(successful, total)
|
|
242
|
+
return 0.0 if total.zero?
|
|
243
|
+
|
|
244
|
+
(successful.to_f / total * 100).round(2)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "svgcheck/parser"
|
|
4
|
+
require_relative "svgcheck/compatibility_engine"
|
|
5
|
+
require_relative "svgcheck/report_generator"
|
|
6
|
+
require_relative "svgcheck/output_generator"
|
|
7
|
+
require_relative "svgcheck/validation_pipeline"
|
|
8
|
+
require_relative "svgcheck/report_comparator"
|
|
9
|
+
|
|
10
|
+
module SvgConform
|
|
11
|
+
module ExternalCheckers
|
|
12
|
+
# SVGCheck external checker integration
|
|
13
|
+
module Svgcheck
|
|
14
|
+
# Main svgcheck checker class
|
|
15
|
+
class Checker < BaseChecker
|
|
16
|
+
SVGCHECK_EXECUTABLE = "python3"
|
|
17
|
+
SVGCHECK_MODULE_PATH = "svgcheck/svgcheck"
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
super(name: "svgcheck", version: detect_version)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def available?
|
|
24
|
+
command_available?(SVGCHECK_EXECUTABLE) &&
|
|
25
|
+
Dir.exist?(SVGCHECK_MODULE_PATH)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def generate_outputs(input_file, mode: :both)
|
|
29
|
+
generator = OutputGenerator.new(
|
|
30
|
+
svgcheck_exec: SVGCHECK_EXECUTABLE,
|
|
31
|
+
svgcheck_path: SVGCHECK_MODULE_PATH,
|
|
32
|
+
)
|
|
33
|
+
generator.generate(input_file, mode: mode)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def parse_output(output_content, error_content = nil)
|
|
37
|
+
parser = Parser.new
|
|
38
|
+
parser.parse(output_content, error_content)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def detect_version
|
|
44
|
+
# Try to detect svgcheck version - placeholder for now
|
|
45
|
+
"unknown"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def command_available?(command)
|
|
49
|
+
system("which #{command} > /dev/null 2>&1") ||
|
|
50
|
+
system("where #{command} > NUL 2>&1") || # Windows
|
|
51
|
+
File.executable?(command) # Direct path
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SvgConform
|
|
4
|
+
# Namespace for external SVG conformance checkers
|
|
5
|
+
# This allows for multiple external checker integrations while keeping them separate
|
|
6
|
+
module ExternalCheckers
|
|
7
|
+
autoload :Svgcheck, "svg_conform/external_checkers/svgcheck"
|
|
8
|
+
|
|
9
|
+
# Base class for external checker integrations
|
|
10
|
+
class BaseChecker
|
|
11
|
+
attr_reader :name, :version
|
|
12
|
+
|
|
13
|
+
def initialize(name:, version: nil)
|
|
14
|
+
@name = name
|
|
15
|
+
@version = version
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Generate outputs for a given file - to be implemented by subclasses
|
|
19
|
+
def generate_outputs(input_file, mode: :both)
|
|
20
|
+
raise NotImplementedError, "Subclasses must implement generate_outputs"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Parse checker output into a ConformanceReport - to be implemented by subclasses
|
|
24
|
+
def parse_output(output_content, error_content = nil)
|
|
25
|
+
raise NotImplementedError, "Subclasses must implement parse_output"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Check if the checker is available on the system
|
|
29
|
+
def available?
|
|
30
|
+
raise NotImplementedError, "Subclasses must implement available?"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SvgConform
|
|
4
|
+
# Utility class for applying fixes to SVG documents
|
|
5
|
+
class Fixer
|
|
6
|
+
attr_reader :document, :fixes_applied
|
|
7
|
+
|
|
8
|
+
def initialize(document)
|
|
9
|
+
@document = document
|
|
10
|
+
@fixes_applied = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Apply a single fix
|
|
14
|
+
def apply_fix(fix)
|
|
15
|
+
return false unless fix.respond_to?(:call) || fix.respond_to?(:apply)
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
result = if fix.respond_to?(:call)
|
|
19
|
+
fix.call
|
|
20
|
+
else
|
|
21
|
+
fix.apply
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
@fixes_applied << fix if result
|
|
25
|
+
result
|
|
26
|
+
rescue StandardError
|
|
27
|
+
false
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Apply multiple fixes
|
|
32
|
+
def apply_fixes(fixes)
|
|
33
|
+
success_count = 0
|
|
34
|
+
|
|
35
|
+
fixes.each do |fix|
|
|
36
|
+
success_count += 1 if apply_fix(fix)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
success_count
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Apply all fixable issues from a validation result
|
|
43
|
+
def apply_validation_fixes(validation_result)
|
|
44
|
+
fixable_issues = (validation_result.errors + validation_result.warnings).select(&:fixable?)
|
|
45
|
+
apply_fixes(fixable_issues.map(&:fix))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def fixes_count
|
|
49
|
+
@fixes_applied.size
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def to_xml
|
|
53
|
+
@document.to_xml
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
require_relative "requirements"
|
|
5
|
+
require_relative "remediations"
|
|
6
|
+
|
|
7
|
+
module SvgConform
|
|
8
|
+
# Base class for SVG validation profiles using lutaml-model serialization
|
|
9
|
+
class Profile < Lutaml::Model::Serializable
|
|
10
|
+
attribute :name, :string
|
|
11
|
+
attribute :description, :string
|
|
12
|
+
attribute :import, :string
|
|
13
|
+
attribute :requirements, SvgConform::Requirements::BaseRequirement,
|
|
14
|
+
collection: true, polymorphic: true
|
|
15
|
+
attribute :remediations, SvgConform::Remediations::BaseRemediation,
|
|
16
|
+
collection: true, polymorphic: true
|
|
17
|
+
|
|
18
|
+
def self.build_class_map(dir_name, namespace, excluded_file)
|
|
19
|
+
target_dir = File.expand_path(dir_name, __dir__)
|
|
20
|
+
class_map = {}
|
|
21
|
+
|
|
22
|
+
Dir.glob(File.join(target_dir,
|
|
23
|
+
"*_#{dir_name.chomp('s')}.rb")).each do |file|
|
|
24
|
+
filename = File.basename(file, ".rb")
|
|
25
|
+
next if filename == excluded_file
|
|
26
|
+
|
|
27
|
+
class_name = filename.split("_").map(&:capitalize).join
|
|
28
|
+
class_map[class_name] = "SvgConform::#{namespace}::#{class_name}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class_map
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Build class maps dynamically from filesystem
|
|
35
|
+
REQUIREMENTS_CLASS_MAP = build_class_map(
|
|
36
|
+
"requirements", "Requirements", "base_requirement"
|
|
37
|
+
)
|
|
38
|
+
REMEDIATIONS_CLASS_MAP = build_class_map(
|
|
39
|
+
"remediations", "Remediations", "base_remediation"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
yaml do
|
|
43
|
+
map "name", to: :name
|
|
44
|
+
map "description", to: :description
|
|
45
|
+
map "import", to: :import
|
|
46
|
+
map "requirements", to: :requirements, polymorphic: {
|
|
47
|
+
attribute: "type",
|
|
48
|
+
class_map: REQUIREMENTS_CLASS_MAP,
|
|
49
|
+
}
|
|
50
|
+
map "remediations", to: :remediations, polymorphic: {
|
|
51
|
+
attribute: "type",
|
|
52
|
+
class_map: REMEDIATIONS_CLASS_MAP,
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# New requirement methods
|
|
57
|
+
def add_requirement(requirement)
|
|
58
|
+
self.requirements ||= []
|
|
59
|
+
requirements << requirement
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def remove_requirement(requirement_id)
|
|
64
|
+
return self unless requirements
|
|
65
|
+
|
|
66
|
+
requirements.reject! { |req| req.id == requirement_id }
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def has_requirement?(requirement_id)
|
|
71
|
+
return false unless requirements
|
|
72
|
+
|
|
73
|
+
requirements.any? { |req| req.id == requirement_id }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def get_requirement(requirement_id)
|
|
77
|
+
return nil unless requirements
|
|
78
|
+
|
|
79
|
+
requirements.find { |req| req.id == requirement_id }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def requirement_count
|
|
83
|
+
requirements&.size || 0
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# New remediation methods
|
|
87
|
+
def add_remediation(remediation)
|
|
88
|
+
self.remediations ||= []
|
|
89
|
+
remediations << remediation
|
|
90
|
+
self
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def remove_remediation(remediation_id)
|
|
94
|
+
return self unless remediations
|
|
95
|
+
|
|
96
|
+
remediations.reject! { |rem| rem.id == remediation_id }
|
|
97
|
+
self
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def has_remediation?(remediation_id)
|
|
101
|
+
return false unless remediations
|
|
102
|
+
|
|
103
|
+
remediations.any? { |rem| rem.id == remediation_id }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def get_remediation(remediation_id)
|
|
107
|
+
return nil unless remediations
|
|
108
|
+
|
|
109
|
+
remediations.find { |rem| rem.id == remediation_id }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def remediation_count
|
|
113
|
+
remediations&.size || 0
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def validate(document)
|
|
117
|
+
context = ValidationContext.new(document, self)
|
|
118
|
+
|
|
119
|
+
# Validate using requirements system
|
|
120
|
+
requirements&.each do |requirement|
|
|
121
|
+
requirement.validate_document(document, context)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
ValidationResult.new(document, self, context)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def validate_file(file_path)
|
|
128
|
+
document = Document.from_file(file_path)
|
|
129
|
+
validate(document)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Apply remediations to a document after validation
|
|
133
|
+
def apply_remediations(document)
|
|
134
|
+
# Run validation to get failed requirements
|
|
135
|
+
validation_result = validate(document)
|
|
136
|
+
|
|
137
|
+
all_changes = []
|
|
138
|
+
|
|
139
|
+
# Apply remediations for failed requirements
|
|
140
|
+
remediations&.each do |remediation|
|
|
141
|
+
if remediation.should_execute?(validation_result.failed_requirements)
|
|
142
|
+
changes = remediation.apply(document, nil)
|
|
143
|
+
all_changes.concat(changes) if changes
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
all_changes
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Simple YAML loading methods for individual profiles
|
|
151
|
+
def self.load_from_file(file_path)
|
|
152
|
+
yaml_content = File.read(file_path)
|
|
153
|
+
from_yaml(yaml_content)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def self.save_to_file(profile, file_path)
|
|
157
|
+
File.write(file_path, profile.to_yaml)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def to_s
|
|
161
|
+
"#{name}: #{description} (#{requirement_count} checks, #{remediation_count} remediations)"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "profile"
|
|
4
|
+
|
|
5
|
+
module SvgConform
|
|
6
|
+
module Profiles
|
|
7
|
+
PROFILES_DIR = File.expand_path("../../config/profiles", __dir__)
|
|
8
|
+
@@cache = {}
|
|
9
|
+
|
|
10
|
+
def self.get(profile_id)
|
|
11
|
+
case profile_id
|
|
12
|
+
when Symbol, String
|
|
13
|
+
load_profile(profile_id.to_s)
|
|
14
|
+
when Profile
|
|
15
|
+
profile_id
|
|
16
|
+
else
|
|
17
|
+
raise ProfileError, "Invalid profile: #{profile_id}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.available_profiles
|
|
22
|
+
return @@cache.keys.map(&:to_sym) if @@cache.any?
|
|
23
|
+
|
|
24
|
+
profile_files = Dir.glob(File.join(PROFILES_DIR, "*.yml"))
|
|
25
|
+
profile_files.map { |file| File.basename(file, ".yml").to_sym }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.load(profile_name)
|
|
29
|
+
load_profile(profile_name.to_s)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.list
|
|
33
|
+
available_profiles.map(&:to_s)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.clear_cache!
|
|
37
|
+
@@cache.clear
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.load_profile(profile_name)
|
|
41
|
+
return @@cache[profile_name] if @@cache[profile_name]
|
|
42
|
+
|
|
43
|
+
profile_file = File.join(PROFILES_DIR, "#{profile_name}.yml")
|
|
44
|
+
|
|
45
|
+
unless File.exist?(profile_file)
|
|
46
|
+
raise ProfileError,
|
|
47
|
+
"Profile not found: #{profile_name} (expected: #{profile_file})"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
profile = Profile.load_from_file(profile_file)
|
|
52
|
+
@@cache[profile_name] = profile
|
|
53
|
+
profile
|
|
54
|
+
# rescue => e
|
|
55
|
+
# raise e
|
|
56
|
+
# raise ProfileError, "Failed to load profile #{profile_name}: #{e.message}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|