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,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SvgConform
|
|
4
|
+
# Engine for applying remediations to SVG documents based on validation failures
|
|
5
|
+
class RemediationEngine
|
|
6
|
+
attr_reader :profile, :results
|
|
7
|
+
|
|
8
|
+
def initialize(profile)
|
|
9
|
+
@profile = profile
|
|
10
|
+
@results = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Apply remediations for failed requirements
|
|
14
|
+
def apply_remediations(document, validation_result)
|
|
15
|
+
@results.clear
|
|
16
|
+
|
|
17
|
+
# Group failed requirements by their remediations
|
|
18
|
+
remediation_groups = group_requirements_by_remediations(validation_result.failed_requirements)
|
|
19
|
+
|
|
20
|
+
# Apply each remediation only once, even if it targets multiple failed requirements
|
|
21
|
+
remediation_groups.each do |remediation, failed_requirements|
|
|
22
|
+
result = remediation.execute(document, failed_requirements)
|
|
23
|
+
@results << result
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@results
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get remediations that would apply to given failed requirements
|
|
30
|
+
def applicable_remediations(failed_requirements)
|
|
31
|
+
failed_requirement_ids = failed_requirements.map do |req|
|
|
32
|
+
req.requirement_id || req.rule&.id
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@profile.remediations.select do |remediation|
|
|
36
|
+
remediation.targets.any? do |req_id|
|
|
37
|
+
failed_requirement_ids.include?(req_id)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Check if any remediations are available for the profile
|
|
43
|
+
def has_remediations?
|
|
44
|
+
@profile.remediation_count.positive?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Get summary of remediation results
|
|
48
|
+
def summary
|
|
49
|
+
return "No remediations applied" if @results.empty?
|
|
50
|
+
|
|
51
|
+
successful = @results.count(&:success?)
|
|
52
|
+
failed = @results.count(&:failure?)
|
|
53
|
+
total_changes = @results.sum(&:changes_count)
|
|
54
|
+
|
|
55
|
+
"Applied #{successful} remediations successfully, #{failed} failed. Total changes: #{total_changes}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Get successful remediation results
|
|
59
|
+
def successful_results
|
|
60
|
+
@results.select(&:success?)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Get failed remediation results
|
|
64
|
+
def failed_results
|
|
65
|
+
@results.select(&:failure?)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Get total number of changes made
|
|
69
|
+
def total_changes
|
|
70
|
+
@results.sum(&:changes_count)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
# Group failed requirements by the remediations that target them
|
|
76
|
+
def group_requirements_by_remediations(failed_requirements)
|
|
77
|
+
groups = {}
|
|
78
|
+
|
|
79
|
+
@profile.remediations.each do |remediation|
|
|
80
|
+
# Find failed requirements that this remediation targets
|
|
81
|
+
targeted_failures = failed_requirements.select do |failure|
|
|
82
|
+
remediation.targets.include?(failure.requirement_id)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Only include remediations that have applicable failures
|
|
86
|
+
groups[remediation] = targeted_failures unless targeted_failures.empty?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
groups
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SvgConform
|
|
4
|
+
# Result object for remediation operations
|
|
5
|
+
class RemediationResult
|
|
6
|
+
attr_reader :remediation_id, :success, :failed_requirements, :message,
|
|
7
|
+
:changes_made, :error
|
|
8
|
+
|
|
9
|
+
def initialize(remediation_id:, success:, failed_requirements:,
|
|
10
|
+
message: nil, changes_made: [], error: nil)
|
|
11
|
+
@remediation_id = remediation_id
|
|
12
|
+
@success = success
|
|
13
|
+
@failed_requirements = failed_requirements
|
|
14
|
+
@message = message
|
|
15
|
+
@changes_made = changes_made || []
|
|
16
|
+
@error = error
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def success?
|
|
20
|
+
@success
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def failure?
|
|
24
|
+
!@success
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def changes_count
|
|
28
|
+
@changes_made.size
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_s
|
|
32
|
+
status = success? ? "SUCCESS" : "FAILURE"
|
|
33
|
+
"#{@remediation_id}: #{status} - #{@message}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "validator"
|
|
4
|
+
require_relative "remediation_engine"
|
|
5
|
+
require_relative "document"
|
|
6
|
+
|
|
7
|
+
module SvgConform
|
|
8
|
+
# Runner for applying SvgConform remediations to SVG content
|
|
9
|
+
class RemediationRunner
|
|
10
|
+
attr_reader :profile, :options
|
|
11
|
+
|
|
12
|
+
def initialize(profile: :svg_1_2_rfc, options: {})
|
|
13
|
+
@profile = resolve_profile(profile)
|
|
14
|
+
@options = {
|
|
15
|
+
verbose: false,
|
|
16
|
+
strict: false,
|
|
17
|
+
}.merge(options)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Run validation and remediation on SVG content
|
|
21
|
+
def run_remediation(svg_content, filename: nil)
|
|
22
|
+
# Parse the SVG content
|
|
23
|
+
document = SvgConform::Document.from_content(svg_content)
|
|
24
|
+
|
|
25
|
+
# Perform initial validation
|
|
26
|
+
initial_validation = @profile.validate(document)
|
|
27
|
+
|
|
28
|
+
# Create remediation engine
|
|
29
|
+
remediation_engine = SvgConform::RemediationEngine.new(@profile)
|
|
30
|
+
|
|
31
|
+
# Apply remediations if there are failures
|
|
32
|
+
remediation_results = []
|
|
33
|
+
if initial_validation.failed_requirements.any?
|
|
34
|
+
remediation_results = remediation_engine.apply_remediations(document,
|
|
35
|
+
initial_validation)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Perform final validation after remediations
|
|
39
|
+
final_validation = @profile.validate(document)
|
|
40
|
+
|
|
41
|
+
# Create result object
|
|
42
|
+
RemediationRunResult.new(
|
|
43
|
+
filename: filename,
|
|
44
|
+
original_content: svg_content,
|
|
45
|
+
remediated_content: document.to_xml,
|
|
46
|
+
initial_validation: initial_validation,
|
|
47
|
+
final_validation: final_validation,
|
|
48
|
+
remediation_results: remediation_results,
|
|
49
|
+
remediation_engine: remediation_engine,
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Run remediation on a file
|
|
54
|
+
def run_remediation_file(file_path)
|
|
55
|
+
raise "File not found: #{file_path}" unless File.exist?(file_path)
|
|
56
|
+
|
|
57
|
+
svg_content = File.read(file_path)
|
|
58
|
+
filename = File.basename(file_path)
|
|
59
|
+
|
|
60
|
+
run_remediation(svg_content, filename: filename)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Run remediation on multiple files
|
|
64
|
+
def run_remediation_batch(file_paths)
|
|
65
|
+
results = {}
|
|
66
|
+
|
|
67
|
+
file_paths.each do |file_path|
|
|
68
|
+
results[file_path] = run_remediation_file(file_path)
|
|
69
|
+
rescue StandardError => e
|
|
70
|
+
results[file_path] = create_error_result(file_path, e)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
results
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Check if profile has any remediations available
|
|
77
|
+
def has_remediations?
|
|
78
|
+
@profile.remediation_count.positive?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Get available remediations for the profile
|
|
82
|
+
def available_remediations
|
|
83
|
+
@profile.remediations
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def resolve_profile(profile)
|
|
89
|
+
case profile
|
|
90
|
+
when Symbol, String
|
|
91
|
+
SvgConform::Profiles.get(profile) || raise("Unknown profile: #{profile}")
|
|
92
|
+
when SvgConform::Profile
|
|
93
|
+
profile
|
|
94
|
+
else
|
|
95
|
+
raise "Invalid profile type: #{profile.class}"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def create_error_result(file_path, error)
|
|
100
|
+
RemediationRunResult.new(
|
|
101
|
+
filename: File.basename(file_path),
|
|
102
|
+
original_content: nil,
|
|
103
|
+
remediated_content: nil,
|
|
104
|
+
initial_validation: nil,
|
|
105
|
+
final_validation: nil,
|
|
106
|
+
remediation_results: [],
|
|
107
|
+
remediation_engine: nil,
|
|
108
|
+
error: error,
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Result object for overall remediation run (not individual remediation result)
|
|
114
|
+
class RemediationRunResult
|
|
115
|
+
attr_reader :filename, :original_content, :remediated_content,
|
|
116
|
+
:initial_validation, :final_validation, :remediation_results,
|
|
117
|
+
:remediation_engine, :error
|
|
118
|
+
|
|
119
|
+
def initialize(filename:, original_content:, remediated_content:,
|
|
120
|
+
initial_validation:, final_validation:, remediation_results:,
|
|
121
|
+
remediation_engine:, error: nil)
|
|
122
|
+
@filename = filename
|
|
123
|
+
@original_content = original_content
|
|
124
|
+
@remediated_content = remediated_content
|
|
125
|
+
@initial_validation = initial_validation
|
|
126
|
+
@final_validation = final_validation
|
|
127
|
+
@remediation_results = remediation_results
|
|
128
|
+
@remediation_engine = remediation_engine
|
|
129
|
+
@error = error
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Check if remediation was successful
|
|
133
|
+
def success?
|
|
134
|
+
@error.nil? && @final_validation&.valid?
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Check if there was an error
|
|
138
|
+
def error?
|
|
139
|
+
!@error.nil?
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Get the number of issues fixed
|
|
143
|
+
def issues_fixed
|
|
144
|
+
return 0 if @error || @initial_validation.nil? || @final_validation.nil?
|
|
145
|
+
|
|
146
|
+
initial_errors = @initial_validation.failed_requirements.length
|
|
147
|
+
final_errors = @final_validation.failed_requirements.length
|
|
148
|
+
|
|
149
|
+
[initial_errors - final_errors, 0].max
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Get the number of remediations applied
|
|
153
|
+
def remediations_applied
|
|
154
|
+
@remediation_results&.length || 0
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Get successful remediations
|
|
158
|
+
def successful_remediations
|
|
159
|
+
return [] if @remediation_results.nil?
|
|
160
|
+
|
|
161
|
+
@remediation_results.select(&:success?)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Get failed remediations
|
|
165
|
+
def failed_remediations
|
|
166
|
+
return [] if @remediation_results.nil?
|
|
167
|
+
|
|
168
|
+
@remediation_results.select(&:failure?)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Check if content was modified
|
|
172
|
+
def content_modified?
|
|
173
|
+
return false if @original_content.nil? || @remediated_content.nil?
|
|
174
|
+
|
|
175
|
+
normalize_content(@original_content) != normalize_content(@remediated_content)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Get summary of changes
|
|
179
|
+
def changes_summary
|
|
180
|
+
return "Error occurred" if @error
|
|
181
|
+
return "No validation performed" if @initial_validation.nil?
|
|
182
|
+
|
|
183
|
+
if content_modified?
|
|
184
|
+
"#{issues_fixed} issues fixed, #{remediations_applied} remediations applied"
|
|
185
|
+
else
|
|
186
|
+
"No changes needed"
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Convert to hash for serialization
|
|
191
|
+
def to_h
|
|
192
|
+
{
|
|
193
|
+
filename: @filename,
|
|
194
|
+
success: success?,
|
|
195
|
+
error: @error&.message,
|
|
196
|
+
issues_fixed: issues_fixed,
|
|
197
|
+
remediations_applied: remediations_applied,
|
|
198
|
+
content_modified: content_modified?,
|
|
199
|
+
changes_summary: changes_summary,
|
|
200
|
+
initial_errors: @initial_validation&.failed_requirements&.length || 0,
|
|
201
|
+
final_errors: @final_validation&.failed_requirements&.length || 0,
|
|
202
|
+
remediation_details: @remediation_results&.map(&:to_h) || [],
|
|
203
|
+
}
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Generate a conformance report for the final state
|
|
207
|
+
def generate_conformance_report
|
|
208
|
+
return nil if @final_validation.nil? || @filename.nil?
|
|
209
|
+
|
|
210
|
+
SvgConform::ConformanceReport.from_svg_conform_result(
|
|
211
|
+
@filename,
|
|
212
|
+
@final_validation,
|
|
213
|
+
profile: @remediation_engine&.profile&.name || :unknown,
|
|
214
|
+
use_svgcheck_mapping: true,
|
|
215
|
+
)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
private
|
|
219
|
+
|
|
220
|
+
def normalize_content(content)
|
|
221
|
+
# Normalize whitespace and formatting for comparison
|
|
222
|
+
content.gsub(/\s+/, " ").strip
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
require_relative "../remediation_result"
|
|
5
|
+
|
|
6
|
+
module SvgConform
|
|
7
|
+
module Remediations
|
|
8
|
+
# Base class for all remediations using lutaml-model serialization
|
|
9
|
+
class BaseRemediation < Lutaml::Model::Serializable
|
|
10
|
+
attribute :id, :string
|
|
11
|
+
attribute :description, :string
|
|
12
|
+
attribute :targets, :string, collection: true
|
|
13
|
+
attribute :config, :hash
|
|
14
|
+
attribute :type, :string, polymorphic_class: true, default: -> {
|
|
15
|
+
self.class.name.split("::").last
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
yaml do
|
|
19
|
+
map "id", to: :id
|
|
20
|
+
map "description", to: :description
|
|
21
|
+
map "targets", to: :targets
|
|
22
|
+
map "config", to: :config
|
|
23
|
+
map "type", to: :type
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def apply(document, context)
|
|
27
|
+
raise NotImplementedError, "Subclasses must implement apply"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Check if this remediation should execute based on failed requirements
|
|
31
|
+
def should_execute?(failed_requirements)
|
|
32
|
+
failed_requirement_ids = failed_requirements.map(&:requirement_id)
|
|
33
|
+
targets.any? { |req_id| failed_requirement_ids.include?(req_id) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Execute the remediation and return a result
|
|
37
|
+
def execute(document, failed_requirements)
|
|
38
|
+
unless should_execute?(failed_requirements)
|
|
39
|
+
return RemediationResult.new(
|
|
40
|
+
remediation_id: @id,
|
|
41
|
+
success: false,
|
|
42
|
+
failed_requirements: [],
|
|
43
|
+
message: "Remediation not applicable",
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
context = { failed_requirements: failed_requirements }
|
|
49
|
+
changes_made = apply(document, context)
|
|
50
|
+
|
|
51
|
+
RemediationResult.new(
|
|
52
|
+
remediation_id: @id,
|
|
53
|
+
success: true,
|
|
54
|
+
failed_requirements: failed_requirements,
|
|
55
|
+
message: "Remediation applied successfully",
|
|
56
|
+
changes_made: changes_made,
|
|
57
|
+
)
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
RemediationResult.new(
|
|
60
|
+
remediation_id: @id,
|
|
61
|
+
success: false,
|
|
62
|
+
failed_requirements: failed_requirements,
|
|
63
|
+
message: "Remediation failed: #{e.message}",
|
|
64
|
+
error: e,
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def to_s
|
|
70
|
+
"#{id}: #{description} (targets: #{targets.join(', ')})"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
protected
|
|
74
|
+
|
|
75
|
+
def element?(node)
|
|
76
|
+
node.respond_to?(:name) && node.name
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def get_attribute(node, attr_name)
|
|
80
|
+
return nil unless node.respond_to?(:[])
|
|
81
|
+
|
|
82
|
+
node[attr_name]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def set_attribute(node, attr_name, value)
|
|
86
|
+
return unless node.respond_to?(:[]=)
|
|
87
|
+
|
|
88
|
+
node[attr_name] = value
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def has_attribute?(node, attr_name)
|
|
92
|
+
return false unless node.respond_to?(:[])
|
|
93
|
+
|
|
94
|
+
!node[attr_name].nil?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def find_nodes(document, &)
|
|
98
|
+
nodes = []
|
|
99
|
+
traverse_nodes(document, nodes, &)
|
|
100
|
+
nodes
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Helper method to remove attribute
|
|
104
|
+
def remove_attribute(node, name)
|
|
105
|
+
if node.respond_to?(:remove_attribute)
|
|
106
|
+
node.remove_attribute(name)
|
|
107
|
+
true
|
|
108
|
+
elsif node.respond_to?(:[]=) && node.respond_to?(:[])
|
|
109
|
+
# Fallback for different implementations
|
|
110
|
+
node[name] = nil
|
|
111
|
+
true
|
|
112
|
+
else
|
|
113
|
+
false
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Helper method to remove node
|
|
118
|
+
def remove_node(node)
|
|
119
|
+
return false unless node.respond_to?(:remove)
|
|
120
|
+
|
|
121
|
+
node.remove
|
|
122
|
+
true
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Helper method to replace node
|
|
126
|
+
def replace_node(node, replacement)
|
|
127
|
+
return false unless node.respond_to?(:replace)
|
|
128
|
+
|
|
129
|
+
node.replace(replacement)
|
|
130
|
+
true
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Helper method to create comment node
|
|
134
|
+
def create_comment(document, text)
|
|
135
|
+
document.create_comment(text)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def log_change(type, message, node = nil)
|
|
139
|
+
{
|
|
140
|
+
type: type,
|
|
141
|
+
message: message,
|
|
142
|
+
node: node&.name || "unknown",
|
|
143
|
+
node_attributes: node.respond_to?(:attributes) ? node.attributes : nil,
|
|
144
|
+
timestamp: Time.now,
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
def traverse_nodes(node, nodes, &block)
|
|
151
|
+
return unless node
|
|
152
|
+
|
|
153
|
+
# Add node if it matches the block condition
|
|
154
|
+
nodes << node if yield(node)
|
|
155
|
+
|
|
156
|
+
# Traverse children if the node supports it
|
|
157
|
+
if node.respond_to?(:children)
|
|
158
|
+
node.children.each { |child| traverse_nodes(child, nodes, &block) }
|
|
159
|
+
elsif node.respond_to?(:each)
|
|
160
|
+
node.each { |child| traverse_nodes(child, nodes, &block) }
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|