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,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_requirement"
|
|
4
|
+
|
|
5
|
+
module SvgConform
|
|
6
|
+
module Requirements
|
|
7
|
+
# Validates that elements don't have attributes from disallowed namespaces
|
|
8
|
+
# or only have attributes from allowed namespaces (whitelist mode)
|
|
9
|
+
class NamespaceAttributesRequirement < BaseRequirement
|
|
10
|
+
attribute :type, :string, default: -> { "NamespaceAttributesRequirement" }
|
|
11
|
+
attribute :disallowed_namespaces, :string, collection: true, default: -> {
|
|
12
|
+
[]
|
|
13
|
+
}
|
|
14
|
+
attribute :allowed_namespaces, :string, collection: true, default: -> {
|
|
15
|
+
[]
|
|
16
|
+
}
|
|
17
|
+
attribute :exempt_elements, :string, collection: true, default: -> { [] }
|
|
18
|
+
|
|
19
|
+
yaml do
|
|
20
|
+
map "id", to: :id
|
|
21
|
+
map "description", to: :description
|
|
22
|
+
map "type", to: :type
|
|
23
|
+
map "disallowed_namespaces", to: :disallowed_namespaces
|
|
24
|
+
map "allowed_namespaces", to: :allowed_namespaces
|
|
25
|
+
map "exempt_elements", to: :exempt_elements
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def check(node, context)
|
|
29
|
+
return unless element?(node)
|
|
30
|
+
|
|
31
|
+
# Skip validation for exempt elements (e.g., RDF metadata elements)
|
|
32
|
+
return if exempt_elements.include?(node.name)
|
|
33
|
+
|
|
34
|
+
# Try to get attributes using different methods depending on what's available
|
|
35
|
+
if node.respond_to?(:attribute_nodes)
|
|
36
|
+
# Use attribute_nodes if available (Nokogiri style)
|
|
37
|
+
check_attribute_nodes(node, context)
|
|
38
|
+
elsif node.respond_to?(:attributes)
|
|
39
|
+
# Fallback to attributes method (Moxml style)
|
|
40
|
+
check_attributes_hash(node, context)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def check_attribute_nodes(node, context)
|
|
47
|
+
node.attribute_nodes.each do |attr|
|
|
48
|
+
# Check if attribute has a namespace
|
|
49
|
+
namespace_uri = attr.namespace&.href
|
|
50
|
+
next unless namespace_uri
|
|
51
|
+
|
|
52
|
+
# Determine if this namespace is invalid based on configuration
|
|
53
|
+
invalid_namespace = if allowed_namespaces.empty?
|
|
54
|
+
# Blacklist mode: disallowed namespaces are forbidden
|
|
55
|
+
disallowed_namespaces.include?(namespace_uri)
|
|
56
|
+
else
|
|
57
|
+
# Whitelist mode: only allowed namespaces are permitted
|
|
58
|
+
!allowed_namespaces.include?(namespace_uri)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
next unless invalid_namespace
|
|
62
|
+
|
|
63
|
+
# Get the full attribute name with prefix if available
|
|
64
|
+
attr_name = if attr.respond_to?(:namespace) && attr.namespace&.prefix
|
|
65
|
+
"#{attr.namespace.prefix}:#{attr.name}"
|
|
66
|
+
else
|
|
67
|
+
attr.name
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
context.add_error(
|
|
71
|
+
requirement_id: id,
|
|
72
|
+
message: "Element '#{node.name}' does not allow attributes with namespace '#{namespace_uri}'",
|
|
73
|
+
node: node,
|
|
74
|
+
severity: :error,
|
|
75
|
+
data: { attribute: attr_name, namespace: namespace_uri },
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def check_attributes_hash(node, context)
|
|
81
|
+
return unless node.respond_to?(:attributes)
|
|
82
|
+
|
|
83
|
+
attributes = node.attributes
|
|
84
|
+
|
|
85
|
+
# Handle both Hash and Array cases
|
|
86
|
+
if attributes.respond_to?(:each_key)
|
|
87
|
+
# Hash case
|
|
88
|
+
attributes.each_key do |name|
|
|
89
|
+
check_attribute_name(name, node, context)
|
|
90
|
+
end
|
|
91
|
+
elsif attributes.respond_to?(:each)
|
|
92
|
+
# Array case - iterate over attribute objects
|
|
93
|
+
attributes.each do |attr|
|
|
94
|
+
check_moxml_attribute(attr, node, context)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def check_moxml_attribute(attr, node, context)
|
|
100
|
+
# For Moxml attributes, check if they have namespace information
|
|
101
|
+
if attr.respond_to?(:namespace) && attr.namespace
|
|
102
|
+
namespace_uri = if attr.namespace.respond_to?(:href)
|
|
103
|
+
attr.namespace.href
|
|
104
|
+
elsif attr.namespace.respond_to?(:uri)
|
|
105
|
+
attr.namespace.uri
|
|
106
|
+
else
|
|
107
|
+
attr.namespace.to_s
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Skip if no namespace URI
|
|
111
|
+
return unless namespace_uri && !namespace_uri.empty?
|
|
112
|
+
|
|
113
|
+
# Determine if this namespace is invalid based on configuration
|
|
114
|
+
invalid_namespace = if allowed_namespaces.empty?
|
|
115
|
+
# Blacklist mode: disallowed namespaces are forbidden
|
|
116
|
+
disallowed_namespaces.include?(namespace_uri)
|
|
117
|
+
else
|
|
118
|
+
# Whitelist mode: only allowed namespaces are permitted
|
|
119
|
+
!allowed_namespaces.include?(namespace_uri)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
return unless invalid_namespace
|
|
123
|
+
|
|
124
|
+
# Get the full attribute name with prefix if available
|
|
125
|
+
attr_name = if attr.namespace.respond_to?(:prefix) && attr.namespace.prefix
|
|
126
|
+
"#{attr.namespace.prefix}:#{attr.name}"
|
|
127
|
+
else
|
|
128
|
+
attr.name
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
context.add_error(
|
|
132
|
+
requirement_id: id,
|
|
133
|
+
message: "Element '#{node.name}' does not allow attributes with namespace '#{namespace_uri}'",
|
|
134
|
+
node: node,
|
|
135
|
+
severity: :error,
|
|
136
|
+
data: { attribute: attr_name, namespace: namespace_uri },
|
|
137
|
+
)
|
|
138
|
+
else
|
|
139
|
+
# Fallback to name-based checking for attributes without namespace objects
|
|
140
|
+
name = attr.respond_to?(:name) ? attr.name : attr.to_s
|
|
141
|
+
check_attribute_name(name, node, context)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def check_attribute_name(name, node, context)
|
|
146
|
+
# Convert name to string if it's not already
|
|
147
|
+
name_str = name.to_s
|
|
148
|
+
|
|
149
|
+
# Check if this is a namespaced attribute by looking for colon in name
|
|
150
|
+
return unless name_str.include?(":")
|
|
151
|
+
|
|
152
|
+
prefix, = name_str.split(":", 2)
|
|
153
|
+
|
|
154
|
+
# Find the namespace URI for this prefix
|
|
155
|
+
namespace_uri = find_namespace_uri(node, prefix)
|
|
156
|
+
|
|
157
|
+
return unless namespace_uri
|
|
158
|
+
|
|
159
|
+
# Determine if this namespace is invalid based on configuration
|
|
160
|
+
invalid_namespace = if allowed_namespaces.empty?
|
|
161
|
+
# Blacklist mode: disallowed namespaces are forbidden
|
|
162
|
+
disallowed_namespaces.include?(namespace_uri)
|
|
163
|
+
else
|
|
164
|
+
# Whitelist mode: only allowed namespaces are permitted
|
|
165
|
+
!allowed_namespaces.include?(namespace_uri)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
return unless invalid_namespace
|
|
169
|
+
|
|
170
|
+
context.add_error(
|
|
171
|
+
requirement_id: id,
|
|
172
|
+
message: "Element '#{node.name}' does not allow attributes with namespace '#{namespace_uri}'",
|
|
173
|
+
node: node,
|
|
174
|
+
severity: :error,
|
|
175
|
+
data: { attribute: name, namespace: namespace_uri },
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def find_namespace_uri(node, prefix)
|
|
180
|
+
# Check current node's namespace definitions
|
|
181
|
+
current = node
|
|
182
|
+
while current.respond_to?(:parent)
|
|
183
|
+
if current.respond_to?(:namespace_definitions)
|
|
184
|
+
ns_def = current.namespace_definitions.find do |ns|
|
|
185
|
+
ns.prefix == prefix
|
|
186
|
+
end
|
|
187
|
+
if ns_def
|
|
188
|
+
return ns_def.uri if ns_def.respond_to?(:uri)
|
|
189
|
+
return ns_def.href if ns_def.respond_to?(:href)
|
|
190
|
+
|
|
191
|
+
return ns_def.to_s
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Check if it's defined as an attribute (xmlns:prefix)
|
|
196
|
+
xmlns_value = get_attribute(current, "xmlns:#{prefix}")
|
|
197
|
+
return xmlns_value if xmlns_value
|
|
198
|
+
|
|
199
|
+
# Move to parent, but break if parent is nil or doesn't respond to parent
|
|
200
|
+
begin
|
|
201
|
+
current = current.parent
|
|
202
|
+
rescue StandardError
|
|
203
|
+
break
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
nil
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,294 @@
|
|
|
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 namespace declarations
|
|
8
|
+
class NamespaceRequirement < BaseRequirement
|
|
9
|
+
attribute :type, :string, default: -> { "NamespaceRequirement" }
|
|
10
|
+
attribute :allowed_namespaces, :string, collection: true, default: -> {
|
|
11
|
+
["http://www.w3.org/2000/svg"]
|
|
12
|
+
}
|
|
13
|
+
attribute :disallowed_namespaces, :string, collection: true, default: -> {
|
|
14
|
+
[]
|
|
15
|
+
}
|
|
16
|
+
attribute :required_namespace, :string, default: "http://www.w3.org/2000/svg"
|
|
17
|
+
attribute :allow_rdf_metadata, :boolean, default: false
|
|
18
|
+
|
|
19
|
+
# RDF-related namespaces commonly found in SVG metadata
|
|
20
|
+
RDF_NAMESPACES = [
|
|
21
|
+
"http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
|
22
|
+
"http://creativecommons.org/ns#",
|
|
23
|
+
"http://purl.org/dc/elements/1.1/",
|
|
24
|
+
"http://purl.org/dc/dcmitype/",
|
|
25
|
+
"http://www.w3.org/2000/01/rdf-schema#",
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
yaml do
|
|
29
|
+
map "id", to: :id
|
|
30
|
+
map "description", to: :description
|
|
31
|
+
map "type", to: :type
|
|
32
|
+
map "allowed_namespaces", to: :allowed_namespaces
|
|
33
|
+
map "disallowed_namespaces", to: :disallowed_namespaces
|
|
34
|
+
map "required_namespace", to: :required_namespace
|
|
35
|
+
map "allow_rdf_metadata", to: :allow_rdf_metadata
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def validate_document(document, context)
|
|
39
|
+
root = document.root
|
|
40
|
+
return unless root
|
|
41
|
+
|
|
42
|
+
# Check if root element is svg
|
|
43
|
+
unless root.name == "svg"
|
|
44
|
+
context.add_error(
|
|
45
|
+
requirement: self,
|
|
46
|
+
node: root,
|
|
47
|
+
message: "Root element must be 'svg'",
|
|
48
|
+
data: { element_name: root.name },
|
|
49
|
+
)
|
|
50
|
+
return
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Check for SVG namespace - Moxml handles namespace differently
|
|
54
|
+
svg_namespace = nil
|
|
55
|
+
|
|
56
|
+
# Try to get namespace from Moxml's namespace method
|
|
57
|
+
if root.respond_to?(:namespace) && root.namespace
|
|
58
|
+
namespace_str = root.namespace.to_s
|
|
59
|
+
# Extract the URI from the namespace string like 'xmlns="http://www.w3.org/2000/svg"'
|
|
60
|
+
svg_namespace = ::Regexp.last_match(1) if namespace_str =~ /xmlns="([^"]+)"/
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Fallback to checking xmlns attribute
|
|
64
|
+
svg_namespace ||= get_attribute(root, "xmlns")
|
|
65
|
+
|
|
66
|
+
# Default namespace (empty string) should be treated as SVG namespace
|
|
67
|
+
svg_namespace = "" if svg_namespace.nil?
|
|
68
|
+
|
|
69
|
+
# Check against allowed namespaces if configured
|
|
70
|
+
if allowed_namespaces && !allowed_namespaces.empty? && !allowed_namespaces.include?(svg_namespace)
|
|
71
|
+
context.add_error(
|
|
72
|
+
requirement: self,
|
|
73
|
+
node: root,
|
|
74
|
+
message: "Namespace '#{svg_namespace}' is not allowed. Only #{allowed_namespaces.join(', ')} are permitted",
|
|
75
|
+
data: {
|
|
76
|
+
current_namespace: svg_namespace,
|
|
77
|
+
allowed_namespaces: allowed_namespaces,
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
return
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Check against disallowed namespaces if configured (legacy support)
|
|
84
|
+
if disallowed_namespaces && !disallowed_namespaces.empty? && disallowed_namespaces.include?(svg_namespace)
|
|
85
|
+
context.add_error(
|
|
86
|
+
requirement: self,
|
|
87
|
+
node: root,
|
|
88
|
+
message: "Namespace '#{svg_namespace}' is not allowed",
|
|
89
|
+
data: {
|
|
90
|
+
current_namespace: svg_namespace,
|
|
91
|
+
disallowed_namespaces: disallowed_namespaces,
|
|
92
|
+
},
|
|
93
|
+
)
|
|
94
|
+
return
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Default behavior: require SVG namespace
|
|
98
|
+
unless allowed_namespaces.empty? && disallowed_namespaces.empty?
|
|
99
|
+
# Now check all elements in the document for namespace violations
|
|
100
|
+
check_all_elements(document, context)
|
|
101
|
+
return
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
return if svg_namespace == "http://www.w3.org/2000/svg"
|
|
105
|
+
|
|
106
|
+
context.add_error(
|
|
107
|
+
requirement: self,
|
|
108
|
+
node: root,
|
|
109
|
+
message: "SVG namespace declaration missing or incorrect",
|
|
110
|
+
data: {
|
|
111
|
+
current_namespace: svg_namespace,
|
|
112
|
+
expected_namespace: "http://www.w3.org/2000/svg",
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Also check all elements for namespace violations
|
|
117
|
+
check_all_elements(document, context)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def check(node, context)
|
|
121
|
+
return unless element?(node)
|
|
122
|
+
|
|
123
|
+
# Debug: Check all elements
|
|
124
|
+
puts "DEBUG: Checking element: #{node.name}" if node.name.include?(":")
|
|
125
|
+
|
|
126
|
+
# Check if this element has a namespace
|
|
127
|
+
element_namespace = nil
|
|
128
|
+
|
|
129
|
+
# Try to get namespace from the element
|
|
130
|
+
if node.respond_to?(:namespace) && node.namespace
|
|
131
|
+
namespace_str = node.namespace.to_s
|
|
132
|
+
puts "DEBUG: namespace_str = #{namespace_str}" if node.name.include?(":")
|
|
133
|
+
# Extract the URI from the namespace string
|
|
134
|
+
element_namespace = ::Regexp.last_match(1) if namespace_str =~ /xmlns[^=]*="([^"]+)"/
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# If no namespace found, check if element has a prefix (indicating it's namespaced)
|
|
138
|
+
if element_namespace.nil? && node.name.include?(":")
|
|
139
|
+
prefix = node.name.split(":").first
|
|
140
|
+
puts "DEBUG: Found prefixed element #{node.name}, prefix = #{prefix}"
|
|
141
|
+
element_namespace = find_namespace_uri_for_prefix(node, prefix)
|
|
142
|
+
puts "DEBUG: Found namespace URI = #{element_namespace}"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Skip if no namespace (default SVG namespace)
|
|
146
|
+
if element_namespace.nil? || element_namespace.empty?
|
|
147
|
+
puts "DEBUG: Skipping #{node.name} - no namespace found" if node.name.include?(":")
|
|
148
|
+
return
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
puts "DEBUG: Element #{node.name} has namespace #{element_namespace}"
|
|
152
|
+
|
|
153
|
+
# Check against allowed namespaces if configured
|
|
154
|
+
# If allow_rdf_metadata is enabled, also allow RDF namespaces
|
|
155
|
+
effective_allowed_namespaces = allowed_namespaces
|
|
156
|
+
if allow_rdf_metadata
|
|
157
|
+
effective_allowed_namespaces = allowed_namespaces + RDF_NAMESPACES
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
if effective_allowed_namespaces && !effective_allowed_namespaces.empty? && !effective_allowed_namespaces.include?(element_namespace)
|
|
161
|
+
puts "DEBUG: Adding error for disallowed namespace #{element_namespace}"
|
|
162
|
+
context.add_error(
|
|
163
|
+
requirement_id: id,
|
|
164
|
+
message: "The namespace #{element_namespace} is not permitted for svg elements.",
|
|
165
|
+
node: node,
|
|
166
|
+
severity: :error,
|
|
167
|
+
data: {
|
|
168
|
+
element_name: node.name,
|
|
169
|
+
namespace: element_namespace,
|
|
170
|
+
allowed_namespaces: effective_allowed_namespaces,
|
|
171
|
+
},
|
|
172
|
+
)
|
|
173
|
+
return
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Check against disallowed namespaces if configured
|
|
177
|
+
return unless disallowed_namespaces && !disallowed_namespaces.empty? && disallowed_namespaces.include?(element_namespace)
|
|
178
|
+
|
|
179
|
+
puts "DEBUG: Adding error for explicitly disallowed namespace #{element_namespace}"
|
|
180
|
+
context.add_error(
|
|
181
|
+
requirement_id: id,
|
|
182
|
+
message: "The namespace #{element_namespace} is not permitted for svg elements.",
|
|
183
|
+
node: node,
|
|
184
|
+
severity: :error,
|
|
185
|
+
data: {
|
|
186
|
+
element_name: node.name,
|
|
187
|
+
namespace: element_namespace,
|
|
188
|
+
disallowed_namespaces: disallowed_namespaces,
|
|
189
|
+
},
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
private
|
|
194
|
+
|
|
195
|
+
def check_all_elements(document, context)
|
|
196
|
+
# Recursively check all elements in the document
|
|
197
|
+
traverse_elements(document.root, context)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def traverse_elements(node, context)
|
|
201
|
+
return unless node
|
|
202
|
+
|
|
203
|
+
# Check this element
|
|
204
|
+
check_element_namespace(node, context) if element?(node)
|
|
205
|
+
|
|
206
|
+
# Recursively check children
|
|
207
|
+
if node.respond_to?(:children)
|
|
208
|
+
node.children.each { |child| traverse_elements(child, context) }
|
|
209
|
+
elsif node.respond_to?(:elements)
|
|
210
|
+
node.elements.each { |child| traverse_elements(child, context) }
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def check_element_namespace(node, context)
|
|
215
|
+
return unless element?(node)
|
|
216
|
+
|
|
217
|
+
# Check if this element has a namespace
|
|
218
|
+
element_namespace = nil
|
|
219
|
+
|
|
220
|
+
# Try to get namespace from the element
|
|
221
|
+
if node.respond_to?(:namespace) && node.namespace
|
|
222
|
+
namespace_str = node.namespace.to_s
|
|
223
|
+
# Extract the URI from the namespace string
|
|
224
|
+
element_namespace = ::Regexp.last_match(1) if namespace_str =~ /xmlns[^=]*="([^"]+)"/
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# If no namespace found, check if element has a prefix (indicating it's namespaced)
|
|
228
|
+
if element_namespace.nil? && node.name.include?(":")
|
|
229
|
+
prefix = node.name.split(":").first
|
|
230
|
+
element_namespace = find_namespace_uri_for_prefix(node, prefix)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Skip if no namespace (default SVG namespace)
|
|
234
|
+
return if element_namespace.nil? || element_namespace.empty?
|
|
235
|
+
|
|
236
|
+
# Check against allowed namespaces if configured
|
|
237
|
+
# If allow_rdf_metadata is enabled, also allow RDF namespaces
|
|
238
|
+
effective_allowed_namespaces = allowed_namespaces
|
|
239
|
+
if allow_rdf_metadata
|
|
240
|
+
effective_allowed_namespaces = allowed_namespaces + RDF_NAMESPACES
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
if effective_allowed_namespaces && !effective_allowed_namespaces.empty? && !effective_allowed_namespaces.include?(element_namespace)
|
|
244
|
+
context.add_error(
|
|
245
|
+
requirement_id: id,
|
|
246
|
+
message: "The namespace #{element_namespace} is not permitted for svg elements.",
|
|
247
|
+
node: node,
|
|
248
|
+
severity: :error,
|
|
249
|
+
data: {
|
|
250
|
+
element_name: node.name,
|
|
251
|
+
namespace: element_namespace,
|
|
252
|
+
allowed_namespaces: effective_allowed_namespaces,
|
|
253
|
+
},
|
|
254
|
+
)
|
|
255
|
+
return
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Check against disallowed namespaces if configured
|
|
259
|
+
return unless disallowed_namespaces && !disallowed_namespaces.empty? && disallowed_namespaces.include?(element_namespace)
|
|
260
|
+
|
|
261
|
+
context.add_error(
|
|
262
|
+
requirement_id: id,
|
|
263
|
+
message: "The namespace #{element_namespace} is not permitted for svg elements.",
|
|
264
|
+
node: node,
|
|
265
|
+
severity: :error,
|
|
266
|
+
data: {
|
|
267
|
+
element_name: node.name,
|
|
268
|
+
namespace: element_namespace,
|
|
269
|
+
disallowed_namespaces: disallowed_namespaces,
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def find_namespace_uri_for_prefix(node, prefix)
|
|
275
|
+
# Check current node and ancestors for namespace declarations
|
|
276
|
+
current = node
|
|
277
|
+
while current
|
|
278
|
+
# Check for xmlns:prefix attribute
|
|
279
|
+
xmlns_attr = "xmlns:#{prefix}"
|
|
280
|
+
return current.attributes[xmlns_attr] if current.respond_to?(:attributes) && current.attributes[xmlns_attr]
|
|
281
|
+
|
|
282
|
+
# Check using get_attribute method
|
|
283
|
+
namespace_uri = get_attribute(current, xmlns_attr)
|
|
284
|
+
return namespace_uri if namespace_uri
|
|
285
|
+
|
|
286
|
+
# Move to parent
|
|
287
|
+
current = current.respond_to?(:parent) ? current.parent : nil
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
nil
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_requirement"
|
|
4
|
+
|
|
5
|
+
module SvgConform
|
|
6
|
+
module Requirements
|
|
7
|
+
# Validates that no external CSS references are present
|
|
8
|
+
class NoExternalCssRequirement < BaseRequirement
|
|
9
|
+
attribute :type, :string, default: -> { "NoExternalCssRequirement" }
|
|
10
|
+
attribute :check_style_elements, :boolean, default: true
|
|
11
|
+
attribute :check_style_attributes, :boolean, default: true
|
|
12
|
+
attribute :check_link_elements, :boolean, default: true
|
|
13
|
+
attribute :allowed_protocols, :string, collection: true
|
|
14
|
+
|
|
15
|
+
yaml do
|
|
16
|
+
map "id", to: :id
|
|
17
|
+
map "description", to: :description
|
|
18
|
+
map "type", to: :type
|
|
19
|
+
map "check_style_elements", to: :check_style_elements
|
|
20
|
+
map "check_style_attributes", to: :check_style_attributes
|
|
21
|
+
map "check_link_elements", to: :check_link_elements
|
|
22
|
+
map "allowed_protocols", to: :allowed_protocols
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def check(node, context)
|
|
26
|
+
return unless element?(node)
|
|
27
|
+
|
|
28
|
+
case node.name
|
|
29
|
+
when "style"
|
|
30
|
+
check_style_element(node, context) if check_style_elements
|
|
31
|
+
when "link"
|
|
32
|
+
check_link_element(node, context) if check_link_elements
|
|
33
|
+
else
|
|
34
|
+
check_style_attribute(node, context) if check_style_attributes
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def should_check_node?(node, context = nil)
|
|
39
|
+
return false unless element?(node)
|
|
40
|
+
return false if context&.node_structurally_invalid?(node)
|
|
41
|
+
|
|
42
|
+
node.name == "style" ||
|
|
43
|
+
node.name == "link" ||
|
|
44
|
+
has_style_attribute?(node)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def check_style_element(node, context)
|
|
50
|
+
# Check for @import rules in style elements
|
|
51
|
+
content = node.text || ""
|
|
52
|
+
|
|
53
|
+
if content =~ /@import\s+url\s*\(\s*['"]?([^'")\s]+)['"]?\s*\)/i
|
|
54
|
+
url = ::Regexp.last_match(1)
|
|
55
|
+
unless allowed_url?(url)
|
|
56
|
+
context.add_error(
|
|
57
|
+
requirement_id: id,
|
|
58
|
+
message: "External CSS import not allowed: #{url}",
|
|
59
|
+
node: node,
|
|
60
|
+
severity: :error,
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
return unless content =~ /@import\s+['"]([^'"]+)['"]/i
|
|
66
|
+
|
|
67
|
+
url = ::Regexp.last_match(1)
|
|
68
|
+
return if allowed_url?(url)
|
|
69
|
+
|
|
70
|
+
context.add_error(
|
|
71
|
+
requirement_id: id,
|
|
72
|
+
message: "External CSS import not allowed: #{url}",
|
|
73
|
+
node: node,
|
|
74
|
+
severity: :error,
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def check_link_element(node, context)
|
|
79
|
+
rel = get_attribute(node, "rel")
|
|
80
|
+
href = get_attribute(node, "href")
|
|
81
|
+
|
|
82
|
+
return unless rel&.downcase == "stylesheet" && href
|
|
83
|
+
|
|
84
|
+
return if allowed_url?(href)
|
|
85
|
+
|
|
86
|
+
context.add_error(
|
|
87
|
+
requirement_id: id,
|
|
88
|
+
message: "External CSS link not allowed: #{href}",
|
|
89
|
+
node: node,
|
|
90
|
+
severity: :error,
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def check_style_attribute(node, context)
|
|
95
|
+
style_value = get_attribute(node, "style")
|
|
96
|
+
return unless style_value
|
|
97
|
+
|
|
98
|
+
# Check for url() references in style attributes
|
|
99
|
+
return unless style_value =~ /url\s*\(\s*['"]?([^'")\s]+)['"]?\s*\)/i
|
|
100
|
+
|
|
101
|
+
url = ::Regexp.last_match(1)
|
|
102
|
+
return if allowed_url?(url)
|
|
103
|
+
|
|
104
|
+
context.add_error(
|
|
105
|
+
requirement_id: id,
|
|
106
|
+
message: "External URL reference in style attribute not allowed: #{url}",
|
|
107
|
+
node: node,
|
|
108
|
+
severity: :error,
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def has_style_attribute?(node)
|
|
113
|
+
!get_attribute(node, "style").nil?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def allowed_url?(url)
|
|
117
|
+
return true if url.nil? || url.empty?
|
|
118
|
+
|
|
119
|
+
# Data URLs are typically allowed
|
|
120
|
+
return true if url.start_with?("data:")
|
|
121
|
+
|
|
122
|
+
# Fragment identifiers (internal references) are allowed
|
|
123
|
+
return true if url.start_with?("#")
|
|
124
|
+
|
|
125
|
+
# Check against allowed protocols
|
|
126
|
+
return false if allowed_protocols.nil? || allowed_protocols.empty?
|
|
127
|
+
|
|
128
|
+
allowed_protocols.any? { |protocol| url.start_with?("#{protocol}:") }
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|