svg_conform 0.2.4 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +1 -0
  3. data/.github/workflows/rake.yml +4 -1
  4. data/Gemfile +6 -3
  5. data/Gemfile.lock +211 -0
  6. data/TODO.issue-100/00-overview.md +135 -0
  7. data/TODO.issue-100/01-build-lens-core-v1.md +92 -0
  8. data/TODO.issue-100/02-adopt-lens-core-in-svg.md +123 -0
  9. data/TODO.issue-100/03-ship-svg-1-1-full-static.md +138 -0
  10. data/TODO.issue-100/04-ship-svg-2-secure-static.md +131 -0
  11. data/TODO.issue-100/05-freeze-candidate-and-handoff.md +108 -0
  12. data/TODO.issue-100/90-progress.md +65 -0
  13. data/config/profiles/metanorma.yml +4 -4
  14. data/config/profiles/svg_1_1_full_static.yml +69 -0
  15. data/config/profiles/svg_2_secure_static.yml +55 -0
  16. data/config/requirements/processing_features_static.yml +29 -0
  17. data/data/svg_1_1/manifest.yml +7 -0
  18. data/data/svg_1_1/svg11-flat-20110816.dtd +5664 -0
  19. data/docs/api_reference.adoc +19 -1
  20. data/docs/cli_guide.adoc +35 -45
  21. data/docs/diagnostics.adoc +73 -0
  22. data/docs/lens_core_handoff.adoc +84 -0
  23. data/docs/profiles.adoc +94 -1
  24. data/lib/svg_conform/cli.rb +3 -4
  25. data/lib/svg_conform/commands/check.rb +11 -60
  26. data/lib/svg_conform/conformance_report.rb +79 -1
  27. data/lib/svg_conform/diagnostics/lens_core_adapter.rb +200 -0
  28. data/lib/svg_conform/document.rb +12 -1
  29. data/lib/svg_conform/external_checkers/svgcheck/parser.rb +1 -3
  30. data/lib/svg_conform/node_helpers.rb +1 -1
  31. data/lib/svg_conform/profile.rb +11 -2
  32. data/lib/svg_conform/quality_metrics/complexity_calculator.rb +3 -3
  33. data/lib/svg_conform/quality_metrics/configuration.rb +4 -2
  34. data/lib/svg_conform/references/base_reference.rb +153 -0
  35. data/lib/svg_conform/references/reference_manifest.rb +1 -2
  36. data/lib/svg_conform/report_comparator.rb +4 -4
  37. data/lib/svg_conform/requirements/base_requirement.rb +6 -0
  38. data/lib/svg_conform/requirements/external_references_requirement.rb +234 -0
  39. data/lib/svg_conform/requirements/link_validation_requirement.rb +2 -0
  40. data/lib/svg_conform/requirements/processing_features_requirement.rb +175 -0
  41. data/lib/svg_conform/requirements/svg_1_1_document_requirement.rb +213 -0
  42. data/lib/svg_conform/requirements.rb +6 -0
  43. data/lib/svg_conform/sax_document.rb +8 -4
  44. data/lib/svg_conform/sax_validation_handler.rb +22 -1
  45. data/lib/svg_conform/validation/css2_core_grammar.rb +359 -0
  46. data/lib/svg_conform/validation/css_references.rb +122 -0
  47. data/lib/svg_conform/validation/error_tracker.rb +40 -9
  48. data/lib/svg_conform/validation/svg_1_1_document_conformance.rb +154 -0
  49. data/lib/svg_conform/validation_context.rb +25 -5
  50. data/lib/svg_conform/validation_result.rb +91 -16
  51. data/lib/svg_conform/version.rb +1 -1
  52. data/lib/svg_conform/yaml_io.rb +20 -0
  53. data/lib/svg_conform.rb +25 -0
  54. data/spec/fixtures/diagnostics/external-reference-info.svg +6 -0
  55. data/spec/fixtures/plantuml/README.adoc +11 -0
  56. data/spec/fixtures/plantuml/unit-type.svg +1 -0
  57. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/animation.svg +4 -0
  58. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/css-grammar.svg +4 -0
  59. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/dtd-attribute.svg +4 -0
  60. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/dtd-content-model.svg +4 -0
  61. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/interaction.svg +4 -0
  62. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/malformed-xml.svg +4 -0
  63. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/script.svg +5 -0
  64. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/wrong-namespace.svg +4 -0
  65. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/wrong-version.svg +4 -0
  66. data/spec/fixtures/profiles/svg_1_1_full_static/invalid/xml-stylesheet.svg +5 -0
  67. data/spec/fixtures/profiles/svg_1_1_full_static/valid/css.svg +5 -0
  68. data/spec/fixtures/profiles/svg_1_1_full_static/valid/foreign-metadata.svg +6 -0
  69. data/spec/fixtures/profiles/svg_1_1_full_static/valid/minimal-full.svg +5 -0
  70. data/spec/fixtures/profiles/svg_1_1_full_static/valid/xml-stylesheet.svg +5 -0
  71. data/spec/fixtures/profiles/svg_2_secure_static/invalid/animation.svg +4 -0
  72. data/spec/fixtures/profiles/svg_2_secure_static/invalid/css-import.svg +4 -0
  73. data/spec/fixtures/profiles/svg_2_secure_static/invalid/css-url.svg +4 -0
  74. data/spec/fixtures/profiles/svg_2_secure_static/invalid/data-bad-png.svg +4 -0
  75. data/spec/fixtures/profiles/svg_2_secure_static/invalid/data-empty-png.svg +4 -0
  76. data/spec/fixtures/profiles/svg_2_secure_static/invalid/data-malformed-base64.svg +4 -0
  77. data/spec/fixtures/profiles/svg_2_secure_static/invalid/data-malformed-percent.svg +4 -0
  78. data/spec/fixtures/profiles/svg_2_secure_static/invalid/data-unsupported-type.svg +4 -0
  79. data/spec/fixtures/profiles/svg_2_secure_static/invalid/file-like-fragment.svg +4 -0
  80. data/spec/fixtures/profiles/svg_2_secure_static/invalid/http.svg +4 -0
  81. data/spec/fixtures/profiles/svg_2_secure_static/invalid/https.svg +4 -0
  82. data/spec/fixtures/profiles/svg_2_secure_static/invalid/interaction.svg +4 -0
  83. data/spec/fixtures/profiles/svg_2_secure_static/invalid/protocol-relative.svg +4 -0
  84. data/spec/fixtures/profiles/svg_2_secure_static/invalid/relative-file-fragment.svg +4 -0
  85. data/spec/fixtures/profiles/svg_2_secure_static/invalid/relative-file.svg +4 -0
  86. data/spec/fixtures/profiles/svg_2_secure_static/invalid/script.svg +4 -0
  87. data/spec/fixtures/profiles/svg_2_secure_static/invalid/svg-data-excessive-nesting.svg +4 -0
  88. data/spec/fixtures/profiles/svg_2_secure_static/invalid/svg-data-malformed-xml.svg +4 -0
  89. data/spec/fixtures/profiles/svg_2_secure_static/invalid/svg-data-nested-external.svg +4 -0
  90. data/spec/fixtures/profiles/svg_2_secure_static/invalid/urn.svg +4 -0
  91. data/spec/fixtures/profiles/svg_2_secure_static/invalid/xml-stylesheet.svg +5 -0
  92. data/spec/fixtures/profiles/svg_2_secure_static/valid/jpeg-base64.svg +4 -0
  93. data/spec/fixtures/profiles/svg_2_secure_static/valid/jpeg-percent.svg +4 -0
  94. data/spec/fixtures/profiles/svg_2_secure_static/valid/png-base64.svg +4 -0
  95. data/spec/fixtures/profiles/svg_2_secure_static/valid/png-percent.svg +4 -0
  96. data/spec/fixtures/profiles/svg_2_secure_static/valid/same-document-fragment.svg +4 -0
  97. data/spec/fixtures/profiles/svg_2_secure_static/valid/svg-base64.svg +4 -0
  98. data/spec/fixtures/profiles/svg_2_secure_static/valid/svg-percent.svg +4 -0
  99. data/spec/fixtures/quality_metrics/aliases.yml +7 -0
  100. data/spec/svg_conform/commands/check_command_spec.rb +22 -2
  101. data/spec/svg_conform/commands/profiles_command_spec.rb +18 -0
  102. data/spec/svg_conform/conformance_report_spec.rb +90 -0
  103. data/spec/svg_conform/diagnostics/lens_core_adapter_spec.rb +136 -0
  104. data/spec/svg_conform/gemspec_spec.rb +34 -0
  105. data/spec/svg_conform/profiles/metanorma_profile_spec.rb +48 -0
  106. data/spec/svg_conform/profiles/svg_1_1_full_static_profile_spec.rb +146 -0
  107. data/spec/svg_conform/profiles/svg_2_secure_static_profile_spec.rb +150 -0
  108. data/spec/svg_conform/profiles_plantuml_spec.rb +81 -0
  109. data/spec/svg_conform/quality_metrics/configuration_spec.rb +82 -0
  110. data/spec/svg_conform/references/data_uri_reference_spec.rb +97 -0
  111. data/spec/svg_conform/references/integration_spec.rb +30 -0
  112. data/spec/svg_conform/references/reference_classifier_spec.rb +17 -0
  113. data/spec/svg_conform/requirements/external_references_requirement_spec.rb +109 -0
  114. data/spec/svg_conform/requirements/link_validation_requirement_spec.rb +16 -0
  115. data/spec/svg_conform/requirements/processing_features_requirement_spec.rb +75 -0
  116. data/spec/svg_conform/requirements/svg_1_1_document_requirement_spec.rb +141 -0
  117. data/spec/svg_conform/requirements/text_as_path_requirement_spec.rb +9 -5
  118. data/spec/svg_conform/validation/css2_core_grammar_spec.rb +116 -0
  119. data/spec/svg_conform/validation/svg_1_1_document_conformance_spec.rb +144 -0
  120. data/spec/svg_conform/validation_context_source_plumbing_spec.rb +122 -0
  121. data/spec/svg_conform/validation_context_spec.rb +39 -0
  122. data/spec/svg_conform/validation_result_spec.rb +123 -0
  123. data/spec/svg_conform/yaml_io_spec.rb +41 -0
  124. data/svg_conform.gemspec +4 -2
  125. metadata +120 -6
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module SvgConform
6
+ module Validation
7
+ # Validates an SVG 1.1 document against the vendored SVG 1.1 flat DTD,
8
+ # following the validity procedure of SVG 1.1 Appendix G.2
9
+ # (conform.html#ConformingSVGDocuments).
10
+ #
11
+ # The DTD is resolved from the local data/svg_1_1 directory only. The
12
+ # caller's source is never modified: the Appendix G.2 transform is built
13
+ # from a separate parse of it, and a document that declares an external
14
+ # entity is rejected before any entity is expanded.
15
+ #
16
+ # Library choice (see the validator spec for the probe results): the
17
+ # bundled Moxml/Leptris stack parses the flat DTD but does not apply its
18
+ # conditional sections, so it accepts every document. Nokogiri (libxml2)
19
+ # validates the flat DTD offline.
20
+ class Svg11DocumentConformance
21
+ SVG_NS = "http://www.w3.org/2000/svg"
22
+ XLINK_NS = "http://www.w3.org/1999/xlink"
23
+ XML_NS = "http://www.w3.org/XML/1998/namespace"
24
+ DATA_DIR = File.expand_path("../../../data/svg_1_1", __dir__)
25
+ DTD_PATH = File.join(DATA_DIR, "svg11-flat-20110816.dtd")
26
+ MANIFEST_PATH = File.join(DATA_DIR, "manifest.yml")
27
+ EXTERNAL_ENTITY = /<!ENTITY\s+(?:%\s+)?[^\s>]+\s+(?:SYSTEM|PUBLIC)\b/m
28
+
29
+ Issue = Struct.new(:kind, :message, :line, keyword_init: true)
30
+
31
+ # Outcome of one validation. +transformed+ is the Appendix G.2 copy
32
+ # that was validated (nil when no copy could be built).
33
+ Result = Struct.new(:issues, :transformed, keyword_init: true) do
34
+ def valid?
35
+ issues.empty?
36
+ end
37
+ end
38
+
39
+ def initialize(dtd_path: DTD_PATH)
40
+ @dtd_path = dtd_path
41
+ end
42
+
43
+ def validate(source)
44
+ source = source.to_s
45
+ return failure(:external_entity, "external entities are not permitted") if external_entity?(source)
46
+
47
+ parsed = parse_expanded(source)
48
+ return parsed if parsed.is_a?(Result)
49
+
50
+ copy = transform(parsed)
51
+ return failure(:no_svg_root, "document element is not an SVG element") unless copy
52
+
53
+ Result.new(issues: dtd_issues(copy), transformed: copy)
54
+ end
55
+
56
+ # Appendix G.2: drop foreign subtrees and attributes, drop prefixes,
57
+ # keep xlink attributes under the xlink prefix, and set the two
58
+ # namespace declarations on the document element. Returns the serialized
59
+ # copy (with the DOCTYPE for the local DTD) or nil without an SVG root.
60
+ def transform(doc)
61
+ root = doc.root
62
+ return unless root&.namespace&.href == SVG_NS
63
+
64
+ body = +""
65
+ write_element(root, body, root: true)
66
+ %(<?xml version="#{doc.version || '1.0'}" encoding="UTF-8"?>\n) +
67
+ %(<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "#{dtd_uri}">\n#{body}\n)
68
+ end
69
+
70
+ private
71
+
72
+ # A file URI that libxml2 accepts for POSIX paths and for Windows drive
73
+ # paths ("D:\\a\\x" becomes "file:///D:/a/x").
74
+ def dtd_uri
75
+ path = @dtd_path.tr("\\", "/")
76
+ path = "/#{path}" unless path.start_with?("/")
77
+ escaped = path.gsub(%r{[^A-Za-z0-9\-._~/:]}) { |char| char.bytes.map { |b| format("%%%02X", b) }.join }
78
+ "file://#{escaped}"
79
+ end
80
+
81
+ def failure(kind, message, line = nil)
82
+ Result.new(issues: [Issue.new(kind: kind, message: message, line: line)], transformed: nil)
83
+ end
84
+
85
+ def external_entity?(source)
86
+ source.match?(EXTERNAL_ENTITY)
87
+ end
88
+
89
+ # Entities are expanded (Appendix G.2) only after the source is known
90
+ # to declare no external entity, so expansion cannot read a file.
91
+ def parse_expanded(source)
92
+ Nokogiri::XML::Document.parse(source) { |config| config.strict.noent.nonet }
93
+ rescue Nokogiri::XML::SyntaxError => e
94
+ failure(:malformed_xml, e.message, e.line)
95
+ end
96
+
97
+ def write_element(node, out, root: false)
98
+ out << "<" << node.name
99
+ if root
100
+ out << %( xmlns="#{SVG_NS}" xmlns:xlink="#{XLINK_NS}")
101
+ end
102
+ node.attribute_nodes.each { |attr| write_attribute(attr, out) }
103
+ children = node.children
104
+ if children.empty?
105
+ out << "/>"
106
+ else
107
+ out << ">"
108
+ children.each { |child| write_node(child, out) }
109
+ out << "</" << node.name << ">"
110
+ end
111
+ end
112
+
113
+ def write_attribute(attr, out)
114
+ href = attr.namespace&.href
115
+ name = case href
116
+ when nil then attr.name
117
+ when XLINK_NS then "xlink:#{attr.name}"
118
+ when XML_NS then "xml:#{attr.name}"
119
+ end
120
+ out << " " << name << %(="#{escape_attribute(attr.value)}") if name
121
+ end
122
+
123
+ def write_node(node, out)
124
+ case node.type
125
+ when Nokogiri::XML::Node::ELEMENT_NODE
126
+ write_element(node, out) if node.namespace&.href == SVG_NS
127
+ when Nokogiri::XML::Node::TEXT_NODE, Nokogiri::XML::Node::CDATA_SECTION_NODE
128
+ out << escape_text(node.content)
129
+ when Nokogiri::XML::Node::COMMENT_NODE
130
+ out << "<!--" << node.content << "-->"
131
+ end
132
+ end
133
+
134
+ def escape_text(text)
135
+ text.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
136
+ end
137
+
138
+ def escape_attribute(text)
139
+ escape_text(text).gsub('"', "&quot;").gsub("\t", "&#9;")
140
+ .gsub("\n", "&#10;").gsub("\r", "&#13;")
141
+ end
142
+
143
+ def dtd_issues(copy)
144
+ doc = Nokogiri::XML::Document.parse(copy) do |config|
145
+ config.dtdload.dtdvalid.nonet
146
+ end
147
+ doc.errors.map do |error|
148
+ Issue.new(kind: :dtd, message: error.message.sub(/\A\d+:\d+: ERROR: /, ""),
149
+ line: error.line)
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -100,6 +100,12 @@ module SvgConform
100
100
  # The document being validated (nil in SAX mode)
101
101
  attr_reader :document
102
102
 
103
+ # Original input text (nil when only a parsed tree was available)
104
+ attr_reader :source
105
+
106
+ # File path the source was read from, when known
107
+ attr_reader :source_path
108
+
103
109
  # The profile containing requirements to validate against
104
110
  attr_reader :profile
105
111
 
@@ -126,9 +132,13 @@ module SvgConform
126
132
  @error_tracker.infos
127
133
  end
128
134
 
129
- def initialize(document, profile)
135
+ def initialize(document, profile, source: nil, source_path: nil,
136
+ source_serialized: false)
130
137
  @document = document
131
138
  @profile = profile
139
+ @source = source
140
+ @source_path = source_path
141
+ @source_serialized = source_serialized
132
142
 
133
143
  # Use TrackerFactory to create all tracker instances
134
144
  trackers = Validation::TrackerFactory.create_all_trackers(document)
@@ -143,6 +153,12 @@ module SvgConform
143
153
 
144
154
  # Get or create state for a requirement
145
155
  # Each requirement gets its own state instance, preventing state pollution
156
+ # True when #source was re-serialized from a parsed tree, so original
157
+ # declaration/doctype/processing instructions cannot be proved.
158
+ def source_serialized?
159
+ @source_serialized ? true : false
160
+ end
161
+
146
162
  def state_for(requirement)
147
163
  @state_registry[requirement.class] ||= requirement.class::State.new
148
164
  end
@@ -178,9 +194,11 @@ requirement_id: nil, severity: nil, fix: nil, data: {})
178
194
  end
179
195
 
180
196
  # Add an informational notice (delegates to ErrorTracker)
181
- def add_notice(rule:, node:, message:, fix: nil, data: {})
197
+ def add_notice(rule:, node:, message:, fix: nil, data: {},
198
+ requirement_id: nil)
182
199
  @error_tracker.add_notice(rule: rule, node: node, message: message,
183
- fix: fix, data: data)
200
+ fix: fix, data: data,
201
+ requirement_id: requirement_id)
184
202
  end
185
203
 
186
204
  def add_fix(fix)
@@ -229,12 +247,14 @@ column_number: nil)
229
247
  end
230
248
 
231
249
  # Add notice for external references (not errors)
232
- def add_external_reference_notice(node:, reference:, message: nil)
250
+ def add_external_reference_notice(node:, reference:, message: nil,
251
+ requirement_id: nil)
233
252
  @error_tracker.add_notice(
234
253
  rule: nil,
235
254
  node: node,
236
255
  message: message || "External reference: #{reference.value}",
237
- data: { reference: reference },
256
+ data: { reference: reference.to_h },
257
+ requirement_id: requirement_id,
238
258
  )
239
259
  end
240
260
 
@@ -6,7 +6,7 @@ module SvgConform
6
6
  # Represents the result of validating an SVG document
7
7
  class ValidationResult
8
8
  attr_reader :document, :profile, :errors, :warnings, :validity_errors,
9
- :fixes_applied, :fixed_document, :reference_manifest
9
+ :infos, :fixes_applied, :fixed_document, :reference_manifest
10
10
 
11
11
  def initialize(document, profile, context)
12
12
  @document = document
@@ -14,6 +14,7 @@ module SvgConform
14
14
  @errors = context.errors
15
15
  @warnings = context.warnings
16
16
  @validity_errors = context.validity_errors
17
+ @infos = context.infos
17
18
  @fixes_applied = []
18
19
  @fixed_document = nil
19
20
  @reference_manifest = context.reference_manifest
@@ -23,8 +24,9 @@ module SvgConform
23
24
  @errors.empty? && @validity_errors.empty?
24
25
  end
25
26
 
27
+ # True for errors and validity errors, matching valid?.
26
28
  def has_errors?
27
- !@errors.empty?
29
+ error_count.positive?
28
30
  end
29
31
 
30
32
  def has_warnings?
@@ -39,14 +41,19 @@ module SvgConform
39
41
  !@fixed_document.nil?
40
42
  end
41
43
 
44
+ # Errors plus validity errors: both are "error" in the canonical report.
42
45
  def error_count
43
- @errors.size
46
+ @errors.size + @validity_errors.size
44
47
  end
45
48
 
46
49
  def warning_count
47
50
  @warnings.size
48
51
  end
49
52
 
53
+ def info_count
54
+ @infos.size
55
+ end
56
+
50
57
  def issue_count
51
58
  error_count + warning_count
52
59
  end
@@ -114,21 +121,46 @@ module SvgConform
114
121
  end
115
122
  end
116
123
 
124
+ # The canonical LensCore::Report for this result (schema version 1).
125
+ # The timestamp is only included when the caller supplies one.
126
+ # Raises when the optional lens_core gem is not installed.
127
+ def to_lens_core_report(timestamp: nil)
128
+ unless SvgConform.lens_core_available?
129
+ raise SvgConform::Error,
130
+ "lens_core is not installed; the canonical diagnostics " \
131
+ "report requires it"
132
+ end
133
+ Diagnostics::LensCoreAdapter.call(self, timestamp: timestamp)
134
+ end
135
+
136
+ # Report as a symbol-keyed Hash: the canonical LensCore shape (with
137
+ # the compatibility extensions +file+, +fixes_applied+, +fixable+ and
138
+ # +reference_manifest+) when lens_core is installed, else the plain
139
+ # structural shape.
117
140
  def to_h
118
- {
119
- file: @document&.file_path,
120
- profile: @profile.name,
121
- valid: valid?,
122
- errors: @errors.map(&:to_h),
123
- warnings: @warnings.map(&:to_h),
124
- fixes_applied: @fixes_applied.size,
125
- fixable: fixable_count,
126
- reference_manifest: @reference_manifest.to_h,
127
- }
141
+ if SvgConform.lens_core_available?
142
+ JSON.parse(to_json, symbolize_names: true)
143
+ else
144
+ structural_h
145
+ end
128
146
  end
129
147
 
130
- def to_json(*args)
131
- JSON.pretty_generate(to_h, *args)
148
+ # Canonical LensCore JSON when lens_core is installed, else the plain
149
+ # structural serialization.
150
+ def to_json(*)
151
+ if SvgConform.lens_core_available?
152
+ LensCore::Renderers::Json.render(compatibility_report).chomp
153
+ else
154
+ JSON.pretty_generate(structural_h)
155
+ end
156
+ end
157
+
158
+ def to_yaml(*)
159
+ if SvgConform.lens_core_available?
160
+ LensCore::Renderers::Yaml.render(compatibility_report)
161
+ else
162
+ structural_h.to_yaml
163
+ end
132
164
  end
133
165
 
134
166
  # Export manifest separately for detailed analysis
@@ -147,7 +179,33 @@ module SvgConform
147
179
 
148
180
  private
149
181
 
182
+ def compatibility_report(timestamp: nil)
183
+ report = to_lens_core_report(timestamp: timestamp)
184
+ extensions = report.extensions.merge(
185
+ "fixes_applied" => @fixes_applied.size,
186
+ "fixable" => fixable_count,
187
+ "reference_manifest" => Diagnostics::LensCoreAdapter.json_value(
188
+ @reference_manifest.to_h, "reference_manifest"
189
+ ),
190
+ )
191
+ LensCore::Report.new(
192
+ producer: report.producer, profile: report.profile,
193
+ valid: report.valid, diagnostics: report.diagnostics,
194
+ timestamp: report.timestamp, extensions: extensions
195
+ )
196
+ end
197
+
150
198
  def to_text
199
+ if SvgConform.lens_core_available?
200
+ lines = [LensCore::Renderers::Human.render(to_lens_core_report).chomp]
201
+ if fixable?
202
+ lines << ""
203
+ lines << "Fixes Available: #{fixable_count}"
204
+ lines << "Fixes Applied: #{@fixes_applied.size}" if fixed?
205
+ end
206
+ return lines.join("\n")
207
+ end
208
+
151
209
  lines = []
152
210
  lines << "SVG Conformance Report"
153
211
  lines << ("=" * 22)
@@ -179,9 +237,26 @@ module SvgConform
179
237
  lines << ""
180
238
  end
181
239
 
182
- lines << "✓ Document is valid and conforms to the #{@profile.name} profile." if valid? && !has_warnings?
240
+ if valid? && !has_warnings?
241
+ lines << "✓ Document is valid and conforms to the #{@profile.name} profile."
242
+ end
183
243
 
184
244
  lines.join("\n")
185
245
  end
246
+
247
+ # The plain structural shape (the 0.2.4 serialization), used when the
248
+ # optional lens_core renderer is not installed.
249
+ def structural_h
250
+ {
251
+ file: @document&.file_path,
252
+ profile: @profile.name,
253
+ valid: valid?,
254
+ errors: @errors.map(&:to_h),
255
+ warnings: @warnings.map(&:to_h),
256
+ fixes_applied: @fixes_applied.size,
257
+ fixable: fixable_count,
258
+ reference_manifest: @reference_manifest.to_h,
259
+ }
260
+ end
186
261
  end
187
262
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SvgConform
4
- VERSION = "0.2.4"
4
+ VERSION = "0.2.7"
5
5
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yeptris/psych"
4
+
5
+ module SvgConform
6
+ # YAML operations through Yeptris without installing its global drop-in.
7
+ module YamlIO
8
+ def self.load_file(...)
9
+ Yeptris::Psych.load_file(...)
10
+ end
11
+
12
+ def self.safe_load_file(...)
13
+ Yeptris::Psych.safe_load_file(...)
14
+ end
15
+
16
+ def self.dump(...)
17
+ Yeptris::Psych.dump(...)
18
+ end
19
+ end
20
+ end
data/lib/svg_conform.rb CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  require "lutaml/model"
4
4
 
5
+ # lens_core is optional (0.2.5 shipped it as a hard dependency while the
6
+ # gem is still unpublished, which made every fresh resolve unsolvable).
7
+ # When present it renders the canonical diagnostics reports; when absent
8
+ # the plain structural serializers answer instead.
9
+ begin
10
+ require "lens_core"
11
+ rescue LoadError
12
+ nil
13
+ end
14
+
5
15
  # Configure Lutaml::Model adapters
6
16
  Lutaml::Model::Config.configure do |config|
7
17
  config.xml_adapter_type = :leptris
@@ -39,7 +49,22 @@ module SvgConform
39
49
  class ProfileError < Error; end
40
50
  class RuleError < Error; end
41
51
 
52
+ # True when the optional lens_core diagnostics renderer is installed.
53
+ def self.lens_core_available?
54
+ return @lens_core_available unless @lens_core_available.nil?
55
+
56
+ @lens_core_available = begin
57
+ require "lens_core"
58
+ true
59
+ rescue LoadError
60
+ false
61
+ end
62
+ end
63
+
64
+ require_relative "svg_conform/diagnostics/lens_core_adapter"
65
+
42
66
  # Autoload core classes
67
+ autoload :YamlIO, File.expand_path("svg_conform/yaml_io", __dir__)
43
68
  autoload :Validator, File.expand_path("svg_conform/validator", __dir__)
44
69
  autoload :Fixer, "svg_conform/fixer"
45
70
  autoload :RemediationEngine, "svg_conform/remediation_engine"
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny" viewBox="0 0 100 100">
3
+ <a xlink:href="https://example.com/page">
4
+ <rect x="10" y="10" width="50" height="50"/>
5
+ </a>
6
+ </svg>
@@ -0,0 +1,11 @@
1
+ = PlantUML SVG regression fixture
2
+
3
+ `unit-type.svg` is native output from PlantUML 1.2026.3 (bundled with metanorma-plugin-plantuml 1.0.15) using the Smetana layout engine.
4
+ It represents the UnitsML XML Schema `UnitType` content model used by
5
+ https://github.com/unitsml/docs/pull/9[UnitsML documentation PR 9].
6
+ The embedded PlantUML source records its schema labels and diagram input.
7
+
8
+ The diagram links target anchors in the surrounding documentation page.
9
+ Standalone profile tests therefore check attribute validation separately from
10
+ those document-level references; the UnitsML documentation checks the targets.
11
+ The test preserves the SVG's measured text layout and link titles.
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" data-diagram-type="CLASS" height="482px" preserveAspectRatio="none" style="width:764px;height:482px;background:#FFFFFF;" version="1.1" viewBox="0 0 764 482" width="764px" zoomAndPan="magnify"><?plantuml 1.2026.3?><defs/><g><!--class schema_UnitType--><g class="entity" data-qualified-name="schema_UnitType" data-source-line="7" id="ent0002"><a href="#xsd_UnitType" target="_top" title="#xsd_UnitType" xlink:actuate="onRequest" xlink:href="#xsd_UnitType" xlink:show="new" xlink:title="#xsd_UnitType" xlink:type="simple"><rect fill="#F1F1F1" height="83.1875" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="261.0264" x="6" y="202.9611"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="62.8359" x="105.0952" y="220.9562">UnitType</text><line style="stroke:#181818;stroke-width:0.5;" x1="7" x2="266.0264" y1="229.258" y2="229.258"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="81.5869" x="12" y="246.2531">@xml:id [1]</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="242.2588" x="12" y="262.55">@timeStamp : xsd:dateTime [0..1]</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="249.0264" x="12" y="278.8469">@dimensionURL : xsd:anyURI [0..1]</text></a></g><!--class schema_UnitType_1--><g class="entity" data-qualified-name="schema_UnitType_1" data-source-line="15" id="ent0003"><rect fill="#F1F1F1" height="26.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="75.0566" x="319" y="231.4611"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="69.0566" x="322" y="249.4562">Sequence</text></g><!--class schema_UnitType_1_0--><g class="entity" data-qualified-name="schema_UnitType_1_0" data-source-line="17" id="ent0005"><a href="#xsd_UnitSystem" target="_top" title="#xsd_UnitSystem" xlink:actuate="onRequest" xlink:href="#xsd_UnitSystem" xlink:show="new" xlink:title="#xsd_UnitSystem" xlink:type="simple"><rect fill="#F1F1F1" height="26.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="186.8652" x="526" y="11.4611"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="180.8652" x="529" y="29.4562">UnitSystem : SystemType</text></a></g><!--class schema_UnitType_1_1--><g class="entity" data-qualified-name="schema_UnitType_1_1" data-source-line="19" id="ent0007"><a href="#xsd_UnitName" target="_top" title="#xsd_UnitName" xlink:actuate="onRequest" xlink:href="#xsd_UnitName" xlink:show="new" xlink:title="#xsd_UnitName" xlink:type="simple"><rect fill="#F1F1F1" height="26.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="165.0586" x="537" y="55.4611"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="159.0586" x="540" y="73.4562">UnitName : NameType</text></a></g><!--class schema_UnitType_1_2--><g class="entity" data-qualified-name="schema_UnitType_1_2" data-source-line="21" id="ent0009"><a href="#xsd_UnitSymbol" target="_top" title="#xsd_UnitSymbol" xlink:actuate="onRequest" xlink:href="#xsd_UnitSymbol" xlink:show="new" xlink:title="#xsd_UnitSymbol" xlink:type="simple"><rect fill="#F1F1F1" height="26.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="186.7559" x="526" y="99.4611"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="180.7559" x="529" y="117.4562">UnitSymbol : SymbolType</text></a></g><!--class schema_UnitType_1_3--><g class="entity" data-qualified-name="schema_UnitType_1_3" data-source-line="23" id="ent0011"><a href="#xsd_CodeListValue" target="_top" title="#xsd_CodeListValue" xlink:actuate="onRequest" xlink:href="#xsd_CodeListValue" xlink:show="new" xlink:title="#xsd_CodeListValue" xlink:type="simple"><rect fill="#F1F1F1" height="26.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="253.6455" x="492.5" y="143.4611"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="247.6455" x="495.5" y="161.4562">CodeListValue : CodeListValueType</text></a></g><!--class schema_UnitType_1_4--><g class="entity" data-qualified-name="schema_UnitType_1_4" data-source-line="25" id="ent0013"><a href="#xsd_RootUnits" target="_top" title="#xsd_RootUnits" xlink:actuate="onRequest" xlink:href="#xsd_RootUnits" xlink:show="new" xlink:title="#xsd_RootUnits" xlink:type="simple"><rect fill="#F1F1F1" height="26.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="190.2354" x="524.5" y="187.4611"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="184.2354" x="527.5" y="205.4562">RootUnits : RootUnitsType</text></a></g><!--class schema_UnitType_1_5--><g class="entity" data-qualified-name="schema_UnitType_1_5" data-source-line="27" id="ent0015"><a href="#xsd_Conversions" target="_top" title="#xsd_Conversions" xlink:actuate="onRequest" xlink:href="#xsd_Conversions" xlink:show="new" xlink:title="#xsd_Conversions" xlink:type="simple"><rect fill="#F1F1F1" height="26.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="225.5225" x="506.5" y="231.4611"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="219.5225" x="509.5" y="249.4562">Conversions : ConversionsType</text></a></g><!--class schema_UnitType_1_6--><g class="entity" data-qualified-name="schema_UnitType_1_6" data-source-line="29" id="ent0017"><a href="#xsd_QuantityReference" target="_top" title="#xsd_QuantityReference" xlink:actuate="onRequest" xlink:href="#xsd_QuantityReference" xlink:show="new" xlink:title="#xsd_QuantityReference" xlink:type="simple"><rect fill="#F1F1F1" height="26.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="257.3232" x="491" y="275.4611"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="251.3232" x="494" y="293.4562">QuantityReference : ReferenceType</text></a></g><!--class schema_UnitType_1_7--><g class="entity" data-qualified-name="schema_UnitType_1_7" data-source-line="31" id="ent0019"><a href="#xsd_UnitVersionHistory" target="_top" title="#xsd_UnitVersionHistory" xlink:actuate="onRequest" xlink:href="#xsd_UnitVersionHistory" xlink:show="new" xlink:title="#xsd_UnitVersionHistory" xlink:type="simple"><rect fill="#F1F1F1" height="26.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="217.9688" x="510.5" y="319.4611"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="211.9688" x="513.5" y="337.4562">UnitVersionHistory : NoteType</text></a></g><!--class schema_UnitType_1_8--><g class="entity" data-qualified-name="schema_UnitType_1_8" data-source-line="33" id="ent0021"><a href="#xsd_UnitDefinition" target="_top" title="#xsd_UnitDefinition" xlink:actuate="onRequest" xlink:href="#xsd_UnitDefinition" xlink:show="new" xlink:title="#xsd_UnitDefinition" xlink:type="simple"><rect fill="#F1F1F1" height="26.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="218.0371" x="510.5" y="363.4611"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="212.0371" x="513.5" y="381.4562">UnitDefinition : DefinitionType</text></a></g><!--class schema_UnitType_1_9--><g class="entity" data-qualified-name="schema_UnitType_1_9" data-source-line="35" id="ent0023"><a href="#xsd_UnitHistory" target="_top" title="#xsd_UnitHistory" xlink:actuate="onRequest" xlink:href="#xsd_UnitHistory" xlink:show="new" xlink:title="#xsd_UnitHistory" xlink:type="simple"><rect fill="#F1F1F1" height="26.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="182.0664" x="528.5" y="407.4611"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="176.0664" x="531.5" y="425.4562">UnitHistory : HistoryType</text></a></g><!--class schema_UnitType_1_10--><g class="entity" data-qualified-name="schema_UnitType_1_10" data-source-line="37" id="ent0025"><a href="#xsd_UnitRemark" target="_top" title="#xsd_UnitRemark" xlink:actuate="onRequest" xlink:href="#xsd_UnitRemark" xlink:show="new" xlink:title="#xsd_UnitRemark" xlink:type="simple"><rect fill="#F1F1F1" height="26.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="191.2949" x="524" y="451.4611"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="185.2949" x="527" y="469.4562">UnitRemark : RemarkType</text></a></g><g class="link" data-entity-1="ent0002" data-entity-2="ent0003" data-link-type="dependency" id="lnk4"><polygon fill="#181818" points="318.7283,244.4611,309.7283,240.4611,313.7283,244.4611,309.7283,248.4611,318.7283,244.4611" style="stroke:#181818;stroke-width:1;"/><path d="M267.1453,244.4611 C285.9807,244.4611 297.9624,244.4611 312.7283,244.4611" fill="none" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="12.4033" x="287" y="239.528">1.</text></g><g class="link" data-entity-1="ent0003" data-entity-2="ent0005" data-link-type="dependency" id="lnk6"><polygon fill="#181818" points="525.9883,12.6966,516.9838,8.7068,520.9883,12.7022,516.9929,16.7068,525.9883,12.6966" style="stroke:#181818;stroke-width:1;"/><path d="M359.1852,231.3775 C364.636,191.2235 383.1284,70.9857 412,46.4611 C442.9818,20.1441 480.7295,12.748 519.9883,12.7034" fill="none" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="49.7148" x="418" y="13.528">1. [0..*]</text></g><g class="link" data-entity-1="ent0003" data-entity-2="ent0007" data-link-type="dependency" id="lnk8"><polygon fill="#181818" points="536.7861,61.2113,527.7282,57.3442,531.7867,61.2849,527.846,65.3433,536.7861,61.2113" style="stroke:#181818;stroke-width:1;"/><path d="M358.0947,231.3355 C360.1818,202.1144 369.5909,131.5906 412,97.4611 C446.5495,69.6568 489.3185,61.9101 530.7868,61.2996" fill="none" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="49.7148" x="418" y="64.528">2. [1..*]</text></g><g class="link" data-entity-1="ent0003" data-entity-2="ent0009" data-link-type="dependency" id="lnk10"><polygon fill="#181818" points="525.7469,109.9772,516.4492,106.7288,520.7638,110.3873,517.1053,114.7018,525.7469,109.9772" style="stroke:#181818;stroke-width:1;"/><path d="M360.7034,231.3623 C366.7432,209.6648 381.9915,166.5696 412,145.4611 C444.7387,122.4322 481.5987,113.6102 519.7671,110.4693" fill="none" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="49.7148" x="418" y="115.528">3. [0..*]</text></g><g class="link" data-entity-1="ent0003" data-entity-2="ent0011" data-link-type="dependency" id="lnk12"><polygon fill="#181818" points="492.4722,163.7255,482.8983,161.4147,487.5542,164.6275,484.3415,169.2835,492.4722,163.7255" style="stroke:#181818;stroke-width:1;"/><path d="M366.6717,231.2022 C376.3599,218.3116 392.8653,199.2826 412,189.4611 C436.6282,176.8199 459.0035,169.8639 486.5706,164.8079" fill="none" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="49.7148" x="418" y="163.528">4. [0..*]</text></g><g class="link" data-entity-1="ent0003" data-entity-2="ent0013" data-link-type="dependency" id="lnk14"><polygon fill="#181818" points="524.2499,204.9716,514.933,201.7787,519.2693,205.4114,515.6366,209.7477,524.2499,204.9716" style="stroke:#181818;stroke-width:1;"/><path d="M385.571,231.4003 C393.9182,227.9929 403.1897,224.663 412,222.4611 C448.1146,213.4354 482.7117,208.6393 518.2732,205.4994" fill="none" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="51.4858" x="417" y="207.528">5. [0..1]</text></g><g class="link" data-entity-1="ent0003" data-entity-2="ent0015" data-link-type="dependency" id="lnk16"><polygon fill="#181818" points="506.2441,244.4611,497.2441,240.4611,501.2441,244.4611,497.2441,248.4611,506.2441,244.4611" style="stroke:#181818;stroke-width:1;"/><path d="M394.0658,244.4611 C423.3172,244.4611 459.9634,244.4611 500.2441,244.4611" fill="none" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="51.4858" x="417" y="239.528">6. [0..1]</text></g><g class="link" data-entity-1="ent0003" data-entity-2="ent0017" data-link-type="dependency" id="lnk18"><polygon fill="#181818" points="490.7725,281.4718,482.3043,276.4428,485.807,280.8848,481.3651,284.3875,490.7725,281.4718" style="stroke:#181818;stroke-width:1;"/><path d="M384.1161,257.5622 C392.8149,261.3333 402.6473,265.0695 412,267.4611 C437.1368,273.8888 458.5102,277.6576 484.814,280.7674" fill="none" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="49.7148" x="418" y="262.528">7. [0..*]</text></g><g class="link" data-entity-1="ent0003" data-entity-2="ent0019" data-link-type="dependency" id="lnk20"><polygon fill="#181818" points="510.358,329.6396,501.9538,324.5045,505.4004,328.9902,500.9147,332.4367,510.358,329.6396" style="stroke:#181818;stroke-width:1;"/><path d="M366.1838,257.7426 C375.7157,271.1367 392.3102,291.2501 412,301.4611 C441.9295,316.9824 471.4166,324.5383 504.4089,328.8603" fill="none" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="49.7148" x="418" y="296.528">8. [0..*]</text></g><g class="link" data-entity-1="ent0003" data-entity-2="ent0021" data-link-type="dependency" id="lnk22"><polygon fill="#181818" points="510.4419,378.6862,501.9571,373.6854,505.4745,378.1157,501.0443,381.6332,510.4419,378.6862" style="stroke:#181818;stroke-width:1;"/><path d="M360.5057,257.4957 C366.3254,279.5507 381.3508,323.9261 412,345.4611 C440.4018,365.4171 470.4849,374.0971 504.4811,378.0016" fill="none" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="49.7148" x="418" y="340.528">9. [0..*]</text></g><g class="link" data-entity-1="ent0003" data-entity-2="ent0023" data-link-type="dependency" id="lnk24"><polygon fill="#181818" points="528.3507,429.0662,519.4444,424.8618,523.352,428.9521,519.2617,432.8597,528.3507,429.0662" style="stroke:#181818;stroke-width:1;"/><path d="M357.9661,257.8906 C359.7899,287.7817 368.6949,359.8859 412,394.4611 C444.2615,420.219 482.858,428.0275 522.3523,428.9293" fill="none" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="57.9858" x="414" y="389.528">10. [0..*]</text></g><g class="link" data-entity-1="ent0003" data-entity-2="ent0025" data-link-type="dependency" id="lnk26"><polygon fill="#181818" points="536.9087,477.4619,527.727,473.8988,531.9145,477.7021,528.1112,481.8895,536.9087,477.4619" style="stroke:#181818;stroke-width:1;"/><path d="M359.1013,257.472 C364.368,297.9913 382.5214,420.6877 412,445.4611 C445.9699,474.0089 489.102,479.7604 530.9157,477.7501" fill="none" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="57.9858" x="414" y="440.528">11. [0..*]</text></g><?plantuml-src bPJBRjim44Nt_0ectHM8OVbjBu84xQ81WWALusmCmQ2biKr495MHQgCM-VSE9UjXMHKWRpIaxkYU6PDp4yNiA1WDMQeIGxK0moGZD-tR8HmCDOh6_7WoDE0n-8OhIKuy00eYCYaL8FOGwtpBvx4V0j5lN4OiPeBw8TFwqPYOxnC3JxwIQ_u7wB0cqISMgDzS7kc1XHf8ba9lDvARbpI2MyeqrVu91DiLUtIx_VIkWtBjUVGlUNWNuOe7TEjuvC5m0Ml3H4HN59MhW1bumJsw7Gu6LX3WGcgiPUC-dpLCfXltgL2G3q8AbZNyJ43whImxXpJfxkxkhrLeuWpgnQrJRK3WTXxyhzQTinlMwytLdaUk7LfzCTiwPsLzzke0tndsOqNjeyFSgPjRRGzhM_G8hPsczJeLUnLcjTkWmtvqMRjLzm0OeSMuMViN5S0pr-QLXObjmCMw0sLSe5mazA0PezcaIUCgPQn6uyioxg2O51Ibk0V114scW-A8b_sGl_06urtGMJVALGV5jEf5AU_1CKMZMPFZHyAauIPruG2nlMws8qNSmJ8hMAu-q8DeXcRpjblocbVt3Vzl5QVslYZJHJEl7zZBv1uuSpHQjE5yXGF71tuK5TMY0sXH1weoUi0iq6hP1bCrvHnrO2ph6FqRihHJRDW6uQ8oVilEYWswHjd58CtbVQPPDachKNh-_GC0?></g></svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="20" height="20">
3
+ <rect width="10" height="10"><animate attributeName="x" from="0" to="5" dur="1s"/></rect>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="20" height="20">
3
+ <rect width="10" height="10" style="fill: {red"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="20" height="20">
3
+ <rect width="10" height="10" bogus="1"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="20" height="20">
3
+ <rect width="10" height="10"><g/></rect>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="20" height="20">
3
+ <rect width="10" height="10" onclick="go()"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="20" height="20">
3
+ <rect width="10" height="10">
4
+ </svg>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="20" height="20">
3
+ <script type="text/ecmascript">var x = 1;</script>
4
+ <rect width="10" height="10"/>
5
+ </svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://example.com/not-svg" version="1.1" width="20" height="20">
3
+ <rect width="10" height="10"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="20" height="20">
3
+ <rect width="10" height="10"/>
4
+ </svg>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <?xml-stylesheet type="text/css"?>
3
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="20" height="20">
4
+ <rect width="10" height="10"/>
5
+ </svg>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="20" height="20">
3
+ <style type="text/css">rect.a { fill: red; stroke: #000 } @media print { rect { stroke-width: 2 } }</style>
4
+ <rect class="a" width="10" height="10" style="fill: blue; opacity: 0.5"/>
5
+ </svg>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:ex="http://example.com/ns" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="20" height="20" ex:note="kept out">
3
+ <metadata><ex:info><ex:item>not SVG</ex:item></ex:info></metadata>
4
+ <ex:extension><ex:deep/></ex:extension>
5
+ <a xlink:href="#r"><rect id="r" width="10" height="10"/></a>
6
+ </svg>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="20" height="20" viewBox="0 0 20 20">
4
+ <rect x="2" y="2" width="16" height="16" fill="#336699"/>
5
+ </svg>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <?xml-stylesheet href="theme.css" type="text/css" title="Theme" media="screen" alternate="no"?>
3
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="20" height="20">
4
+ <rect width="10" height="10"/>
5
+ </svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="2.0" viewBox="0 0 10 10">
3
+ <rect width="1" height="1"><animate attributeName="x" from="0" to="1" dur="1s"/></rect>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="2.0" viewBox="0 0 10 10">
3
+ <style>@import url("other.css"); rect { fill: red }</style><rect width="1" height="1"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="2.0" viewBox="0 0 10 10">
3
+ <rect width="1" height="1" style="fill:url(other.svg#g)"/>
4
+ </svg>