nokogiri_schematron_builder 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,42 @@
1
+ require "nokogiri/xml/schematron/base"
2
+
3
+ module Nokogiri
4
+ module XML
5
+ module Schematron
6
+ # The internal representation of the +<sch:ns>+ XML element.
7
+ #
8
+ # For example:
9
+ #
10
+ # namespace = Nokogiri::XML::Schematron::Namespace.new(nil, prefix: "ex", uri: "http://example.com/ns#")
11
+ # # => #<Nokogiri::XML::Schematron::Namespace:0x00007f8486acd4f8 @parent=nil, @children=[], @options={:prefix=>"ex", :uri=>"http://example.com/ns#"}>
12
+ # namespace.to_builder.to_xml
13
+ # # => "<?xml version=\"1.0\"?>\n<sch:ns xmlns:sch=\"http://purl.oclc.org/dsdl/schematron\" prefix=\"ex\" uri=\"http://example.com/ns#\"/>\n"
14
+ #
15
+ class Namespace < Nokogiri::XML::Schematron::Base
16
+ # @!attribute [rw] prefix
17
+ # @return [String] the value of the +@prefix+ XML attribute.
18
+ attribute :prefix
19
+
20
+ # @!attribute [rw] uri
21
+ # @return [String] the value of the +@uri+ XML attribute.
22
+ attribute :uri
23
+
24
+ protected
25
+
26
+ def build!(xml)
27
+ xml["sch"].send(:ns, %w(prefix uri).inject(xmlns) { |acc, method_name|
28
+ unless (s = send(method_name.to_sym)).nil?
29
+ acc[method_name.to_s] = s
30
+ end
31
+
32
+ acc
33
+ }) do
34
+ super(xml)
35
+ end
36
+
37
+ return
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,189 @@
1
+ require "nokogiri/xml/schematron/base"
2
+
3
+ module Nokogiri
4
+ module XML
5
+ module Schematron
6
+ module Nodes
7
+ # The base class for internal representations of abstract syntax tree
8
+ # (AST) nodes, where the tree is denested and then used to build a
9
+ # sequence of zero or more +<sch:rule>+ elements.
10
+ # @abstract
11
+ class Base < Nokogiri::XML::Schematron::Base
12
+ # @return [Hash<String, Nokogiri::XML::Schematron::Nodes::Base>] the hash of child nodes by XPaths.
13
+ attr_reader :child_node_by_context
14
+
15
+ # @return [String] the XPath.
16
+ attr_accessor :context
17
+
18
+ # Create a new +Nodes::Base+ object.
19
+ #
20
+ # @param parent [Nokogiri::XML::Schematron::Base] the parent object.
21
+ # @param context [String] the XPath.
22
+ # @param options [Hash<Symbol, Object>] the options.
23
+ # @yieldparam node [Nokogiri::XML::Schematron::Nodes::Base] the +Nodes::Base+ object.
24
+ # @yieldreturn [void]
25
+ # @raise [ArgumentError] if the XPath is +nil+.
26
+ def initialize(parent, context, **options, &block)
27
+ @child_node_by_context = {}
28
+
29
+ if context.nil?
30
+ raise ::ArgumentError
31
+ else
32
+ @context = context
33
+ end
34
+
35
+ super(parent, **options, &block)
36
+ end
37
+
38
+ # Create a new +Nodes::Permit+ object.
39
+ #
40
+ # @param context [String] the XPath.
41
+ # @param options [Hash<Symbol, Object>] the options.
42
+ # @yieldparam node [Nokogiri::XML::Schematron::Nodes::Permit] the +Nodes::Permit+ object.
43
+ # @yieldreturn [void]
44
+ # @return [Nokogiri::XML::Schematron::Nodes::Permit] the +Nodes::Permit+ object.
45
+ # @raise [ArgumentError] if the XPath is +nil+.
46
+ def permit(context, **options, &block)
47
+ node = Nokogiri::XML::Schematron::Nodes::Permit.new(self, context, **options, &block)
48
+ @child_node_by_context[context] = node
49
+ node
50
+ end
51
+
52
+ # Create a new +Nodes::Reject+ object.
53
+ #
54
+ # @param context [String] the XPath.
55
+ # @param options [Hash<Symbol, Object>] the options.
56
+ # @yieldparam node [Nokogiri::XML::Schematron::Nodes::Reject] the +Nodes::Reject+ object.
57
+ # @yieldreturn [void]
58
+ # @return [Nokogiri::XML::Schematron::Nodes::Reject] the +Nodes::Reject+ object.
59
+ # @raise [ArgumentError] if the XPath is +nil+.
60
+ def reject(context, **options, &block)
61
+ node = Nokogiri::XML::Schematron::Nodes::Reject.new(self, context, **options, &block)
62
+ @child_node_by_context[context] = node
63
+ node
64
+ end
65
+
66
+ # Create a new +Nodes::Require+ object.
67
+ #
68
+ # @param context [String] the XPath.
69
+ # @param options [Hash<Symbol, Object>] the options.
70
+ # @yieldparam node [Nokogiri::XML::Schematron::Nodes::Require] the +Nodes::Require+ object.
71
+ # @yieldreturn [void]
72
+ # @return [Nokogiri::XML::Schematron::Nodes::Require] the +Nodes::Require+ object.
73
+ # @raise [ArgumentError] if the XPath is +nil+.
74
+ def require(context, **options, &block)
75
+ node = Nokogiri::XML::Schematron::Nodes::Require.new(self, context, **options, &block)
76
+ @child_node_by_context[context] = node
77
+ node
78
+ end
79
+
80
+ # Create a new +Nodes::Validations::Exclusion+ object.
81
+ #
82
+ # @param context [String] the XPath.
83
+ # @param options [Hash<Symbol, Object>] the options.
84
+ # @option options [Array<String>] :in ([]) the array of available items.
85
+ # @yieldparam node [Nokogiri::XML::Schematron::Nodes::Validations::Exclusion] the +Nodes::Validations::Exclusion+ object.
86
+ # @yieldreturn [void]
87
+ # @return [Nokogiri::XML::Schematron::Nodes::Validations::Exclusion] the +Nodes::Validations::Exclusion+ object.
88
+ def validates_exclusion_of(context, **options, &block)
89
+ node = Nokogiri::XML::Schematron::Nodes::Validations::Exclusion.new(self, context, **options, &block)
90
+ @child_node_by_context[context] = node
91
+ node
92
+ end
93
+
94
+ # Create a new +Nodes::Validations::Inclusion+ object.
95
+ #
96
+ # @param context [String] the XPath.
97
+ # @param options [Hash<Symbol, Object>] the options.
98
+ # @option options [Array<String>] :in ([]) the array of available items.
99
+ # @yieldparam node [Nokogiri::XML::Schematron::Nodes::Validations::Inclusion] the +Nodes::Validations::Inclusion+ object.
100
+ # @yieldreturn [void]
101
+ # @return [Nokogiri::XML::Schematron::Nodes::Validations::Inclusion] the +Nodes::Validations::Inclusion+ object.
102
+ def validates_inclusion_of(context, **options, &block)
103
+ node = Nokogiri::XML::Schematron::Nodes::Validations::Inclusion.new(self, context, **options, &block)
104
+ @child_node_by_context[context] = node
105
+ node
106
+ end
107
+
108
+ # Create a new +Nodes::Validations::Numericality+ object.
109
+ #
110
+ # @param context [String] the XPath.
111
+ # @param options [Hash<Symbol, Object>] the options.
112
+ # @option options [Boolean] :even (false) Specifies the value must be an even number.
113
+ # @option options [Integer, Float] :equal_to (nil) Specifies the value must be equal to the supplied value.
114
+ # @option options [Integer, Float] :greater_than (nil) Specifies the value must be greater than the supplied value.
115
+ # @option options [Integer, Float] :greater_than_or_equal_to (nil) Specifies the value must be greater than or equal the supplied value.
116
+ # @option options [Integer, Float] :less_than (nil) Specifies the value must be less than the supplied value.
117
+ # @option options [Integer, Float] :less_than_or_equal_to (nil) Specifies the value must be less than or equal the supplied value.
118
+ # @option options [Boolean] :odd (false) Specifies the value must be an odd number.
119
+ # @option options [Integer, Float] :other_than (nil) Specifies the value must be other than the supplied value.
120
+ # @yieldparam node [Nokogiri::XML::Schematron::Nodes::Validations::Numericality] the +Nodes::Validations::Numericality+ object.
121
+ # @yieldreturn [void]
122
+ # @return [Nokogiri::XML::Schematron::Nodes::Validations::Numericality] the +Nodes::Validations::Numericality+ object.
123
+ def validates_numericality_of(context, **options, &block)
124
+ node = Nokogiri::XML::Schematron::Nodes::Validations::Numericality.new(self, context, **options, &block)
125
+ @child_node_by_context[context] = node
126
+ node
127
+ end
128
+
129
+ protected
130
+
131
+ def build!(xml)
132
+ if @child_node_by_context.any?
133
+ xml["sch"].send(:rule, xmlns.merge({
134
+ "context" => send(:absolute_context),
135
+ })) do
136
+ @child_node_by_context.each_pair.sort_by { |pair| pair[0] }.each do |pair|
137
+ pair[1].send(:build_assertion!, xml)
138
+ end
139
+
140
+ super(xml)
141
+ end
142
+
143
+ @child_node_by_context.each_pair.sort_by { |pair| pair[0] }.each do |pair|
144
+ pair[1].send(:build!, xml)
145
+ end
146
+ end
147
+
148
+ return
149
+ end
150
+
151
+ # Build the XML representation of the +<sch:assert>+ XML element.
152
+ #
153
+ # @param xml [Nokogiri::XML::Builder] the XML builder.
154
+ # @return [void]
155
+ def build_assertion!(xml)
156
+ return
157
+ end
158
+
159
+ private
160
+
161
+ # @return [String] the absolute XPath with respect to the abstract syntax tree.
162
+ def absolute_context
163
+ return @context unless @parent.is_a?(Nokogiri::XML::Schematron::Nodes::Base)
164
+
165
+ prefix = @parent.send(:absolute_context)
166
+
167
+ separator = \
168
+ case prefix
169
+ when "/" then ""
170
+ else "/"
171
+ end
172
+
173
+ [
174
+ prefix,
175
+ @context,
176
+ ].join(separator)
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ require "nokogiri/xml/schematron/nodes/permit"
185
+ require "nokogiri/xml/schematron/nodes/reject"
186
+ require "nokogiri/xml/schematron/nodes/require"
187
+ require "nokogiri/xml/schematron/nodes/validations/exclusion"
188
+ require "nokogiri/xml/schematron/nodes/validations/inclusion"
189
+ require "nokogiri/xml/schematron/nodes/validations/numericality"
@@ -0,0 +1,13 @@
1
+ require "nokogiri/xml/schematron/nodes/base"
2
+
3
+ module Nokogiri
4
+ module XML
5
+ module Schematron
6
+ module Nodes
7
+ # The abstract syntax tree node whose +@context+ XML attribute is the root.
8
+ class Context < Nokogiri::XML::Schematron::Nodes::Base
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ require "nokogiri/xml/schematron/nodes/base"
2
+
3
+ module Nokogiri
4
+ module XML
5
+ module Schematron
6
+ module Nodes
7
+ # The abstract syntax tree node whose +@context+ XML attribute is OPTIONAL.
8
+ class Permit < Nokogiri::XML::Schematron::Nodes::Base
9
+ protected
10
+
11
+ def build_assertion!(xml)
12
+ xml["sch"].send(:assert, xmlns.merge({
13
+ "test" => "count(#{@context}) >= 0",
14
+ })) do
15
+ xml.text("element \"#{@context}\" is OPTIONAL")
16
+ end
17
+
18
+ return
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ require "nokogiri/xml/schematron/nodes/base"
2
+
3
+ module Nokogiri
4
+ module XML
5
+ module Schematron
6
+ module Nodes
7
+ # The abstract syntax tree node whose +@context+ XML attribute is NOT RECOMMENDED.
8
+ class Reject < Nokogiri::XML::Schematron::Nodes::Base
9
+ protected
10
+
11
+ def build_assertion!(xml)
12
+ xml["sch"].send(:assert, xmlns.merge({
13
+ "test" => "not(#{@context})",
14
+ })) do
15
+ xml.text("element \"#{@context}\" is NOT RECOMMENDED")
16
+ end
17
+
18
+ return
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ require "nokogiri/xml/schematron/nodes/base"
2
+
3
+ module Nokogiri
4
+ module XML
5
+ module Schematron
6
+ module Nodes
7
+ # The abstract syntax tree node whose +@context+ XML attribute is REQUIRED.
8
+ class Require < Nokogiri::XML::Schematron::Nodes::Base
9
+ protected
10
+
11
+ def build_assertion!(xml)
12
+ xml["sch"].send(:assert, xmlns.merge({
13
+ "test" => "count(#{@context}) >= 1",
14
+ })) do
15
+ xml.text("element \"#{@context}\" is REQUIRED")
16
+ end
17
+
18
+ return
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,51 @@
1
+ require "nokogiri/xml/schematron/internal/core_ext/array"
2
+ require "nokogiri/xml/schematron/nodes/base"
3
+
4
+ module Nokogiri
5
+ module XML
6
+ module Schematron
7
+ module Nodes
8
+ module Validations
9
+ # The abstract syntax tree node that uses the +contains(haystack, needle)+
10
+ # XPath function, where +haystack+ and +needle+ are strings, to test
11
+ # if the +@context+ XML attribute is not included in an array of
12
+ # available items.
13
+ #
14
+ # For the supplied array of available items
15
+ #
16
+ # ["a", "b", "c"]
17
+ #
18
+ # and context
19
+ #
20
+ # "/x/y/z/text()"
21
+ #
22
+ # this abstract syntax tree node is equivalent to the XPath:
23
+ #
24
+ # "not(contains('_a_ _b_ _c_', concat('_', /x/y/z/text(), '_')))"
25
+ #
26
+ class Exclusion < Nokogiri::XML::Schematron::Nodes::Base
27
+ # @!attribute [rw] in
28
+ # @return [Array<String>] the array of available items.
29
+ attribute :in
30
+
31
+ protected
32
+
33
+ def build_assertion!(xml)
34
+ xml["sch"].send(:assert, xmlns.merge({
35
+ "test" => "not(contains('#{(send(:in) || []).collect { |s| "_#{s.gsub("'", "\\'")}_" }.join(" ")}', concat('_', #{@context}, '_')))",
36
+ })) do
37
+ xml.text("text \"")
38
+ xml["sch"].send(:"value-of", {
39
+ "select" => @context,
40
+ })
41
+ xml.text("\": element \"#{@context}\" MUST NOT be #{Nokogiri::XML::Schematron::Internal::CoreExt::Array.to_sentence((send(:in) || []).collect { |s| "\"#{s.gsub("\"", "\\\"")}\"" }, last_word_connector: ", or ", two_words_connector: " or ", words_connector: ", ")}")
42
+ end
43
+
44
+ return
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,51 @@
1
+ require "nokogiri/xml/schematron/internal/core_ext/array"
2
+ require "nokogiri/xml/schematron/nodes/base"
3
+
4
+ module Nokogiri
5
+ module XML
6
+ module Schematron
7
+ module Nodes
8
+ module Validations
9
+ # The abstract syntax tree node that uses the +contains(haystack, needle)+
10
+ # XPath function, where +haystack+ and +needle+ are strings, to test
11
+ # if the +@context+ XML attribute is included in an array of available
12
+ # items.
13
+ #
14
+ # For the supplied array of available items
15
+ #
16
+ # ["a", "b", "c"]
17
+ #
18
+ # and context
19
+ #
20
+ # "/x/y/z/text()"
21
+ #
22
+ # this abstract syntax tree node is equivalent to the XPath:
23
+ #
24
+ # "contains('_a_ _b_ _c_', concat('_', /x/y/z/text(), '_'))"
25
+ #
26
+ class Inclusion < Nokogiri::XML::Schematron::Nodes::Base
27
+ # @!attribute [rw] in
28
+ # @return [Array<String>] the array of available items.
29
+ attribute :in
30
+
31
+ protected
32
+
33
+ def build_assertion!(xml)
34
+ xml["sch"].send(:assert, xmlns.merge({
35
+ "test" => "contains('#{(send(:in) || []).collect { |s| "_#{s.gsub("'", "\\'")}_" }.join(" ")}', concat('_', #{@context}, '_'))",
36
+ })) do
37
+ xml.text("text \"")
38
+ xml["sch"].send(:"value-of", {
39
+ "select" => @context,
40
+ })
41
+ xml.text("\": element \"#{@context}\" MUST be #{Nokogiri::XML::Schematron::Internal::CoreExt::Array.to_sentence((send(:in) || []).collect { |s| "\"#{s.gsub("\"", "\\\"")}\"" }, last_word_connector: ", or ", two_words_connector: " or ", words_connector: ", ")}")
42
+ end
43
+
44
+ return
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,142 @@
1
+ require "nokogiri/xml/schematron/internal/core_ext/array"
2
+ require "nokogiri/xml/schematron/nodes/base"
3
+
4
+ module Nokogiri
5
+ module XML
6
+ module Schematron
7
+ module Nodes
8
+ module Validations
9
+ # The abstract syntax tree node that uses the +number(any)+
10
+ # XPath function, where +any+ is any value, to test if the +@context+
11
+ # XML attribute is numeric.
12
+ class Numericality < Nokogiri::XML::Schematron::Nodes::Base
13
+ # @!attribute [rw] even
14
+ # @return [Boolean] the value must be an even number.
15
+ attribute :even
16
+
17
+ # @!attribute [rw] equal_to
18
+ # @return [Integer, Float] the value must be equal to the supplied value.
19
+ attribute :equal_to
20
+
21
+ # @!attribute [rw] greater_than
22
+ # @return [Integer, Float] the value must be greater than the supplied value.
23
+ attribute :greater_than
24
+
25
+ # @!attribute [rw] greater_than_or_equal_to
26
+ # @return [Integer, Float] the value must be greater than or equal the supplied value.
27
+ attribute :greater_than_or_equal_to
28
+
29
+ # @!attribute [rw] less_than
30
+ # @return [Integer, Float] the value must be less than the supplied value.
31
+ attribute :less_than
32
+
33
+ # @!attribute [rw] less_than_or_equal_to
34
+ # @return [Integer, Float] the value must be less than or equal the supplied value.
35
+ attribute :less_than_or_equal_to
36
+
37
+ # @!attribute [rw] odd
38
+ # @return [Boolean] the value must be an odd number.
39
+ attribute :odd
40
+
41
+ # @!attribute [rw] other_than
42
+ # @return [Integer, Float] the value must be other than the supplied value.
43
+ attribute :other_than
44
+
45
+ protected
46
+
47
+ def build_assertion!(xml)
48
+ tests = [
49
+ "number(#{@context}) = #{@context}",
50
+ ]
51
+
52
+ messages = [
53
+ "MUST be a number",
54
+ ]
55
+
56
+ PROC_BY_METHOD_NAME.each do |method_name, block|
57
+ unless (orig_value = send(method_name.to_sym)).nil? || (pair = block.call(orig_value)).nil?
58
+ tests << pair[0]
59
+ messages << pair[1]
60
+ end
61
+ end
62
+
63
+ xml["sch"].send(:assert, xmlns.merge({
64
+ "test" => tests.collect { |s| "(#{s})" }.join(" and "),
65
+ })) do
66
+ xml.text("text \"")
67
+ xml["sch"].send(:"value-of", {
68
+ "select" => @context,
69
+ })
70
+ xml.text("\": element \"#{@context}\" #{Nokogiri::XML::Schematron::Internal::CoreExt::Array.to_sentence(messages, last_word_connector: ", and ", two_words_connector: " and ", words_connector: ", ")}")
71
+ end
72
+
73
+ return
74
+ end
75
+
76
+ private
77
+
78
+ # @return [Hash<Symbol, Proc>] the hash of blocks by method name.
79
+ PROC_BY_METHOD_NAME = {
80
+ even: ::Proc.new { |bool|
81
+ if bool == true
82
+ [
83
+ "number(#{@context}) mod 2 = 0",
84
+ "MUST be even",
85
+ ]
86
+ else
87
+ nil
88
+ end
89
+ },
90
+ equal_to: ::Proc.new { |number|
91
+ [
92
+ "number(#{@context}) = #{number}",
93
+ "MUST be equal to #{number}",
94
+ ]
95
+ },
96
+ greater_than: ::Proc.new { |number|
97
+ [
98
+ "number(#{@context}) > #{number}",
99
+ "MUST be greater than #{number}",
100
+ ]
101
+ },
102
+ greater_than_or_equal_to: ::Proc.new { |number|
103
+ [
104
+ "number(#{@context}) >= #{number}",
105
+ "MUST be greater than or equal to #{number}",
106
+ ]
107
+ },
108
+ less_than: ::Proc.new { |number|
109
+ [
110
+ "number(#{@context}) < #{number}",
111
+ "MUST be less than #{number}",
112
+ ]
113
+ },
114
+ less_than_or_equal_to: ::Proc.new { |number|
115
+ [
116
+ "number(#{@context}) <= #{number}",
117
+ "MUST be less than or equal to #{number}",
118
+ ]
119
+ },
120
+ odd: ::Proc.new { |bool|
121
+ if bool == true
122
+ [
123
+ "number(#{@context}) mod 2 = 1",
124
+ "MUST be odd",
125
+ ]
126
+ else
127
+ nil
128
+ end
129
+ },
130
+ other_than: ::Proc.new { |number|
131
+ [
132
+ "number(#{@context}) != #{number}",
133
+ "MUST NOT be equal to #{number}",
134
+ ]
135
+ },
136
+ }
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end