nokogiri_schematron_builder 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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