sevgi-standard 0.93.1 → 0.94.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 845a552c81846d5e0183f1ef2243ea0ddcf90a94a8bcbe64edab77df9e6ac877
4
- data.tar.gz: dd99636019944aaee3d6481c44fc67b48c834718a1f843afd22bfb5c1553ec78
3
+ metadata.gz: 032a1a8f609e2cebf4de52c844f69cdfc13504a438c08042aff5dde09ef0b331
4
+ data.tar.gz: 8aa466073442ec8062aa48468e7274e7e9d275558a8be154cf26fce32c121d0d
5
5
  SHA512:
6
- metadata.gz: d1d2d534bab867b6357bb7623f498d60a82965446c06c0553cc37a63529a8e783024ff65f10422cdd382dec9f19497f1c435965a4e72bfd3afcb2afb0e228afa
7
- data.tar.gz: aae602ac61b37d6c4de6549bb2763d02fd3a1c3de3157dc8f6bc831d1a93a0f357a125be55fb221d0acf3a7a9c2480fc3d8965e8f1b0b3dde5cded9fdb39f070
6
+ metadata.gz: 4756ece8ebbcf5d7d971d27e9711ba116652378f271d23bcc543e19a28eae94cdd47a51d90489b79233166a1f9e9adaf0e446f4a43d73d90a6423aad6074d3c6
7
+ data.tar.gz: bec85c8ec04964a3e44e3d780d4b22f61e87f8faef3fbebad5b95144dbe639a76a4d5373212203e123ce57d7abb441b785d904d2f6b526a4e2e46947e1e99334
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ # Changelog
2
+
3
+ Sevgi Standard follows the root Sevgi release notes:
4
+ https://github.com/roktas/sevgi/blob/main/CHANGELOG.md
data/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Sevgi Standard is distributed under the GNU General Public License v3.0 or later.
2
+
3
+ SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ Full project license: https://github.com/roktas/sevgi/blob/main/LICENSE
data/README.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Sevgi Standard
2
2
 
3
- Provides SVG element, attribute, and conformance data used by Sevgi validation.
3
+ SVG element, attribute, and conformance data used by Sevgi validation.
4
4
 
5
- See the root [README](../README.md) and the documentation site for usage.
5
+ ## Install
6
+
7
+ ```sh
8
+ gem install sevgi-standard
9
+ ```
10
+
11
+ ## Require
12
+
13
+ ```ruby
14
+ require "sevgi/standard"
15
+ ```
16
+
17
+ ## Example
18
+
19
+ ```ruby
20
+ Sevgi::Standard.element?(:rect)
21
+ Sevgi::Standard.attribute?(:width)
22
+ ```
23
+
24
+ ## Ruby compatibility
25
+
26
+ Requires Ruby 3.4.0 or newer. CI verifies Ruby 3.4 and the current development Ruby from `.ruby-version`.
27
+
28
+ ## Native prerequisites
29
+
30
+ None beyond Ruby and this gem's Ruby dependencies.
31
+
32
+ ## Links
33
+
34
+ - Documentation: https://sevgi.roktas.dev
35
+ - API documentation: https://www.rubydoc.info/gems/sevgi-standard
36
+ - Source: https://github.com/roktas/sevgi/tree/main/standard
37
+ - Changelog: https://github.com/roktas/sevgi/blob/main/CHANGELOG.md
@@ -14,11 +14,14 @@ module Sevgi
14
14
  attr_reader :spec
15
15
 
16
16
  # Builds a validator for one SVG element.
17
- # @param element [Symbol] SVG element name
17
+ # @param element [String, Symbol] SVG element name
18
18
  # @return [void]
19
+ # @raise [Sevgi::ArgumentError] when element is not a valid public name
19
20
  # @raise [Sevgi::InvalidElementsError] when the element is unknown
20
21
  # @raise [Sevgi::PanicError] when the element model is missing or unimplemented
21
22
  def initialize(element)
23
+ element = Name.normalize!(element, context: "element")
24
+
22
25
  InvalidElementsError.(element) unless (@spec = Specification[@element = element])
23
26
 
24
27
  PanicError.("No model specified: #{element}") unless spec[:model]
@@ -30,10 +33,11 @@ module Sevgi
30
33
  end
31
34
 
32
35
  # Validates one usage of the configured SVG element.
33
- # @param attributes [Array<Symbol>, nil] attribute names used by the element
36
+ # @param attributes [Array<String, Symbol>, nil] attribute names used by the element
34
37
  # @param cdata [String, nil] character data content
35
- # @param elements [Array<Symbol>, nil] child element names
38
+ # @param elements [Array<String, Symbol>, nil] child element names
36
39
  # @return [Boolean] true when the usage conforms
40
+ # @raise [Sevgi::ArgumentError] when any name is not a valid public name
37
41
  # @raise [Sevgi::ValidationError] when the usage violates the standard data
38
42
  def call(attributes: nil, cdata: nil, elements: nil)
39
43
  if attributes
@@ -50,18 +54,23 @@ module Sevgi
50
54
  @cache = {}
51
55
 
52
56
  # Validates one SVG element usage, using cached element validators.
53
- # @param element [Symbol] SVG element name
54
- # @param attributes [Array<Symbol>, nil] attribute names used by the element
57
+ # @param element [String, Symbol] SVG element name
58
+ # @param attributes [Array<String, Symbol>, nil] attribute names used by the element
55
59
  # @param cdata [String, nil] character data content
56
- # @param elements [Array<Symbol>, nil] child element names
60
+ # @param elements [Array<String, Symbol>, nil] child element names
57
61
  # @return [Boolean] true when the usage conforms or the element is ignored
62
+ # @raise [Sevgi::ArgumentError] when any name is not a valid public name
58
63
  # @raise [Sevgi::ValidationError] when the usage violates the standard data
59
64
  # @raise [Sevgi::PanicError] when the standard data refers to an invalid model
60
65
  def self.call(element, attributes: nil, cdata: nil, elements: nil)
66
+ element = Name.normalize!(element, context: "element")
67
+ attributes = Name.list!(attributes, context: "attribute")
68
+ elements = Name.list!(elements || [], context: "element")
69
+
61
70
  Element.ignore?(element) or
62
71
  (@cache[element] ||= new(element)).call(
63
72
  attributes: Attribute.concerns(attributes),
64
- elements: Element.concerns(elements || []),
73
+ elements: Element.concerns(elements),
65
74
  cdata:
66
75
  )
67
76
  end
@@ -483,7 +483,6 @@ module Sevgi
483
483
  Deprecated: %i[
484
484
  accent-height
485
485
  alphabetic
486
- amplitude
487
486
  arabic-form
488
487
  ascent
489
488
  attributeType
@@ -492,12 +491,29 @@ module Sevgi
492
491
  ]
493
492
  )
494
493
 
494
+ RESERVED_NAMESPACE_PREFIXES = %w[xlink: xml:].freeze
495
+ private_constant :RESERVED_NAMESPACE_PREFIXES
496
+
497
+ # Reports whether an attribute name should be ignored by standard validation.
498
+ # @param attribute [String, Symbol, Object] candidate attribute name
499
+ # @return [Boolean] true for valid private, data, xmlns, or foreign namespaced attributes
495
500
  def ignore?(attribute)
496
- attribute.start_with?("_") ||
497
- attribute == :xmlns ||
498
- attribute.start_with?("data-") ||
499
- (attribute.to_s.include?(":") && !attribute.start_with?("xlink:") && !attribute.start_with?("xml:"))
501
+ return false unless attribute.is_a?(::String) || attribute.is_a?(::Symbol)
502
+
503
+ name = attribute.to_s
504
+
505
+ Name.valid?(name) && ignored_name?(name)
500
506
  end
507
+
508
+ def ignored_name?(name)
509
+ name.start_with?("_") || name == "xmlns" || name.start_with?("data-") || foreign_namespace?(name)
510
+ end
511
+
512
+ def foreign_namespace?(name)
513
+ name.include?(":") && RESERVED_NAMESPACE_PREFIXES.none? { name.start_with?(it) }
514
+ end
515
+
516
+ private_class_method :foreign_namespace?, :ignored_name?
501
517
  end
502
518
  end
503
519
  end
@@ -8,6 +8,7 @@ module Sevgi
8
8
  animate
9
9
  animateMotion
10
10
  animateTransform
11
+ discard
11
12
  mpath
12
13
  set
13
14
  ],
@@ -146,7 +147,6 @@ module Sevgi
146
147
  linearGradient
147
148
  pattern
148
149
  radialGradient
149
- solidcolor
150
150
  ],
151
151
 
152
152
  Renderable: %i[
@@ -265,8 +265,15 @@ module Sevgi
265
265
  ]
266
266
  )
267
267
 
268
+ # Reports whether an element name should be ignored by standard validation.
269
+ # @param element [String, Symbol, Object] candidate element name
270
+ # @return [Boolean] true for valid private or namespaced element names that Sevgi should not validate
268
271
  def ignore?(element)
269
- (name = element.to_s).include?(":") || name.start_with?("_")
272
+ return false unless element.is_a?(::String) || element.is_a?(::Symbol)
273
+
274
+ name = element.to_s
275
+
276
+ Name.valid?(name) && (name.include?(":") || name.start_with?("_"))
270
277
  end
271
278
  end
272
279
  end
@@ -4,12 +4,44 @@ require_relative "data/color"
4
4
 
5
5
  module Sevgi
6
6
  module Standard
7
+ # Defensive copy helper for public standard-data snapshots.
8
+ # @api private
9
+ module Snapshot
10
+ # Returns a recursively independent copy of a value.
11
+ # @param value [Object] value to copy
12
+ # @return [Object] copied value
13
+ def self.copy(value)
14
+ case value
15
+ when ::Hash
16
+ hash(value)
17
+ when ::Array
18
+ value.map { copy(it) }
19
+ when ::Set
20
+ Set[*value.map { copy(it) }]
21
+ else
22
+ duplicate(value)
23
+ end
24
+ end
25
+
26
+ def self.hash(value) = value.to_h { |key, item| [key, copy(item)] }
27
+
28
+ def self.duplicate(value)
29
+ value.dup
30
+ rescue ::TypeError
31
+ value
32
+ end
33
+
34
+ private_class_method :duplicate, :hash
35
+ end
36
+
37
+ private_constant :Snapshot
38
+
7
39
  # Shared set operations for SVG standard data lists.
8
40
  # @api private
9
41
  module Common
10
42
  # Returns every known name in this data list.
11
- # @return [Set<Symbol>]
12
- def all = @all ||= Set[*data.values.flatten.uniq.sort]
43
+ # @return [Set<Symbol>] mutation-isolated set snapshot
44
+ def all = Snapshot.copy(@all ||= Set[*data.values.flatten.uniq.sort])
13
45
 
14
46
  # Checks whether a name belongs to a group.
15
47
  # @param name [Symbol] item name
@@ -25,7 +57,7 @@ module Sevgi
25
57
 
26
58
  # Returns all names or names from selected groups.
27
59
  # @param groups [Array<Symbol>] group names
28
- # @return [Set<Symbol>] selected names
60
+ # @return [Set<Symbol>] mutation-isolated selected-name snapshot
29
61
  def set(*groups) = groups.empty? ? all : Set[*data.values_at(*groups).flatten.compact.uniq.sort]
30
62
 
31
63
  # Removes names that belong to any requested group.
@@ -74,8 +106,8 @@ module Sevgi
74
106
 
75
107
  # Returns expanded standard specification data for one element.
76
108
  # @param name [Symbol] SVG element name
77
- # @return [Hash, nil] expanded specification data
78
- def [](name) = expand(name)
109
+ # @return [Hash, nil] mutation-isolated expanded specification snapshot
110
+ def [](name) = Snapshot.copy(expand(name))
79
111
 
80
112
  # Reports whether a name is a data group name.
81
113
  # @param name [Symbol] element or group name
@@ -83,10 +115,16 @@ module Sevgi
83
115
  def group?(name) = /[[:upper:]]/.match?(name[0])
84
116
 
85
117
  # Checks whether an element uses one of the requested content models.
86
- # @param name [Symbol] SVG element name
87
- # @param models [Array<Symbol>] model names to match
118
+ # @param name [String, Symbol] SVG element name
119
+ # @param models [Array<String, Symbol>] model names to match
88
120
  # @return [Boolean]
89
- def model?(name, *models) = models.any? { (self[name] || {})[:model] == it }
121
+ # @raise [Sevgi::ArgumentError] when any name is not a valid public name
122
+ def model?(name, *models)
123
+ name = Name.normalize!(name, context: "element")
124
+ models = models.map { Name.normalize!(it, context: "model") }
125
+
126
+ models.any? { (self[name] || {})[:model] == it }
127
+ end
90
128
 
91
129
  private
92
130
 
@@ -104,12 +142,12 @@ module Sevgi
104
142
  def expand_names(names, list)
105
143
  return unless names
106
144
 
107
- names.replace(names.map { |name| group?(name) ? list[name] : name }.flatten)
145
+ names.replace(names.flat_map { |name| group?(name) ? list[name] : name }.uniq)
108
146
  end
109
147
 
110
148
  # For testing purposes
111
149
 
112
- def charge = data.keys { expand(it) }
150
+ def charge = data.each_key { expand(it) }
113
151
 
114
152
  def flush = (@spec = {})
115
153
  end
@@ -2,6 +2,47 @@
2
2
 
3
3
  module Sevgi
4
4
  module Standard
5
+ # Normalizes public SVG standard names at API boundaries.
6
+ # @api private
7
+ module Name
8
+ PATTERN = /\A[A-Za-z_][A-Za-z0-9_.-]*(?::[A-Za-z_][A-Za-z0-9_.-]*)?\z/
9
+ private_constant :PATTERN
10
+
11
+ # Reports whether a string is a supported SVG-style name token.
12
+ # @param name [String] candidate name
13
+ # @return [Boolean]
14
+ def self.valid?(name) = !!PATTERN.match?(name)
15
+
16
+ # Normalizes one public name to the registry symbol form.
17
+ # @param value [String, Symbol] public name
18
+ # @param context [String] name context for error messages
19
+ # @return [Symbol] normalized name
20
+ # @raise [Sevgi::ArgumentError] when value is not a String or Symbol
21
+ # @raise [Sevgi::ArgumentError] when value is not a valid SVG-style name
22
+ def self.normalize!(value, context:)
23
+ case value
24
+ when ::String, ::Symbol
25
+ text = value.to_s
26
+ ArgumentError.("Invalid SVG #{context} name: #{value.inspect}") unless valid?(text)
27
+
28
+ text.to_sym
29
+ else
30
+ ArgumentError.("SVG #{context} name must be a String or Symbol: #{value.inspect}")
31
+ end
32
+ end
33
+
34
+ # Normalizes a list of public names.
35
+ # @param values [Array<String, Symbol>, nil] public names
36
+ # @param context [String] name context for error messages
37
+ # @return [Array<Symbol>, nil] normalized names
38
+ # @raise [Sevgi::ArgumentError] when any value is not a valid public name
39
+ def self.list!(values, context:)
40
+ values&.map { normalize!(it, context:) }
41
+ end
42
+ end
43
+
44
+ private_constant :Name
45
+
5
46
  # Low-level import and lookup helper for static SVG standard data.
6
47
  # @api private
7
48
  module List
@@ -103,7 +103,7 @@ module Sevgi
103
103
  UnmetConditionError.(element, "Exactly one FilterLightSource element as first required")
104
104
  end
105
105
 
106
- if !(unallowed = Element.unpick(elements[1..], :Descriptive)).empty? && !unallowed.empty?
106
+ unless (unallowed = Element.unpick(elements[1..], :Descriptive)).empty?
107
107
  UnallowedElementsError.(element, unallowed)
108
108
  end
109
109
  end
@@ -3,6 +3,6 @@
3
3
  module Sevgi
4
4
  module Standard
5
5
  # Current version of the Sevgi standard gem.
6
- VERSION = "0.93.1"
6
+ VERSION = "0.94.0"
7
7
  end
8
8
  end
@@ -12,27 +12,34 @@ require_relative "standard/version"
12
12
 
13
13
  module Sevgi
14
14
  # SVG standard data and validation helpers.
15
+ # The registry is a Sevgi compatibility set based on SVG 2 plus the split-out SVG modules and legacy entries already
16
+ # modeled in the bundled specifications. Supported element names must have matching specification data; abandoned or
17
+ # unspecified proposal entries are not exposed as supported elements.
15
18
  module Standard
16
19
  extend self
17
20
 
18
21
  # @overload attributes(*groups)
19
22
  # Returns SVG attributes, optionally restricted to one or more attribute groups.
20
23
  # @param groups [Array<Symbol>] attribute group names
21
- # @return [Set<Symbol>] attribute names
24
+ # @return [Set<Symbol>] mutation-isolated attribute-name snapshot
22
25
  def attributes(...) = Attribute.set(...)
23
26
 
24
27
  # Reports whether an attribute name is recognized by the SVG standard data.
25
- # @param name [Object] attribute name
28
+ # @param name [String, Symbol] attribute name
26
29
  # @return [Boolean]
27
- def attribute?(name) = name.respond_to?(:to_sym) && Attribute.all.include?(name.to_sym)
30
+ # @raise [Sevgi::ArgumentError] when name is not a String or Symbol
31
+ # @raise [Sevgi::ArgumentError] when name is not a valid SVG-style name
32
+ def attribute?(name) = Attribute.all.include?(Name.normalize!(name, context: "attribute"))
28
33
 
29
34
  # @overload conform(element, attributes: nil, cdata: nil, elements: nil)
30
35
  # Validates an SVG element usage against the standard data.
31
- # @param element [Symbol] SVG element name
32
- # @param attributes [Array<Symbol>, nil] attribute names used by the element
36
+ # @param element [String, Symbol] SVG element name
37
+ # @param attributes [Array<String, Symbol>, nil] attribute names used by the element
33
38
  # @param cdata [String, nil] character data content
34
- # @param elements [Array<Symbol>, nil] child element names
39
+ # @param elements [Array<String, Symbol>, nil] child element names
35
40
  # @return [Boolean] true when the usage conforms
41
+ # @raise [Sevgi::ArgumentError] when any name is not a String or Symbol
42
+ # @raise [Sevgi::ArgumentError] when any name is not a valid SVG-style name
36
43
  # @raise [Sevgi::ValidationError] when the usage violates the standard data
37
44
  # @raise [Sevgi::PanicError] when the standard data refers to an invalid model
38
45
  def conform(...) = Conform.(...)
@@ -40,25 +47,32 @@ module Sevgi
40
47
  # @overload elements(*groups)
41
48
  # Returns SVG elements, optionally restricted to one or more element groups.
42
49
  # @param groups [Array<Symbol>] element group names
43
- # @return [Set<Symbol>] element names
50
+ # @return [Set<Symbol>] mutation-isolated element-name snapshot
44
51
  def elements(...) = Element.set(...)
45
52
 
46
53
  # Reports whether an element name is recognized by the SVG standard data.
47
- # @param name [Object] element name
54
+ # @param name [String, Symbol] element name
48
55
  # @return [Boolean]
49
- def element?(name) = name.respond_to?(:to_sym) && Element.all.include?(name.to_sym)
56
+ # @raise [Sevgi::ArgumentError] when name is not a String or Symbol
57
+ # @raise [Sevgi::ArgumentError] when name is not a valid SVG-style name
58
+ def element?(name) = Element.all.include?(Name.normalize!(name, context: "element"))
50
59
 
51
60
  # @overload model?(name, *models)
52
61
  # Checks whether an element uses one of the requested content models.
53
- # @param name [Symbol] SVG element name
54
- # @param models [Array<Symbol>] model names to match
62
+ # @param name [String, Symbol] SVG element name
63
+ # @param models [Array<String, Symbol>] model names to match
55
64
  # @return [Boolean]
65
+ # @raise [Sevgi::ArgumentError] when any name is not a String or Symbol
66
+ # @raise [Sevgi::ArgumentError] when any name is not a valid SVG-style name
56
67
  def model?(...) = Specification.model?(...)
57
68
 
58
69
  # Returns the expanded standard contract for an SVG element.
59
- # @param name [Object] SVG element name
60
- # @return [Hash, nil] expanded specification data, or nil when name is invalid or unknown
61
- def specification(name) = name.respond_to?(:to_sym) ? Specification[name.to_sym] : nil
70
+ # The returned hash and nested arrays are mutation-isolated snapshots; changing them does not alter the registry.
71
+ # @param name [String, Symbol] SVG element name
72
+ # @return [Hash, nil] expanded specification snapshot, or nil when name is unknown
73
+ # @raise [Sevgi::ArgumentError] when name is not a String or Symbol
74
+ # @raise [Sevgi::ArgumentError] when name is not a valid SVG-style name
75
+ def specification(name) = Specification[Name.normalize!(name, context: "element")]
62
76
 
63
77
  alias [] specification
64
78
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sevgi-standard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.93.1
4
+ version: 0.94.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Recai Oktaş
@@ -15,20 +15,22 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.93.1
18
+ version: 0.94.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.93.1
25
+ version: 0.94.0
26
26
  description: Validates elements and attributes according to the SVG specification.
27
27
  email: roktas@gmail.com
28
28
  executables: []
29
29
  extensions: []
30
30
  extra_rdoc_files: []
31
31
  files:
32
+ - CHANGELOG.md
33
+ - LICENSE
32
34
  - README.md
33
35
  - lib/sevgi/standard.rb
34
36
  - lib/sevgi/standard/conform.rb
@@ -56,7 +58,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
56
58
  requirements:
57
59
  - - ">="
58
60
  - !ruby/object:Gem::Version
59
- version: 3.4.0.pre.preview1
61
+ version: 3.4.0
60
62
  required_rubygems_version: !ruby/object:Gem::Requirement
61
63
  requirements:
62
64
  - - ">="