svg_conform 0.1.0 → 0.1.2

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +4 -1
  3. data/.github/workflows/release.yml +6 -2
  4. data/.rubocop_todo.yml +273 -10
  5. data/Gemfile +1 -0
  6. data/README.adoc +54 -37
  7. data/config/profiles/metanorma.yml +4 -4
  8. data/docs/remediation.adoc +541 -542
  9. data/docs/requirements.adoc +800 -357
  10. data/examples/readme_usage.rb +67 -0
  11. data/examples/requirements_demo.rb +4 -4
  12. data/lib/svg_conform/document.rb +7 -1
  13. data/lib/svg_conform/element_proxy.rb +101 -0
  14. data/lib/svg_conform/fast_document_analyzer.rb +82 -0
  15. data/lib/svg_conform/node_index_builder.rb +47 -0
  16. data/lib/svg_conform/remediations/no_external_css_remediation.rb +4 -4
  17. data/lib/svg_conform/requirements/allowed_elements_requirement.rb +202 -0
  18. data/lib/svg_conform/requirements/base_requirement.rb +27 -0
  19. data/lib/svg_conform/requirements/color_restrictions_requirement.rb +53 -0
  20. data/lib/svg_conform/requirements/font_family_requirement.rb +18 -0
  21. data/lib/svg_conform/requirements/forbidden_content_requirement.rb +26 -0
  22. data/lib/svg_conform/requirements/id_reference_requirement.rb +96 -0
  23. data/lib/svg_conform/requirements/invalid_id_references_requirement.rb +91 -0
  24. data/lib/svg_conform/requirements/link_validation_requirement.rb +30 -0
  25. data/lib/svg_conform/requirements/namespace_attributes_requirement.rb +59 -0
  26. data/lib/svg_conform/requirements/namespace_requirement.rb +74 -0
  27. data/lib/svg_conform/requirements/no_external_css_requirement.rb +74 -0
  28. data/lib/svg_conform/requirements/no_external_fonts_requirement.rb +58 -0
  29. data/lib/svg_conform/requirements/no_external_images_requirement.rb +40 -0
  30. data/lib/svg_conform/requirements/style_requirement.rb +12 -0
  31. data/lib/svg_conform/requirements/viewbox_required_requirement.rb +72 -0
  32. data/lib/svg_conform/sax_document.rb +46 -0
  33. data/lib/svg_conform/sax_validation_handler.rb +158 -0
  34. data/lib/svg_conform/validation_context.rb +84 -2
  35. data/lib/svg_conform/validator.rb +74 -6
  36. data/lib/svg_conform/version.rb +1 -1
  37. data/lib/svg_conform.rb +1 -0
  38. data/spec/fixtures/namespace/repair/basic_violations.svg +3 -3
  39. data/spec/fixtures/namespace_attributes/repair/basic_violations.svg +2 -2
  40. data/spec/fixtures/no_external_css/repair/basic_violations.svg +2 -2
  41. data/spec/fixtures/style_promotion/repair/basic_test.svg +2 -2
  42. data/svg_conform.gemspec +1 -1
  43. metadata +12 -6
@@ -83,6 +83,78 @@ module SvgConform
83
83
  )
84
84
  end
85
85
 
86
+ def validate_sax_element(element, context)
87
+ # Only check the root SVG element
88
+ return unless element.name == "svg" && element.parent.nil?
89
+
90
+ viewbox = element.raw_attributes["viewBox"]
91
+
92
+ if viewbox.nil? || viewbox.empty?
93
+ context.add_error(
94
+ requirement_id: id,
95
+ message: "SVG root element must have a viewBox attribute",
96
+ node: element,
97
+ severity: :error,
98
+ data: { missing_attribute: "viewBox" }
99
+ )
100
+
101
+ # Add informational message about calculated viewBox if width/height are present
102
+ width = element.raw_attributes["width"]
103
+ height = element.raw_attributes["height"]
104
+
105
+ if width && height && valid_number?(width) && valid_number?(height)
106
+ calculated_viewbox = "0 0 #{width.to_f} #{height.to_f}"
107
+ context.add_error(
108
+ requirement_id: id,
109
+ message: "Trying to put in the attribute with value '#{calculated_viewbox}'",
110
+ node: element,
111
+ severity: :error,
112
+ data: {
113
+ calculated_viewbox: calculated_viewbox,
114
+ source_width: width,
115
+ source_height: height
116
+ }
117
+ )
118
+ end
119
+ return
120
+ end
121
+
122
+ # Validate viewBox format (should be "min-x min-y width height")
123
+ normalized_viewbox = viewbox.strip.gsub(/[(),]/, " ").squeeze(" ")
124
+ parts = normalized_viewbox.strip.split(/\s+/)
125
+ unless parts.length == 4 && parts.all? { |part| valid_number?(part) }
126
+ context.add_error(
127
+ requirement_id: id,
128
+ message: "viewBox attribute must contain four numeric values (min-x min-y width height)",
129
+ node: element,
130
+ severity: :error,
131
+ data: {
132
+ viewbox_value: viewbox,
133
+ parsed_parts: parts
134
+ }
135
+ )
136
+ return
137
+ end
138
+
139
+ # Check that width and height are positive
140
+ width = parts[2].to_f
141
+ height = parts[3].to_f
142
+
143
+ return unless width <= 0 || height <= 0
144
+
145
+ context.add_error(
146
+ requirement_id: id,
147
+ message: "viewBox width and height must be positive values",
148
+ node: element,
149
+ severity: :error,
150
+ data: {
151
+ viewbox_value: viewbox,
152
+ width: width,
153
+ height: height
154
+ }
155
+ )
156
+ end
157
+
86
158
  private
87
159
 
88
160
  def valid_number?(str)
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+ require_relative "sax_validation_handler"
5
+
6
+ module SvgConform
7
+ # SAX-based document for streaming validation
8
+ # Provides high-performance validation for large SVG files
9
+ # without loading entire DOM tree into memory
10
+ class SaxDocument
11
+ attr_reader :file_path, :content
12
+
13
+ def self.from_file(file_path)
14
+ new(File.read(file_path), file_path)
15
+ end
16
+
17
+ def self.from_content(content)
18
+ new(content, nil)
19
+ end
20
+
21
+ def initialize(content, file_path = nil)
22
+ @content = content
23
+ @file_path = file_path
24
+ end
25
+
26
+ # Validate using SAX streaming parser
27
+ def validate_with_profile(profile)
28
+ handler = SaxValidationHandler.new(profile)
29
+ parser = Nokogiri::XML::SAX::Parser.new(handler)
30
+
31
+ begin
32
+ parser.parse(@content)
33
+ rescue StandardError => e
34
+ # Handle parse errors
35
+ handler.add_parse_error(e)
36
+ end
37
+
38
+ handler.result
39
+ end
40
+
41
+ # For compatibility - convert to DOM when needed
42
+ def to_dom
43
+ Document.from_content(@content)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+ require_relative "element_proxy"
5
+ require_relative "validation_context"
6
+ require_relative "validation_result"
7
+
8
+ module SvgConform
9
+ # SAX event handler for streaming SVG validation
10
+ # Processes XML events and dispatches to requirements
11
+ class SaxValidationHandler < Nokogiri::XML::SAX::Document
12
+ attr_reader :result, :context
13
+
14
+ def initialize(profile)
15
+ @profile = profile
16
+ @element_stack = [] # Track parent-child hierarchy
17
+ @path_stack = [] # Current element path
18
+ @position_counters = [] # Stack of sibling counters per level
19
+ @parse_errors = []
20
+ @result = nil # Will be set in end_document
21
+
22
+ # Create validation context (without document reference for SAX)
23
+ @context = create_sax_context
24
+
25
+ # Classify requirements into immediate vs deferred
26
+ @immediate_requirements = []
27
+ @deferred_requirements = []
28
+ classify_requirements
29
+ end
30
+
31
+ # SAX Event: Document start
32
+ def start_document
33
+ # Initialize root level counters
34
+ @position_counters.push({})
35
+ end
36
+
37
+ # SAX Event: Element start tag
38
+ def start_element(name, attributes = [])
39
+ attrs = Hash[attributes]
40
+
41
+ # Calculate position among siblings at current level
42
+ current_counters = @position_counters.last || {}
43
+ current_counters[name] ||= 0
44
+ current_counters[name] += 1
45
+ position = current_counters[name]
46
+
47
+ # Build element proxy
48
+ element = ElementProxy.new(
49
+ name: name,
50
+ attributes: attrs,
51
+ position: position,
52
+ path: @path_stack.dup,
53
+ parent: @element_stack.last
54
+ )
55
+
56
+ # Push to stacks
57
+ @element_stack.push(element)
58
+ @path_stack.push("#{name}[#{position}]")
59
+ @position_counters.push({}) # New level for this element's children
60
+
61
+ # Validate with immediate requirements
62
+ @immediate_requirements.each do |req|
63
+ req.validate_sax_element(element, @context)
64
+ end
65
+
66
+ # Deferred requirements may need to collect data
67
+ @deferred_requirements.each do |req|
68
+ req.collect_sax_data(element, @context) if req.respond_to?(:collect_sax_data)
69
+ end
70
+ end
71
+
72
+ # SAX Event: Element end tag
73
+ def end_element(name)
74
+ @element_stack.pop
75
+ @path_stack.pop
76
+ @position_counters.pop
77
+ end
78
+
79
+ # SAX Event: Text content
80
+ def characters(string)
81
+ return if @element_stack.empty?
82
+ @element_stack.last.text_content << string
83
+ end
84
+
85
+ # SAX Event: Document complete
86
+ def end_document
87
+ # Run deferred validation
88
+ @deferred_requirements.each do |req|
89
+ req.validate_sax_complete(@context)
90
+ end
91
+
92
+ # Create result
93
+ @result = ValidationResult.new(nil, @profile, @context)
94
+ end
95
+
96
+ # SAX Event: Parse error
97
+ def error(error_message)
98
+ @parse_errors << error_message
99
+ end
100
+
101
+ # SAX Event: Warning
102
+ def warning(warning_message)
103
+ # Can log warnings if needed
104
+ end
105
+
106
+ # Handle parse errors
107
+ def add_parse_error(error)
108
+ @context.add_error(
109
+ node: nil,
110
+ message: "Parse error: #{error.message}",
111
+ requirement_id: "parse_error",
112
+ severity: :error
113
+ )
114
+ end
115
+
116
+ # Get result (will be nil until end_document called)
117
+ def result
118
+ @result || create_incomplete_result
119
+ end
120
+
121
+ private
122
+
123
+ def create_incomplete_result
124
+ # Return result even if parsing incomplete
125
+ ValidationResult.new(nil, @profile, @context)
126
+ end
127
+
128
+ # Create a SAX-compatible validation context
129
+ def create_sax_context
130
+ # Create context without triggering DOM operations
131
+ context = ValidationContext.allocate
132
+ context.instance_variable_set(:@document, nil)
133
+ context.instance_variable_set(:@profile, @profile)
134
+ context.instance_variable_set(:@errors, [])
135
+ context.instance_variable_set(:@warnings, [])
136
+ context.instance_variable_set(:@validity_errors, [])
137
+ context.instance_variable_set(:@infos, [])
138
+ context.instance_variable_set(:@data, {})
139
+ context.instance_variable_set(:@structurally_invalid_node_ids, Set.new)
140
+ context.instance_variable_set(:@node_id_cache, {})
141
+ context.instance_variable_set(:@cache_populated, true) # Skip population for SAX
142
+ context
143
+ end
144
+
145
+ # Classify requirements based on validation needs
146
+ def classify_requirements
147
+ return unless @profile.requirements
148
+
149
+ @profile.requirements.each do |req|
150
+ if req.respond_to?(:needs_deferred_validation?) && req.needs_deferred_validation?
151
+ @deferred_requirements << req
152
+ else
153
+ @immediate_requirements << req
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -17,6 +17,8 @@ module SvgConform
17
17
  @infos = []
18
18
  @data = {}
19
19
  @structurally_invalid_node_ids = Set.new
20
+ @node_id_cache = {}
21
+ @cache_populated = false
20
22
  end
21
23
 
22
24
  # Mark a node as structurally invalid (e.g., invalid parent-child relationship)
@@ -24,6 +26,8 @@ module SvgConform
24
26
  # Also marks all descendants as invalid since they'll be removed with the parent
25
27
  def mark_node_structurally_invalid(node)
26
28
  node_id = generate_node_id(node)
29
+ return if node_id.nil? # Safety check
30
+
27
31
  @structurally_invalid_node_ids.add(node_id)
28
32
 
29
33
  # Mark all descendants as invalid too
@@ -36,6 +40,8 @@ module SvgConform
36
40
 
37
41
  node.children.each do |child|
38
42
  child_id = generate_node_id(child)
43
+ return if child_id.nil? # Safety check
44
+
39
45
  @structurally_invalid_node_ids.add(child_id)
40
46
  # Recursively mark descendants
41
47
  mark_descendants_invalid(child)
@@ -45,6 +51,8 @@ module SvgConform
45
51
  # Check if a node is structurally invalid
46
52
  def node_structurally_invalid?(node)
47
53
  node_id = generate_node_id(node)
54
+ return false if node_id.nil? # Safety check
55
+
48
56
  @structurally_invalid_node_ids.include?(node_id)
49
57
  end
50
58
 
@@ -120,16 +128,90 @@ requirement_id: nil, severity: nil, fix: nil, data: {})
120
128
 
121
129
  # Generate a unique identifier for a node based on its path
122
130
  # Builds a stable path by walking up the parent chain
131
+ # OPTIMIZED: Lazy cache population - populate entire cache on first call
123
132
  def generate_node_id(node)
124
133
  return nil unless node.respond_to?(:name)
125
134
 
126
- # Build path by walking up parent chain
135
+ # Populate cache for ALL nodes on first access
136
+ unless @cache_populated
137
+ populate_node_id_cache
138
+ @cache_populated = true
139
+ end
140
+
141
+ # Try cache lookup first
142
+ cached_id = @node_id_cache[node]
143
+ return cached_id if cached_id
144
+
145
+ # Fall back to building path if node not in cache
146
+ # (happens when different traversals create different wrapper objects)
147
+ build_node_path(node)
148
+ end
149
+
150
+ private
151
+
152
+ # Populate cache for all nodes using document.traverse with parent tracking
153
+ def populate_node_id_cache
154
+ parent_stack = []
155
+ counter_stack = [{}] # Stack of {element_name => count} hashes
156
+
157
+ @document.traverse do |node|
158
+ next unless node.respond_to?(:name) && node.name
159
+
160
+ # Detect parent changes by checking node.parent
161
+ current_parent = node.respond_to?(:parent) ? node.parent : nil
162
+
163
+ # Adjust stack based on actual parent
164
+ while parent_stack.size > 0 && !parent_stack.last.equal?(current_parent)
165
+ parent_stack.pop
166
+ counter_stack.pop
167
+ end
168
+
169
+ # If we have a new parent level, push it
170
+ if current_parent && (parent_stack.empty? || !parent_stack.last.equal?(current_parent))
171
+ parent_stack.push(current_parent)
172
+ counter_stack.push({})
173
+ end
174
+
175
+ # Increment counter at current level
176
+ current_counters = counter_stack.last || {}
177
+ current_counters[node.name] ||= 0
178
+ current_counters[node.name] += 1
179
+
180
+ # Build path using original backward logic (for correctness)
181
+ @node_id_cache[node] = build_node_path(node)
182
+ end
183
+ end
184
+
185
+ # Traverse tree, building paths with forward position counters
186
+ def traverse_with_forward_counting(node, path_parts, sibling_counters)
187
+ return unless node.respond_to?(:name) && node.name
188
+
189
+ # Increment counter for this node name at current level
190
+ sibling_counters[node.name] ||= 0
191
+ sibling_counters[node.name] += 1
192
+ position = sibling_counters[node.name]
193
+
194
+ # Build and cache path
195
+ current_path = path_parts + ["#{node.name}[#{position}]"]
196
+ @node_id_cache[node] = "/#{current_path.join('/')}"
197
+
198
+ # Traverse children with fresh counters
199
+ if node.respond_to?(:children)
200
+ child_counters = {}
201
+ node.children.each do |child|
202
+ traverse_with_forward_counting(child, current_path, child_counters)
203
+ end
204
+ end
205
+ end
206
+
207
+ # Build path-based ID for a node (original logic, unchanged)
208
+ def build_node_path(node)
127
209
  path_parts = []
128
210
  current = node
129
211
 
130
212
  while current
131
213
  if current.respond_to?(:name) && current.name
132
- # Count previous siblings of the same type for position
214
+ # Count previous siblings of the same type for position (ORIGINAL LOGIC)
133
215
  position = 1
134
216
  if current.respond_to?(:previous_sibling)
135
217
  sibling = current.previous_sibling
@@ -9,6 +9,7 @@ module SvgConform
9
9
  @options = {
10
10
  fix: false,
11
11
  strict: false,
12
+ mode: :auto # :auto, :dom, or :sax
12
13
  }.merge(options)
13
14
  end
14
15
 
@@ -19,17 +20,31 @@ module SvgConform
19
20
  "File not found: #{file_path}"
20
21
  end
21
22
 
22
- document = Document.from_file(file_path)
23
- validate_document(document, profile: profile, **options)
23
+ merged_options = @options.merge(options)
24
+ mode = determine_mode(file_path, merged_options[:mode])
25
+
26
+ case mode
27
+ when :sax
28
+ validate_file_sax(file_path, profile: profile, **merged_options)
29
+ when :dom
30
+ validate_file_dom(file_path, profile: profile, **merged_options)
31
+ end
24
32
  end
25
33
 
26
34
  # Validate SVG content string
27
35
  def validate(svg_content, profile: :svg_1_2_rfc, **options)
28
- document = Document.from_content(svg_content)
29
- validate_document(document, profile: profile, **options)
36
+ merged_options = @options.merge(options)
37
+ mode = merged_options[:mode] == :sax ? :sax : :dom
38
+
39
+ case mode
40
+ when :sax
41
+ validate_content_sax(svg_content, profile: profile, **merged_options)
42
+ when :dom
43
+ validate_content_dom(svg_content, profile: profile, **merged_options)
44
+ end
30
45
  end
31
46
 
32
- # Validate a Document object
47
+ # Validate a Document object (DOM only)
33
48
  def validate_document(document, profile: :svg_1_2_rfc, **options)
34
49
  merged_options = @options.merge(options)
35
50
  profile_obj = resolve_profile(profile)
@@ -64,6 +79,59 @@ module SvgConform
64
79
 
65
80
  private
66
81
 
82
+ def determine_mode(file_path, requested_mode)
83
+ case requested_mode
84
+ when :sax
85
+ :sax
86
+ when :dom
87
+ :dom
88
+ when :auto
89
+ # Use SAX for files larger than 1MB
90
+ file_size = File.size(file_path)
91
+ file_size > 1_000_000 ? :sax : :dom
92
+ else
93
+ :dom
94
+ end
95
+ end
96
+
97
+ def validate_file_sax(file_path, profile:, **options)
98
+ profile_obj = resolve_profile(profile)
99
+ sax_doc = SvgConform::SaxDocument.from_file(file_path)
100
+ result = sax_doc.validate_with_profile(profile_obj)
101
+
102
+ # If fixing is requested, convert to DOM and apply fixes
103
+ if options[:fix] && result.has_errors?
104
+ dom_doc = SvgConform::Document.from_file(file_path)
105
+ result = validate_document(dom_doc, profile: profile_obj, **options)
106
+ end
107
+
108
+ result
109
+ end
110
+
111
+ def validate_file_dom(file_path, profile:, **options)
112
+ document = SvgConform::Document.from_file(file_path)
113
+ validate_document(document, profile: profile, **options)
114
+ end
115
+
116
+ def validate_content_sax(content, profile:, **options)
117
+ profile_obj = resolve_profile(profile)
118
+ sax_doc = SvgConform::SaxDocument.from_content(content)
119
+ result = sax_doc.validate_with_profile(profile_obj)
120
+
121
+ # If fixing is requested, convert to DOM and apply fixes
122
+ if options[:fix] && result.has_errors?
123
+ dom_doc = SvgConform::Document.from_content(content)
124
+ result = validate_document(dom_doc, profile: profile_obj, **options)
125
+ end
126
+
127
+ result
128
+ end
129
+
130
+ def validate_content_dom(content, profile:, **options)
131
+ document = SvgConform::Document.from_content(content)
132
+ validate_document(document, profile: profile, **options)
133
+ end
134
+
67
135
  def resolve_profile(profile)
68
136
  case profile
69
137
  when Symbol, String
@@ -84,7 +152,7 @@ module SvgConform
84
152
  warnings: [],
85
153
  file_path: file_path,
86
154
  error?: true,
87
- to_s: -> { "Error processing #{file_path}: #{error.message}" },
155
+ to_s: -> { "Error processing #{file_path}: #{error.message}" }
88
156
  )
89
157
  end
90
158
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SvgConform
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
data/lib/svg_conform.rb CHANGED
@@ -11,6 +11,7 @@ end
11
11
 
12
12
  require_relative "svg_conform/version"
13
13
  require_relative "svg_conform/document"
14
+ require_relative "svg_conform/sax_document"
14
15
  require_relative "svg_conform/validation_context"
15
16
  require_relative "svg_conform/validation_result"
16
17
  require_relative "svg_conform/profiles"
@@ -3,15 +3,15 @@
3
3
  version="1.2" baseProfile="tiny" width="100" height="100" viewBox="0 0 100 100">
4
4
  <title>Namespace Violations</title>
5
5
 
6
- <!-- Invalid namespace elements removed -->
6
+ <!-- Invalid namespace elements -->
7
7
 
8
- <!-- Invalid namespace attributes removed -->
8
+ <!-- Invalid namespace attributes on valid elements -->
9
9
  <rect x="10" y="10" width="20" height="20"/>
10
10
  <g>
11
11
  <circle cx="50" cy="50" r="15"/>
12
12
  </g>
13
13
 
14
- <!-- Valid namespaces remain -->
14
+ <!-- Valid namespaces -->
15
15
  <use xlink:href="#test" x="10" y="10"/>
16
16
  <text xml:space="preserve" x="10" y="90">Valid</text>
17
17
  </svg>
@@ -3,13 +3,13 @@
3
3
  version="1.2" baseProfile="tiny" width="100" height="100" viewBox="0 0 100 100">
4
4
  <title>Namespace Attributes Test</title>
5
5
 
6
- <!-- Invalid namespace attributes removed -->
6
+ <!-- Invalid namespace attributes on valid elements -->
7
7
  <rect x="10" y="10" width="20" height="20"/>
8
8
  <g>
9
9
  <circle cx="50" cy="50" r="15"/>
10
10
  </g>
11
11
 
12
- <!-- Valid namespace attributes remain -->
12
+ <!-- Valid namespace attributes -->
13
13
  <use xlink:href="#test" x="10" y="10"/>
14
14
  <text xml:space="preserve" x="10" y="90">Valid</text>
15
15
  </svg>
@@ -10,9 +10,9 @@
10
10
  </style>
11
11
  </defs>
12
12
 
13
- <!-- External stylesheet link removed -->
13
+ <!-- External stylesheet link -->
14
14
 
15
- <!-- Valid internal styles remain -->
15
+ <!-- Valid internal styles -->
16
16
  <rect x="10" y="10" width="30" height="30" class="internal"/>
17
17
  <circle cx="70" cy="25" r="15" style="fill: white; stroke: black"/>
18
18
  </svg>
@@ -4,12 +4,12 @@
4
4
  <title>Style Promotion</title>
5
5
  <desc>Testing style property promotion to attributes</desc>
6
6
 
7
- <!-- Style properties promoted to attributes -->
7
+ <!-- Style properties that should be promoted -->
8
8
  <rect x="10" y="10" width="30" height="20" fill="black" stroke="none"/>
9
9
  <circle cx="60" cy="20" r="10" fill="white" stroke="black" stroke-width="1"/>
10
10
  <text x="10" y="50" font-family="serif" font-size="12" fill="black">Text</text>
11
11
  <path d="M 10 60 L 40 70 L 10 70 Z" fill="none" stroke="black" stroke-width="2"/>
12
12
 
13
- <!-- Style promoted (overrides attribute) -->
13
+ <!-- Mixed style and attribute (style should win) -->
14
14
  <rect x="80" y="10" width="20" height="20" fill="black"/>
15
15
  </svg>
data/svg_conform.gemspec CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
13
13
  spec.email = ["open.source@ribose.com"]
14
14
 
15
15
  spec.summary = "SVG profile conformance checker for Ruby."
16
- spec.homepage = "https://github.com/metanorma/svg_conform"
16
+ spec.homepage = "https://github.com/claricle/svg_conform"
17
17
  spec.license = "BSD-2-Clause"
18
18
  spec.required_ruby_version = Gem::Requirement.new(">= 3.1.0")
19
19
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: svg_conform
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-10-12 00:00:00.000000000 Z
11
+ date: 2025-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model
@@ -97,6 +97,7 @@ files:
97
97
  - docs/remediation.adoc
98
98
  - docs/requirements.adoc
99
99
  - examples/demo.rb
100
+ - examples/readme_usage.rb
100
101
  - examples/requirements_demo.rb
101
102
  - exe/svg_conform
102
103
  - lib/svg_conform.rb
@@ -122,6 +123,7 @@ files:
122
123
  - lib/svg_conform/constants.rb
123
124
  - lib/svg_conform/css_color.rb
124
125
  - lib/svg_conform/document.rb
126
+ - lib/svg_conform/element_proxy.rb
125
127
  - lib/svg_conform/external_checkers.rb
126
128
  - lib/svg_conform/external_checkers/svgcheck.rb
127
129
  - lib/svg_conform/external_checkers/svgcheck/compatibility_engine.rb
@@ -130,7 +132,9 @@ files:
130
132
  - lib/svg_conform/external_checkers/svgcheck/report_comparator.rb
131
133
  - lib/svg_conform/external_checkers/svgcheck/report_generator.rb
132
134
  - lib/svg_conform/external_checkers/svgcheck/validation_pipeline.rb
135
+ - lib/svg_conform/fast_document_analyzer.rb
133
136
  - lib/svg_conform/fixer.rb
137
+ - lib/svg_conform/node_index_builder.rb
134
138
  - lib/svg_conform/profile.rb
135
139
  - lib/svg_conform/profiles.rb
136
140
  - lib/svg_conform/remediation_engine.rb
@@ -167,6 +171,8 @@ files:
167
171
  - lib/svg_conform/requirements/style_promotion_requirement.rb
168
172
  - lib/svg_conform/requirements/style_requirement.rb
169
173
  - lib/svg_conform/requirements/viewbox_required_requirement.rb
174
+ - lib/svg_conform/sax_document.rb
175
+ - lib/svg_conform/sax_validation_handler.rb
170
176
  - lib/svg_conform/semantic_comparator.rb
171
177
  - lib/svg_conform/validation_context.rb
172
178
  - lib/svg_conform/validation_result.rb
@@ -407,13 +413,13 @@ files:
407
413
  - spec/svg_conform_spec.rb
408
414
  - spec/svgcheck_compatibility_spec.rb
409
415
  - svg_conform.gemspec
410
- homepage: https://github.com/metanorma/svg_conform
416
+ homepage: https://github.com/claricle/svg_conform
411
417
  licenses:
412
418
  - BSD-2-Clause
413
419
  metadata:
414
- homepage_uri: https://github.com/metanorma/svg_conform
415
- source_code_uri: https://github.com/metanorma/svg_conform
416
- bug_tracker_uri: https://github.com/metanorma/svg_conform/issues
420
+ homepage_uri: https://github.com/claricle/svg_conform
421
+ source_code_uri: https://github.com/claricle/svg_conform
422
+ bug_tracker_uri: https://github.com/claricle/svg_conform/issues
417
423
  post_install_message:
418
424
  rdoc_options: []
419
425
  require_paths: