sevgi-standard 0.73.2 → 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: fd86945f53c28030148a5d7e40a05c370ea9743f92cf1c264f3c30741d65baa8
4
- data.tar.gz: d4012c0190f449a989c10f35d60153c397dabd9536d5567caa3956c1e3f0db68
3
+ metadata.gz: 032a1a8f609e2cebf4de52c844f69cdfc13504a438c08042aff5dde09ef0b331
4
+ data.tar.gz: 8aa466073442ec8062aa48468e7274e7e9d275558a8be154cf26fce32c121d0d
5
5
  SHA512:
6
- metadata.gz: 65a9c7367b4ebc2e34fe647d71cfe50ee0038ab3acdf688a2e5460d9bd500677ca93c91daa63c08e1b958c6a6b993e864cca65ef0ee38c6bd61b78fbb532846f
7
- data.tar.gz: 0b787afc81c3edf324bc272c7d25d22ff7a60d05b48fc8708981dc5ae2e8c6587a178b8a35cb9fba6ca2dd2edc4edc889190ded91a488aa47e007d70272a5d8d
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
@@ -0,0 +1,37 @@
1
+ # Sevgi Standard
2
+
3
+ SVG element, attribute, and conformance data used by Sevgi validation.
4
+
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
@@ -2,22 +2,43 @@
2
2
 
3
3
  module Sevgi
4
4
  module Standard
5
+ # Validates one SVG element usage against the standard data.
6
+ # @api private
5
7
  class Conform
6
8
  require_relative "model"
7
9
 
8
- attr_reader :element, :spec
10
+ # @return [Symbol] SVG element name
11
+ attr_reader :element
9
12
 
13
+ # @return [Hash] expanded standard specification for the element
14
+ attr_reader :spec
15
+
16
+ # Builds a validator for one SVG element.
17
+ # @param element [String, Symbol] SVG element name
18
+ # @return [void]
19
+ # @raise [Sevgi::ArgumentError] when element is not a valid public name
20
+ # @raise [Sevgi::InvalidElementsError] when the element is unknown
21
+ # @raise [Sevgi::PanicError] when the element model is missing or unimplemented
10
22
  def initialize(element)
23
+ element = Name.normalize!(element, context: "element")
24
+
11
25
  InvalidElementsError.(element) unless (@spec = Specification[@element = element])
12
26
 
13
- raise ModelError, "No model specified: #{element}" unless spec[:model]
14
- raise ModelError, "Model unimplemented: #{spec[:model]}" unless Model.const_defined?(spec[:model])
27
+ PanicError.("No model specified: #{element}") unless spec[:model]
28
+ PanicError.("Model unimplemented: #{spec[:model]}") unless Model.const_defined?(spec[:model])
15
29
 
16
30
  extend(Model.const_get(spec[:model]))
17
31
 
18
- raise NoMethodError, "#{self.class}#apply must be implemented" unless respond_to?(:apply)
32
+ PanicError.("#{self.class}#apply must be implemented") unless respond_to?(:apply)
19
33
  end
20
34
 
35
+ # Validates one usage of the configured SVG element.
36
+ # @param attributes [Array<String, Symbol>, nil] attribute names used by the element
37
+ # @param cdata [String, nil] character data content
38
+ # @param elements [Array<String, Symbol>, nil] child element names
39
+ # @return [Boolean] true when the usage conforms
40
+ # @raise [Sevgi::ArgumentError] when any name is not a valid public name
41
+ # @raise [Sevgi::ValidationError] when the usage violates the standard data
21
42
  def call(attributes: nil, cdata: nil, elements: nil)
22
43
  if attributes
23
44
  unrecognized = attributes - spec[:attributes]
@@ -32,7 +53,20 @@ module Sevgi
32
53
 
33
54
  @cache = {}
34
55
 
56
+ # Validates one SVG element usage, using cached element validators.
57
+ # @param element [String, Symbol] SVG element name
58
+ # @param attributes [Array<String, Symbol>, nil] attribute names used by the element
59
+ # @param cdata [String, nil] character data content
60
+ # @param elements [Array<String, Symbol>, nil] child element names
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
63
+ # @raise [Sevgi::ValidationError] when the usage violates the standard data
64
+ # @raise [Sevgi::PanicError] when the standard data refers to an invalid model
35
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
+
36
70
  Element.ignore?(element) or
37
71
  (@cache[element] ||= new(element)).call(
38
72
  attributes: Attribute.concerns(attributes),
@@ -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
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Recognized color keyword names: https://www.w3.org/TR/2003/REC-SVG11-20030114/types.html#ColorKeywords
4
-
5
3
  module Sevgi
6
4
  module Standard
5
+ # Recognized SVG color keyword names mapped to hexadecimal RGB values.
7
6
  Color = {
8
7
  aliceblue: "#F0F8FF",
9
8
  antiquewhite: "#FAEBD7",
@@ -154,7 +153,7 @@ module Sevgi
154
153
  yellowgreen: "#9ACD32"
155
154
  }
156
155
  .tap do |color|
157
- def color.valid?(name) = key?(name.to_sym)
156
+ def color.valid?(name) = name.respond_to?(:to_sym) && key?(name.to_sym)
158
157
 
159
158
  color.each { |name, hex| color.define_singleton_method(name) { hex } }
160
159
  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,19 +4,77 @@ require_relative "data/color"
4
4
 
5
5
  module Sevgi
6
6
  module Standard
7
- module Common
8
- def all = @all ||= Set[*data.values.flatten.uniq.sort]
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
9
33
 
34
+ private_class_method :duplicate, :hash
35
+ end
36
+
37
+ private_constant :Snapshot
38
+
39
+ # Shared set operations for SVG standard data lists.
40
+ # @api private
41
+ module Common
42
+ # Returns every known name in this data list.
43
+ # @return [Set<Symbol>] mutation-isolated set snapshot
44
+ def all = Snapshot.copy(@all ||= Set[*data.values.flatten.uniq.sort])
45
+
46
+ # Checks whether a name belongs to a group.
47
+ # @param name [Symbol] item name
48
+ # @param group [Symbol] group name
49
+ # @return [Boolean]
10
50
  def is?(name, group) = self[group].include?(name)
11
51
 
52
+ # Keeps names that belong to any requested group.
53
+ # @param names [Array<Symbol>] names to filter
54
+ # @param groups [Array<Symbol>] group names
55
+ # @return [Array<Symbol>] filtered names
12
56
  def pick(names, *groups) = names.select { |name| groups.any? { is?(name, it) } }
13
57
 
58
+ # Returns all names or names from selected groups.
59
+ # @param groups [Array<Symbol>] group names
60
+ # @return [Set<Symbol>] mutation-isolated selected-name snapshot
14
61
  def set(*groups) = groups.empty? ? all : Set[*data.values_at(*groups).flatten.compact.uniq.sort]
15
62
 
63
+ # Removes names that belong to any requested group.
64
+ # @param names [Array<Symbol>] names to filter
65
+ # @param groups [Array<Symbol>] group names
66
+ # @return [Array<Symbol>] filtered names
16
67
  def unpick(names, *groups) = names.reject { |name| groups.any? { is?(name, it) } }
17
68
 
69
+ # Removes ignored names from a list.
70
+ # @param names [Array<Symbol>, nil] names to filter
71
+ # @return [Array<Symbol>, nil] names that should be validated
18
72
  def concerns(names) = names ? names.reject { ignore?(it) } : names
19
73
 
74
+ # Installs low-level list helpers into data modules.
75
+ # @param base [Module] data module receiving helpers
76
+ # @return [void]
77
+ # @api private
20
78
  def self.extended(base) = base.extend(List)
21
79
  end
22
80
 
@@ -46,11 +104,27 @@ module Sevgi
46
104
 
47
105
  require_relative "data/specification"
48
106
 
49
- def [](name) = expand(name)
107
+ # Returns expanded standard specification data for one element.
108
+ # @param name [Symbol] SVG element name
109
+ # @return [Hash, nil] mutation-isolated expanded specification snapshot
110
+ def [](name) = Snapshot.copy(expand(name))
50
111
 
112
+ # Reports whether a name is a data group name.
113
+ # @param name [Symbol] element or group name
114
+ # @return [Boolean]
51
115
  def group?(name) = /[[:upper:]]/.match?(name[0])
52
116
 
53
- def model?(name, *models) = models.any? { (self[name] || {})[:model] == name }
117
+ # Checks whether an element uses one of the requested content models.
118
+ # @param name [String, Symbol] SVG element name
119
+ # @param models [Array<String, Symbol>] model names to match
120
+ # @return [Boolean]
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
54
128
 
55
129
  private
56
130
 
@@ -58,7 +132,7 @@ module Sevgi
58
132
 
59
133
  def expand(name)
60
134
  @spec[name] ||= if data[name]
61
- data[name].dup.tap do |spec|
135
+ data[name].transform_values { |value| value.is_a?(::Array) ? value.dup : value }.tap do |spec|
62
136
  expand_names(spec[:elements], Element.data)
63
137
  expand_names(spec[:attributes], Attribute.data)
64
138
  end
@@ -68,12 +142,12 @@ module Sevgi
68
142
  def expand_names(names, list)
69
143
  return unless names
70
144
 
71
- names.replace(names.map { |name| group?(name) ? list[name] : name }.flatten)
145
+ names.replace(names.flat_map { |name| group?(name) ? list[name] : name }.uniq)
72
146
  end
73
147
 
74
148
  # For testing purposes
75
149
 
76
- def charge = data.keys { expand(it) }
150
+ def charge = data.each_key { expand(it) }
77
151
 
78
152
  def flush = (@spec = {})
79
153
  end
@@ -1,7 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sevgi
4
+ # Base class for SVG standard validation errors.
4
5
  class ValidationError < Error
6
+ # Builds a validation error with a context label and offending values.
7
+ # @param context [Object] validation context included in the error message
8
+ # @param args [Array<Object>, Object] invalid values included in the error message
9
+ # @return [void]
5
10
  def initialize(context, args = [])
6
11
  @context = context
7
12
  @args = Array(args)
@@ -9,8 +14,18 @@ module Sevgi
9
14
  super()
10
15
  end
11
16
 
12
- def self.call(context, args = []) = raise new(context, args)
17
+ # Raises this validation error class.
18
+ # @param context [Object] validation context included in the error message
19
+ # @param args [Array<Object>, Object] invalid values included in the error message
20
+ # @return [void]
21
+ # @raise [Sevgi::ValidationError] always raises an instance of this class
22
+ def self.call(context, args = [])
23
+ raise new(context, args)
24
+ end
13
25
 
26
+ # Builds a validation error subclass with a fixed message fragment.
27
+ # @param message [String] message fragment placed after the validation context
28
+ # @return [Class<Sevgi::ValidationError>] validation error subclass
14
29
  def self.variant(message)
15
30
  Class.new(self) do
16
31
  define_method(:message) do
@@ -24,9 +39,14 @@ module Sevgi
24
39
  end
25
40
  end
26
41
 
42
+ # Raised when an element contains invalid attributes.
27
43
  InvalidAttributesError = ValidationError.variant("Invalid attribute(s)")
44
+ # Raised when an element contains invalid child elements.
28
45
  InvalidElementsError = ValidationError.variant("Invalid element(s)")
46
+ # Raised when character data appears where the SVG model forbids it.
29
47
  UnallowedCDataError = ValidationError.variant("Character data not allowed")
48
+ # Raised when an element contains disallowed child elements.
30
49
  UnallowedElementsError = ValidationError.variant("Element(s) not allowed")
50
+ # Raised when a conditional SVG content model requirement is not met.
31
51
  UnmetConditionError = ValidationError.variant("Condition unmet for the element")
32
52
  end
@@ -2,13 +2,69 @@
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
+
46
+ # Low-level import and lookup helper for static SVG standard data.
47
+ # @api private
5
48
  module List
49
+ # Looks up a data entry.
50
+ # @param name [Symbol] data key
51
+ # @return [Object, nil] stored data value
6
52
  def [](name) = data[name]
7
53
 
54
+ # Imports new data entries without overwriting existing keys.
55
+ # @param kwargs [Hash] entries to import
56
+ # @return [Hash] backing data hash
8
57
  def import(**kwargs) = data.merge!(kwargs.reject { |key, _| data.key?(key) })
9
58
 
59
+ # Reports whether a data entry exists.
60
+ # @param name [Symbol] data key
61
+ # @return [Boolean]
10
62
  def valid?(name) = data.key?(name)
11
63
 
64
+ # Initializes the backing data hash on extended modules.
65
+ # @param base [Module] module receiving list behavior
66
+ # @return [void]
67
+ # @api private
12
68
  def self.extended(base)
13
69
  super
14
70
 
@@ -2,8 +2,16 @@
2
2
 
3
3
  module Sevgi
4
4
  module Standard
5
+ # SVG content model validators used by {Conform}.
6
+ # @api private
5
7
  module Model
8
+ # Validates elements that allow only character data.
6
9
  module CDataOnly
10
+ # Applies this content model.
11
+ # @param cdata [String, nil] character data content
12
+ # @param elements [Array<Symbol>] child element names
13
+ # @return [nil]
14
+ # @raise [Sevgi::UnallowedElementsError] when child elements are present
7
15
  def apply(cdata:, elements:)
8
16
  _cdata = cdata
9
17
 
@@ -11,7 +19,13 @@ module Sevgi
11
19
  end
12
20
  end
13
21
 
22
+ # Validates elements that allow character data or selected child elements.
14
23
  module CDataOrSomeElements
24
+ # Applies this content model.
25
+ # @param cdata [String, nil] character data content
26
+ # @param elements [Array<Symbol>] child element names
27
+ # @return [nil]
28
+ # @raise [Sevgi::UnallowedElementsError] when unallowed child elements are present
15
29
  def apply(cdata:, elements:)
16
30
  _cdata = cdata
17
31
 
@@ -20,7 +34,14 @@ module Sevgi
20
34
  end
21
35
  end
22
36
 
37
+ # Validates empty elements.
23
38
  module NoneElements
39
+ # Applies this content model.
40
+ # @param cdata [String, nil] character data content
41
+ # @param elements [Array<Symbol>] child element names
42
+ # @return [nil]
43
+ # @raise [Sevgi::UnallowedCDataError] when character data is present
44
+ # @raise [Sevgi::UnallowedElementsError] when child elements are present
24
45
  def apply(cdata:, elements:)
25
46
  UnallowedCDataError.(element, cdata) if cdata
26
47
 
@@ -28,7 +49,14 @@ module Sevgi
28
49
  end
29
50
  end
30
51
 
52
+ # Validates elements that allow selected child elements but no character data.
31
53
  module SomeElements
54
+ # Applies this content model.
55
+ # @param cdata [String, nil] character data content
56
+ # @param elements [Array<Symbol>] child element names
57
+ # @return [nil]
58
+ # @raise [Sevgi::UnallowedCDataError] when character data is present
59
+ # @raise [Sevgi::UnallowedElementsError] when unallowed child elements are present
32
60
  def apply(cdata:, elements:)
33
61
  UnallowedCDataError.(element, cdata) if cdata
34
62
 
@@ -37,8 +65,15 @@ module Sevgi
37
65
  end
38
66
  end
39
67
 
68
+ # Validates the special `feDiffuseLighting` content model.
40
69
  module SpecialFeDiffuseLighting
41
- # Any number of Descriptive elements and exactly one FilterLightSource element, in any order.
70
+ # Applies this content model.
71
+ # @param cdata [String, nil] character data content
72
+ # @param elements [Array<Symbol>] child element names
73
+ # @return [nil]
74
+ # @raise [Sevgi::UnallowedCDataError] when character data is present
75
+ # @raise [Sevgi::UnallowedElementsError] when unallowed child elements are present
76
+ # @raise [Sevgi::UnmetConditionError] when required light source elements are absent
42
77
  def apply(cdata:, elements:)
43
78
  UnallowedCDataError.(element, cdata) if cdata
44
79
 
@@ -52,8 +87,15 @@ module Sevgi
52
87
  end
53
88
  end
54
89
 
90
+ # Validates the special `feSpecularLighting` content model.
55
91
  module SpecialFeSpecularLighting
56
- # Exactly one FilterLightSource element first and any number of Descriptive elements in any order.
92
+ # Applies this content model.
93
+ # @param cdata [String, nil] character data content
94
+ # @param elements [Array<Symbol>] child element names
95
+ # @return [nil]
96
+ # @raise [Sevgi::UnallowedCDataError] when character data is present
97
+ # @raise [Sevgi::UnallowedElementsError] when unallowed child elements are present
98
+ # @raise [Sevgi::UnmetConditionError] when the first child is not a light source
57
99
  def apply(cdata:, elements:)
58
100
  UnallowedCDataError.(element, cdata) if cdata
59
101
 
@@ -61,14 +103,21 @@ module Sevgi
61
103
  UnmetConditionError.(element, "Exactly one FilterLightSource element as first required")
62
104
  end
63
105
 
64
- if !(unallowed = Element.unpick(elements[1..], :Descriptive)).empty? && !unallowed.empty?
106
+ unless (unallowed = Element.unpick(elements[1..], :Descriptive)).empty?
65
107
  UnallowedElementsError.(element, unallowed)
66
108
  end
67
109
  end
68
110
  end
69
111
 
112
+ # Validates the special `font-face` content model.
70
113
  module SpecialFontFace
71
- # Any number of Descriptive elements and at most one font-face element in any order.
114
+ # Applies this content model.
115
+ # @param cdata [String, nil] character data content
116
+ # @param elements [Array<Symbol>] child element names
117
+ # @return [nil]
118
+ # @raise [Sevgi::UnallowedCDataError] when character data is present
119
+ # @raise [Sevgi::UnallowedElementsError] when unallowed child elements are present
120
+ # @raise [Sevgi::UnmetConditionError] when more than one `font-face` child is present
72
121
  def apply(cdata:, elements:)
73
122
  UnallowedCDataError.(element, cdata) if cdata
74
123
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Sevgi
4
4
  module Standard
5
- VERSION = "0.73.2"
5
+ # Current version of the Sevgi standard gem.
6
+ VERSION = "0.94.0"
6
7
  end
7
8
  end
@@ -11,21 +11,69 @@ require_relative "standard/conform"
11
11
  require_relative "standard/version"
12
12
 
13
13
  module Sevgi
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.
14
18
  module Standard
15
19
  extend self
16
20
 
21
+ # @overload attributes(*groups)
22
+ # Returns SVG attributes, optionally restricted to one or more attribute groups.
23
+ # @param groups [Array<Symbol>] attribute group names
24
+ # @return [Set<Symbol>] mutation-isolated attribute-name snapshot
17
25
  def attributes(...) = Attribute.set(...)
18
26
 
19
- def attribute?(name) = Attribute.all.include?(name.to_sym)
27
+ # Reports whether an attribute name is recognized by the SVG standard data.
28
+ # @param name [String, Symbol] attribute name
29
+ # @return [Boolean]
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"))
20
33
 
34
+ # @overload conform(element, attributes: nil, cdata: nil, elements: nil)
35
+ # Validates an SVG element usage against the standard data.
36
+ # @param element [String, Symbol] SVG element name
37
+ # @param attributes [Array<String, Symbol>, nil] attribute names used by the element
38
+ # @param cdata [String, nil] character data content
39
+ # @param elements [Array<String, Symbol>, nil] child element names
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
43
+ # @raise [Sevgi::ValidationError] when the usage violates the standard data
44
+ # @raise [Sevgi::PanicError] when the standard data refers to an invalid model
21
45
  def conform(...) = Conform.(...)
22
46
 
47
+ # @overload elements(*groups)
48
+ # Returns SVG elements, optionally restricted to one or more element groups.
49
+ # @param groups [Array<Symbol>] element group names
50
+ # @return [Set<Symbol>] mutation-isolated element-name snapshot
23
51
  def elements(...) = Element.set(...)
24
52
 
25
- def element?(name) = Element.all.include?(name.to_sym)
53
+ # Reports whether an element name is recognized by the SVG standard data.
54
+ # @param name [String, Symbol] element name
55
+ # @return [Boolean]
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"))
26
59
 
60
+ # @overload model?(name, *models)
61
+ # Checks whether an element uses one of the requested content models.
62
+ # @param name [String, Symbol] SVG element name
63
+ # @param models [Array<String, Symbol>] model names to match
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
27
67
  def model?(...) = Specification.model?(...)
28
68
 
29
- def [](name) = Specification[name.to_sym]
69
+ # Returns the expanded standard contract for an SVG element.
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")]
76
+
77
+ alias [] specification
30
78
  end
31
79
  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.73.2
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.73.2
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.73.2
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
  - - ">="