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,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_requirement"
|
|
4
|
+
|
|
5
|
+
module SvgConform
|
|
6
|
+
module Requirements
|
|
7
|
+
# Validates that no external font references are present
|
|
8
|
+
# All fonts must be embedded in the SVG
|
|
9
|
+
class NoExternalFontsRequirement < BaseRequirement
|
|
10
|
+
attribute :type, :string, default: -> { "NoExternalFontsRequirement" }
|
|
11
|
+
attribute :check_font_face, :boolean, default: true
|
|
12
|
+
attribute :check_style_fonts, :boolean, default: true
|
|
13
|
+
|
|
14
|
+
yaml do
|
|
15
|
+
map "id", to: :id
|
|
16
|
+
map "description", to: :description
|
|
17
|
+
map "type", to: :type
|
|
18
|
+
map "check_font_face", to: :check_font_face
|
|
19
|
+
map "check_style_fonts", to: :check_style_fonts
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def check(node, context)
|
|
23
|
+
return unless element?(node)
|
|
24
|
+
|
|
25
|
+
case node.name
|
|
26
|
+
when "style"
|
|
27
|
+
check_style_element(node, context) if check_style_fonts
|
|
28
|
+
when "font-face"
|
|
29
|
+
check_font_face_element(node, context) if check_font_face
|
|
30
|
+
else
|
|
31
|
+
check_style_attribute(node, context) if check_style_fonts
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def should_check_node?(node, context = nil)
|
|
36
|
+
return false unless element?(node)
|
|
37
|
+
return false if context&.node_structurally_invalid?(node)
|
|
38
|
+
|
|
39
|
+
node.name == "style" ||
|
|
40
|
+
node.name == "font-face" ||
|
|
41
|
+
has_style_attribute?(node)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def check_style_element(node, context)
|
|
47
|
+
# Check for @font-face with external src in style elements
|
|
48
|
+
content = node.text || ""
|
|
49
|
+
|
|
50
|
+
# Match @font-face blocks
|
|
51
|
+
content.scan(/@font-face\s*\{([^}]+)\}/m) do |match|
|
|
52
|
+
font_face_content = match[0]
|
|
53
|
+
|
|
54
|
+
# Check for src with url() that is not data: URI
|
|
55
|
+
if font_face_content =~ /src\s*:\s*url\s*\(\s*['"]?([^'")\s]+)['"]?\s*\)/i
|
|
56
|
+
url = ::Regexp.last_match(1)
|
|
57
|
+
unless embedded_font?(url)
|
|
58
|
+
context.add_error(
|
|
59
|
+
requirement_id: id,
|
|
60
|
+
message: "External font reference not allowed: #{url}. Fonts must be embedded as data URIs.",
|
|
61
|
+
node: node,
|
|
62
|
+
severity: :error,
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def check_font_face_element(node, context)
|
|
70
|
+
# Check font-face-src and font-face-uri elements
|
|
71
|
+
node.xpath(".//font-face-src/font-face-uri").each do |uri_node|
|
|
72
|
+
href = get_attribute(uri_node,
|
|
73
|
+
"xlink:href") || get_attribute(uri_node, "href")
|
|
74
|
+
if href && !embedded_font?(href)
|
|
75
|
+
context.add_error(
|
|
76
|
+
requirement_id: id,
|
|
77
|
+
message: "External font reference in font-face-uri not allowed: #{href}. Fonts must be embedded as data URIs.",
|
|
78
|
+
node: uri_node,
|
|
79
|
+
severity: :error,
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def check_style_attribute(node, context)
|
|
86
|
+
style_value = get_attribute(node, "style")
|
|
87
|
+
return unless style_value
|
|
88
|
+
|
|
89
|
+
# Check for font-family with url() references
|
|
90
|
+
return unless style_value =~ /font-family\s*:\s*.*url\s*\(\s*['"]?([^'")\s]+)['"]?\s*\)/i
|
|
91
|
+
|
|
92
|
+
url = ::Regexp.last_match(1)
|
|
93
|
+
return if embedded_font?(url)
|
|
94
|
+
|
|
95
|
+
context.add_error(
|
|
96
|
+
requirement_id: id,
|
|
97
|
+
message: "External font URL in style attribute not allowed: #{url}. Fonts must be embedded as data URIs.",
|
|
98
|
+
node: node,
|
|
99
|
+
severity: :error,
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def has_style_attribute?(node)
|
|
104
|
+
!get_attribute(node, "style").nil?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def embedded_font?(url)
|
|
108
|
+
return true if url.nil? || url.empty?
|
|
109
|
+
|
|
110
|
+
# Data URLs are embedded
|
|
111
|
+
return true if url.start_with?("data:")
|
|
112
|
+
|
|
113
|
+
# Fragment identifiers (internal references) are allowed
|
|
114
|
+
return true if url.start_with?("#")
|
|
115
|
+
|
|
116
|
+
# Everything else is external
|
|
117
|
+
false
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_requirement"
|
|
4
|
+
|
|
5
|
+
module SvgConform
|
|
6
|
+
module Requirements
|
|
7
|
+
# Validates that no external image references are present
|
|
8
|
+
# All images must be embedded in the SVG
|
|
9
|
+
class NoExternalImagesRequirement < BaseRequirement
|
|
10
|
+
attribute :type, :string, default: -> { "NoExternalImagesRequirement" }
|
|
11
|
+
attribute :check_image_elements, :boolean, default: true
|
|
12
|
+
attribute :check_style_images, :boolean, default: true
|
|
13
|
+
|
|
14
|
+
yaml do
|
|
15
|
+
map "id", to: :id
|
|
16
|
+
map "description", to: :description
|
|
17
|
+
map "type", to: :type
|
|
18
|
+
map "check_image_elements", to: :check_image_elements
|
|
19
|
+
map "check_style_images", to: :check_style_images
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def check(node, context)
|
|
23
|
+
return unless element?(node)
|
|
24
|
+
|
|
25
|
+
case node.name
|
|
26
|
+
when "image"
|
|
27
|
+
check_image_element(node, context) if check_image_elements
|
|
28
|
+
else
|
|
29
|
+
check_style_attribute(node, context) if check_style_images
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def should_check_node?(node, context = nil)
|
|
34
|
+
return false unless element?(node)
|
|
35
|
+
return false if context&.node_structurally_invalid?(node)
|
|
36
|
+
|
|
37
|
+
node.name == "image" || has_style_attribute?(node)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def check_image_element(node, context)
|
|
43
|
+
# Check href and xlink:href attributes
|
|
44
|
+
href = get_attribute(node, "href") || get_attribute(node, "xlink:href")
|
|
45
|
+
return unless href && !embedded_image?(href)
|
|
46
|
+
|
|
47
|
+
context.add_error(
|
|
48
|
+
requirement_id: id,
|
|
49
|
+
message: "External image reference not allowed: #{href}. Images must be embedded as data URIs.",
|
|
50
|
+
node: node,
|
|
51
|
+
severity: :error,
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def check_style_attribute(node, context)
|
|
56
|
+
style_value = get_attribute(node, "style")
|
|
57
|
+
return unless style_value
|
|
58
|
+
|
|
59
|
+
# Check for url() references to images in background, background-image, etc.
|
|
60
|
+
style_value.scan(/url\s*\(\s*['"]?([^'")\s]+)['"]?\s*\)/i) do
|
|
61
|
+
url = ::Regexp.last_match(1)
|
|
62
|
+
next if embedded_image?(url)
|
|
63
|
+
|
|
64
|
+
context.add_error(
|
|
65
|
+
requirement_id: id,
|
|
66
|
+
message: "External image URL in style attribute not allowed: #{url}. Images must be embedded as data URIs.",
|
|
67
|
+
node: node,
|
|
68
|
+
severity: :error,
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def has_style_attribute?(node)
|
|
74
|
+
!get_attribute(node, "style").nil?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def embedded_image?(url)
|
|
78
|
+
return true if url.nil? || url.empty?
|
|
79
|
+
|
|
80
|
+
# Data URLs are embedded
|
|
81
|
+
return true if url.start_with?("data:")
|
|
82
|
+
|
|
83
|
+
# Fragment identifiers (internal references) are allowed
|
|
84
|
+
return true if url.start_with?("#")
|
|
85
|
+
|
|
86
|
+
# Everything else is external
|
|
87
|
+
false
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SvgConform
|
|
4
|
+
module Requirements
|
|
5
|
+
# Detects CSS style properties that should be promoted to attributes
|
|
6
|
+
class StylePromotionRequirement < BaseRequirement
|
|
7
|
+
# CSS properties that can be promoted to SVG attributes
|
|
8
|
+
PROMOTABLE_PROPERTIES = %w[
|
|
9
|
+
fill stroke stroke-width stroke-opacity stroke-linecap stroke-linejoin
|
|
10
|
+
fill-opacity fill-rule text-anchor font-family font-size font-weight
|
|
11
|
+
font-style opacity visibility display
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
def validate_document(document, context)
|
|
15
|
+
document.xpath("//*[@style]").each do |element|
|
|
16
|
+
style_attr = element["style"]
|
|
17
|
+
next if style_attr.nil? || style_attr.strip.empty?
|
|
18
|
+
|
|
19
|
+
validate_style_properties(element, style_attr, context)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def validate_style_properties(element, style_attr, context)
|
|
26
|
+
# Parse CSS style declarations
|
|
27
|
+
style_declarations = parse_style_declarations(style_attr)
|
|
28
|
+
|
|
29
|
+
style_declarations.each do |property, value|
|
|
30
|
+
next unless PROMOTABLE_PROPERTIES.include?(property)
|
|
31
|
+
|
|
32
|
+
# Report each promotable property as an issue
|
|
33
|
+
context.add_error(
|
|
34
|
+
requirement_id: id,
|
|
35
|
+
message: "Style property '#{property}' can be promoted to attribute",
|
|
36
|
+
node: element,
|
|
37
|
+
severity: :info,
|
|
38
|
+
data: {
|
|
39
|
+
attribute: "style",
|
|
40
|
+
property: property,
|
|
41
|
+
value: value,
|
|
42
|
+
element: element.name,
|
|
43
|
+
suggestion: "Move '#{property}:#{value}' from style to #{property} attribute",
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def parse_style_declarations(style_attr)
|
|
50
|
+
declarations = {}
|
|
51
|
+
|
|
52
|
+
# Split by semicolon and parse each declaration
|
|
53
|
+
style_attr.split(";").each do |declaration|
|
|
54
|
+
next if declaration.strip.empty?
|
|
55
|
+
|
|
56
|
+
parts = declaration.split(":", 2)
|
|
57
|
+
next unless parts.length == 2
|
|
58
|
+
|
|
59
|
+
property = parts[0].strip
|
|
60
|
+
value = parts[1].strip
|
|
61
|
+
|
|
62
|
+
# Remove any trailing semicolon from value
|
|
63
|
+
value = value.chomp(";")
|
|
64
|
+
|
|
65
|
+
declarations[property] = value
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
declarations
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_requirement"
|
|
4
|
+
|
|
5
|
+
module SvgConform
|
|
6
|
+
module Requirements
|
|
7
|
+
# Unified style requirement that handles all style validation:
|
|
8
|
+
# - Style syntax validation
|
|
9
|
+
# - Allowed style properties (whitelist/blacklist)
|
|
10
|
+
# - Property value validation
|
|
11
|
+
class StyleRequirement < BaseRequirement
|
|
12
|
+
attribute :type, :string, default: -> { "StyleRequirement" }
|
|
13
|
+
attribute :allowed_properties, :string, collection: true, default: -> {
|
|
14
|
+
[]
|
|
15
|
+
}
|
|
16
|
+
attribute :disallowed_properties, :string, collection: true, default: -> {
|
|
17
|
+
[]
|
|
18
|
+
}
|
|
19
|
+
attribute :property_values, :hash, default: -> { {} }
|
|
20
|
+
attribute :property_types, :hash, default: -> { {} }
|
|
21
|
+
|
|
22
|
+
yaml do
|
|
23
|
+
map "id", to: :id
|
|
24
|
+
map "description", to: :description
|
|
25
|
+
map "type", to: :type
|
|
26
|
+
map "allowed_properties", to: :allowed_properties
|
|
27
|
+
map "disallowed_properties", to: :disallowed_properties
|
|
28
|
+
map "property_values", to: :property_values
|
|
29
|
+
map "property_types", to: :property_types
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def check(node, context)
|
|
33
|
+
return unless element?(node)
|
|
34
|
+
|
|
35
|
+
style_value = get_attribute(node, "style")
|
|
36
|
+
return unless style_value
|
|
37
|
+
return if style_value.strip.empty?
|
|
38
|
+
|
|
39
|
+
# 1. Check for malformed style syntax
|
|
40
|
+
check_malformed_syntax(style_value, node, context)
|
|
41
|
+
|
|
42
|
+
# 2. Check for allowed/disallowed properties and validate their values
|
|
43
|
+
check_style_properties(style_value, node, context)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def check_malformed_syntax(style_value, node, context)
|
|
49
|
+
# Check for malformed fields like ['malformed']
|
|
50
|
+
if /\[['"][^'"]*['"]\]/.match?(style_value)
|
|
51
|
+
context.add_error(
|
|
52
|
+
requirement_id: id,
|
|
53
|
+
message: "Malformed field '#{style_value.match(/\[['"][^'"]*['"]\]/)[0]}' in style attribute found. Field removed.",
|
|
54
|
+
node: node,
|
|
55
|
+
severity: :error,
|
|
56
|
+
data: { attribute: "style", value: style_value },
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Check for other malformed syntax patterns
|
|
61
|
+
# Invalid property declarations without colons
|
|
62
|
+
style_value.split(";").each do |declaration|
|
|
63
|
+
declaration = declaration.strip
|
|
64
|
+
next if declaration.empty?
|
|
65
|
+
|
|
66
|
+
next if declaration.include?(":")
|
|
67
|
+
|
|
68
|
+
context.add_error(
|
|
69
|
+
requirement_id: id,
|
|
70
|
+
message: "Malformed style declaration '#{declaration}' found. Declaration removed.",
|
|
71
|
+
node: node,
|
|
72
|
+
severity: :error,
|
|
73
|
+
data: { attribute: "style", declaration: declaration },
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def check_style_properties(style_value, node, context)
|
|
79
|
+
style_value.split(";").each do |declaration|
|
|
80
|
+
declaration = declaration.strip
|
|
81
|
+
next if declaration.empty?
|
|
82
|
+
|
|
83
|
+
next unless declaration.include?(":")
|
|
84
|
+
|
|
85
|
+
property, value = declaration.split(":", 2).map(&:strip)
|
|
86
|
+
property = property.downcase
|
|
87
|
+
|
|
88
|
+
# Check if property is allowed
|
|
89
|
+
if property_invalid?(property)
|
|
90
|
+
context.add_error(
|
|
91
|
+
requirement_id: id,
|
|
92
|
+
message: "Style property '#{property}' removed",
|
|
93
|
+
node: node,
|
|
94
|
+
severity: :error,
|
|
95
|
+
data: { attribute: "style", property: property },
|
|
96
|
+
)
|
|
97
|
+
next
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Skip value validation for color properties - handled by ColorRestrictionsRequirement
|
|
101
|
+
color_properties = %w[fill stroke color stop-color flood-color
|
|
102
|
+
lighting-color]
|
|
103
|
+
next if color_properties.include?(property)
|
|
104
|
+
|
|
105
|
+
# Skip value validation for font-family - handled by FontFamilyRequirement
|
|
106
|
+
next if property == "font-family"
|
|
107
|
+
|
|
108
|
+
# Check if property value is valid
|
|
109
|
+
next unless property_value_invalid?(property, value)
|
|
110
|
+
|
|
111
|
+
context.add_error(
|
|
112
|
+
requirement_id: id,
|
|
113
|
+
message: "Invalid value '#{value}' for style property '#{property}'",
|
|
114
|
+
node: node,
|
|
115
|
+
severity: :error,
|
|
116
|
+
data: { attribute: "style", property: property, value: value },
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def property_invalid?(property)
|
|
122
|
+
# If allowed_properties is specified (whitelist approach), use that
|
|
123
|
+
if allowed_properties && !allowed_properties.empty?
|
|
124
|
+
allowed_props = allowed_properties
|
|
125
|
+
allowed_props = [allowed_props] if allowed_props.is_a?(String)
|
|
126
|
+
return !allowed_props.include?(property)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Otherwise, use disallowed_properties (blacklist approach)
|
|
130
|
+
if disallowed_properties && !disallowed_properties.empty?
|
|
131
|
+
disallowed_props = disallowed_properties
|
|
132
|
+
disallowed_props = [disallowed_props] if disallowed_props.is_a?(String)
|
|
133
|
+
return disallowed_props.include?(property)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# If neither is specified, allow all properties
|
|
137
|
+
false
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def property_value_invalid?(property, value)
|
|
141
|
+
# First check explicit allowed values
|
|
142
|
+
if property_values && property_values[property]
|
|
143
|
+
allowed_values = property_values[property]
|
|
144
|
+
unless allowed_values.nil? || allowed_values.empty?
|
|
145
|
+
# Convert to array if it's a single value
|
|
146
|
+
allowed_values = [allowed_values] if allowed_values.is_a?(String)
|
|
147
|
+
|
|
148
|
+
# Separate literal values from type references (those starting with '<')
|
|
149
|
+
literal_values = allowed_values.reject { |v| v.start_with?("<") }
|
|
150
|
+
type_references = allowed_values.select { |v| v.start_with?("<") }
|
|
151
|
+
|
|
152
|
+
# Check literal values first (case-insensitive)
|
|
153
|
+
return false if literal_values.map(&:downcase).include?(value.downcase)
|
|
154
|
+
|
|
155
|
+
# If there are type references, validate against those types
|
|
156
|
+
if type_references.any?
|
|
157
|
+
type_references.each do |type_ref|
|
|
158
|
+
return false if validate_property_type(value, type_ref)
|
|
159
|
+
end
|
|
160
|
+
# Value didn't match any type reference
|
|
161
|
+
else
|
|
162
|
+
# No type references, and didn't match literals
|
|
163
|
+
end
|
|
164
|
+
return true
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Then check type-based validation
|
|
169
|
+
if property_types && property_types[property]
|
|
170
|
+
property_type = property_types[property]
|
|
171
|
+
return !validate_property_type(value, property_type)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# If no validation rules are specified, allow the value
|
|
175
|
+
false
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Validate a property value against its type (from word_properties.py)
|
|
179
|
+
def validate_property_type(value, type)
|
|
180
|
+
case type
|
|
181
|
+
when "<color>"
|
|
182
|
+
validate_color_value(value)
|
|
183
|
+
when "<paint>"
|
|
184
|
+
validate_paint_value(value)
|
|
185
|
+
when "<number>"
|
|
186
|
+
validate_number_value(value)
|
|
187
|
+
when "<integer>"
|
|
188
|
+
validate_integer_value(value)
|
|
189
|
+
else
|
|
190
|
+
# Unknown type, allow it
|
|
191
|
+
true
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Validate color values (from basic_types['<color>'] in word_properties.py)
|
|
196
|
+
def validate_color_value(value)
|
|
197
|
+
allowed_colors = ["black", "#ffffff", "#FFFFFF", "white", "#000000",
|
|
198
|
+
"currentColor", "inherit"]
|
|
199
|
+
# Case-insensitive comparison for all values
|
|
200
|
+
allowed_colors_lower = allowed_colors.map(&:downcase)
|
|
201
|
+
allowed_colors_lower.include?(value.downcase) || allowed_colors.include?(value)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Validate paint values (from basic_types['<paint>'] in word_properties.py)
|
|
205
|
+
def validate_paint_value(value)
|
|
206
|
+
# <paint> includes 'none', 'currentColor', 'inherit', and <color> values
|
|
207
|
+
return true if %w[none currentColor inherit].include?(value.downcase)
|
|
208
|
+
|
|
209
|
+
# Also check if it's a valid color
|
|
210
|
+
validate_color_value(value)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Validate number values (basic numeric validation)
|
|
214
|
+
def validate_number_value(value)
|
|
215
|
+
# Allow numbers with optional units, decimals, percentages
|
|
216
|
+
value.match?(/^[+-]?(\d+\.?\d*|\.\d+)(%|px|em|ex|pt|pc|cm|mm|in)?$/i)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Validate integer values
|
|
220
|
+
def validate_integer_value(value)
|
|
221
|
+
# Allow positive/negative integers
|
|
222
|
+
value.match?(/^[+-]?\d+$/)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_requirement"
|
|
4
|
+
|
|
5
|
+
module SvgConform
|
|
6
|
+
module Requirements
|
|
7
|
+
# Validates that SVG documents have proper viewBox attributes
|
|
8
|
+
class ViewboxRequiredRequirement < BaseRequirement
|
|
9
|
+
attribute :type, :string, default: -> { "ViewboxRequiredRequirement" }
|
|
10
|
+
|
|
11
|
+
yaml do
|
|
12
|
+
map "id", to: :id
|
|
13
|
+
map "description", to: :description
|
|
14
|
+
map "type", to: :type
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def validate_document(document, context)
|
|
18
|
+
root = document.root
|
|
19
|
+
return unless root&.name == "svg"
|
|
20
|
+
|
|
21
|
+
viewbox = get_attribute(root, "viewBox")
|
|
22
|
+
|
|
23
|
+
if viewbox.nil? || viewbox.empty?
|
|
24
|
+
context.add_error(
|
|
25
|
+
requirement: self,
|
|
26
|
+
node: root,
|
|
27
|
+
message: "SVG root element must have a viewBox attribute",
|
|
28
|
+
data: { missing_attribute: "viewBox" },
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Add informational message about calculated viewBox if width/height are present
|
|
32
|
+
width = get_attribute(root, "width")
|
|
33
|
+
height = get_attribute(root, "height")
|
|
34
|
+
|
|
35
|
+
if width && height && valid_number?(width) && valid_number?(height)
|
|
36
|
+
calculated_viewbox = "0 0 #{width.to_f} #{height.to_f}"
|
|
37
|
+
context.add_error(
|
|
38
|
+
requirement: self,
|
|
39
|
+
node: root,
|
|
40
|
+
message: "Trying to put in the attribute with value '#{calculated_viewbox}'",
|
|
41
|
+
data: {
|
|
42
|
+
calculated_viewbox: calculated_viewbox,
|
|
43
|
+
source_width: width,
|
|
44
|
+
source_height: height,
|
|
45
|
+
},
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
return
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Validate viewBox format (should be "min-x min-y width height")
|
|
52
|
+
# Also accept comma-separated with parentheses like "(0, 0, 100, 100)" (svgcheck is lenient)
|
|
53
|
+
normalized_viewbox = viewbox.strip.gsub(/[(),]/, " ").squeeze(" ")
|
|
54
|
+
parts = normalized_viewbox.strip.split(/\s+/)
|
|
55
|
+
unless parts.length == 4 && parts.all? { |part| valid_number?(part) }
|
|
56
|
+
context.add_error(
|
|
57
|
+
requirement: self,
|
|
58
|
+
node: root,
|
|
59
|
+
message: "viewBox attribute must contain four numeric values (min-x min-y width height)",
|
|
60
|
+
data: {
|
|
61
|
+
viewbox_value: viewbox,
|
|
62
|
+
parsed_parts: parts,
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
return
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Check that width and height are positive
|
|
69
|
+
width = parts[2].to_f
|
|
70
|
+
height = parts[3].to_f
|
|
71
|
+
|
|
72
|
+
return unless width <= 0 || height <= 0
|
|
73
|
+
|
|
74
|
+
context.add_error(
|
|
75
|
+
requirement: self,
|
|
76
|
+
node: root,
|
|
77
|
+
message: "viewBox width and height must be positive values",
|
|
78
|
+
data: {
|
|
79
|
+
viewbox_value: viewbox,
|
|
80
|
+
width: width,
|
|
81
|
+
height: height,
|
|
82
|
+
},
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def valid_number?(str)
|
|
89
|
+
Float(str)
|
|
90
|
+
true
|
|
91
|
+
rescue ArgumentError
|
|
92
|
+
false
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "requirements/base_requirement"
|
|
4
|
+
require_relative "requirements/allowed_elements_requirement"
|
|
5
|
+
require_relative "requirements/color_restrictions_requirement"
|
|
6
|
+
require_relative "requirements/font_family_requirement"
|
|
7
|
+
require_relative "requirements/invalid_id_references_requirement"
|
|
8
|
+
require_relative "requirements/namespace_requirement"
|
|
9
|
+
require_relative "requirements/no_external_css_requirement"
|
|
10
|
+
require_relative "requirements/no_external_fonts_requirement"
|
|
11
|
+
require_relative "requirements/no_external_images_requirement"
|
|
12
|
+
require_relative "requirements/viewbox_required_requirement"
|
|
13
|
+
require_relative "requirements/namespace_attributes_requirement"
|
|
14
|
+
require_relative "requirements/forbidden_content_requirement"
|
|
15
|
+
require_relative "requirements/style_requirement"
|
|
16
|
+
require_relative "requirements/style_promotion_requirement"
|
|
17
|
+
require_relative "requirements/link_validation_requirement"
|
|
18
|
+
require_relative "requirements/id_reference_requirement"
|
|
19
|
+
|
|
20
|
+
module SvgConform
|
|
21
|
+
module Requirements
|
|
22
|
+
# Auto-load all requirement classes
|
|
23
|
+
def self.all
|
|
24
|
+
[
|
|
25
|
+
AllowedElementsRequirement,
|
|
26
|
+
ColorRestrictionsRequirement,
|
|
27
|
+
FontFamilyRequirement,
|
|
28
|
+
InvalidIdReferencesRequirement,
|
|
29
|
+
NamespaceRequirement,
|
|
30
|
+
NoExternalCssRequirement,
|
|
31
|
+
NoExternalFontsRequirement,
|
|
32
|
+
NoExternalImagesRequirement,
|
|
33
|
+
ViewboxRequiredRequirement,
|
|
34
|
+
NamespaceAttributesRequirement,
|
|
35
|
+
ForbiddenContentRequirement,
|
|
36
|
+
StyleRequirement,
|
|
37
|
+
LinkValidationRequirement,
|
|
38
|
+
IdReferenceRequirement,
|
|
39
|
+
]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Find a requirement class by name
|
|
43
|
+
def self.find(class_name)
|
|
44
|
+
const_get(class_name)
|
|
45
|
+
rescue NameError
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|