saxon-rb 0.6.0-java → 0.8.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +623 -44
- data/.rspec-jar-loading +2 -0
- data/.ruby-version +1 -1
- data/.yardopts +1 -0
- data/README.md +1 -1
- data/Rakefile +10 -4
- data/docs/templates/plugin.rb +73 -0
- data/lib/saxon-rb.rb +0 -1
- data/lib/saxon/configuration.rb +15 -13
- data/lib/saxon/document_builder.rb +216 -5
- data/lib/saxon/feature_flags.rb +11 -0
- data/lib/saxon/feature_flags/errors.rb +8 -0
- data/lib/saxon/feature_flags/helpers.rb +15 -0
- data/lib/saxon/feature_flags/version.rb +100 -0
- data/lib/saxon/item_type.rb +116 -71
- data/lib/saxon/item_type/lexical_string_conversion.rb +78 -1
- data/lib/saxon/item_type/value_to_ruby.rb +12 -0
- data/lib/saxon/jruby_bug_6197_workaround.rb +5 -0
- data/lib/saxon/loader.rb +56 -43
- data/lib/saxon/processor.rb +6 -4
- data/lib/saxon/s9api.rb +17 -17
- data/lib/saxon/version.rb +7 -1
- data/lib/saxon/version/library.rb +89 -0
- data/lib/saxon/xdm/atomic_value.rb +16 -9
- data/lib/saxon/xdm/node.rb +30 -0
- data/lib/saxon/xpath/compiler.rb +2 -2
- data/lib/saxon/xpath/static_context.rb +6 -1
- data/lib/saxon/xslt/evaluation_context.rb +11 -1
- data/lib/saxon/xslt/executable.rb +14 -2
- data/lib/saxon/xslt/invocation.rb +2 -1
- data/saxon-rb.gemspec +1 -1
- metadata +22 -14
@@ -8,6 +8,7 @@ module Saxon
|
|
8
8
|
# Regexp patterns to assist in converting XDM typed values into Ruby
|
9
9
|
# native types
|
10
10
|
module Patterns
|
11
|
+
# @see https://www.w3.org/TR/xmlschema-2/#dateTime-lexical-representation
|
11
12
|
DATE_TIME = /\A(-?[0-9]{4,})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}(?:\.[0-9]+)?)(Z|[-+][0-9]{2}:[0-9]{2})?\z/
|
12
13
|
end
|
13
14
|
|
@@ -54,34 +55,45 @@ module Saxon
|
|
54
55
|
# A collection of Lambdas (or objects responding to +#call+) for
|
55
56
|
# converting XDM typed values to native Ruby types.
|
56
57
|
module Convertors
|
58
|
+
# @see https://www.w3.org/TR/xmlschema-2/#integer
|
57
59
|
INTEGER = INT = SHORT = LONG = UNSIGNED_INT = UNSIGNED_SHORT = UNSIGNED_LONG = \
|
58
60
|
POSITIVE_INTEGER = NON_POSITIVE_INTEGER = NEGATIVE_INTEGER = NON_NEGATIVE_INTEGER = ->(xdm_atomic_value) {
|
59
61
|
xdm_atomic_value.to_java.getValue
|
60
62
|
}
|
63
|
+
# @see https://www.w3.org/TR/xmlschema-2/#decimal
|
61
64
|
DECIMAL = ->(xdm_atomic_value) {
|
62
65
|
BigDecimal(xdm_atomic_value.to_s)
|
63
66
|
}
|
67
|
+
# @see https://www.w3.org/TR/xmlschema-2/#float
|
64
68
|
FLOAT = DOUBLE = ->(xdm_atomic_value) {
|
65
69
|
xdm_atomic_value.to_java.getValue
|
66
70
|
}
|
71
|
+
# @see https://www.w3.org/TR/xmlschema-2/#dateTime
|
67
72
|
DATE_TIME = DATE_TIME_STAMP = ValueToRuby::DateTimeConvertor
|
73
|
+
# @see https://www.w3.org/TR/xmlschema-2/#boolean
|
68
74
|
BOOLEAN = ->(xdm_atomic_value) {
|
69
75
|
xdm_atomic_value.to_java.getValue
|
70
76
|
}
|
77
|
+
# @see https://www.w3.org/TR/xmlschema-2/#NOTATION
|
78
|
+
# @see https://www.w3.org/TR/xmlschema-2/#QName
|
71
79
|
NOTATION = QNAME = ->(xdm_atomic_value) {
|
72
80
|
Saxon::QName.new(xdm_atomic_value.to_java.getQNameValue)
|
73
81
|
}
|
82
|
+
# @see https://www.w3.org/TR/xmlschema-2/#base64Binary
|
74
83
|
BASE64_BINARY = ->(xdm_atomic_value) {
|
75
84
|
Base64.decode64(xdm_atomic_value.to_s)
|
76
85
|
}
|
86
|
+
# @see https://www.w3.org/TR/xmlschema-2/#hexBinary
|
77
87
|
HEX_BINARY = ->(xdm_atomic_value) {
|
78
88
|
bytes = []
|
79
89
|
xdm_atomic_value.to_s.scan(/../) { |x| bytes << x.hex.chr(Encoding::ASCII_8BIT) }
|
80
90
|
bytes.join
|
81
91
|
}
|
92
|
+
# @see https://www.w3.org/TR/xmlschema-2/#byte
|
82
93
|
BYTE = ->(xdm_atomic_value) {
|
83
94
|
[xdm_atomic_value.to_java.getLongValue].pack('c')
|
84
95
|
}
|
96
|
+
# @see https://www.w3.org/TR/xmlschema-2/#unsignedByte
|
85
97
|
UNSIGNED_BYTE = ->(xdm_atomic_value) {
|
86
98
|
[xdm_atomic_value.to_java.getLongValue].pack('C')
|
87
99
|
}
|
data/lib/saxon/loader.rb
CHANGED
@@ -19,6 +19,7 @@ module Saxon
|
|
19
19
|
@path = path
|
20
20
|
end
|
21
21
|
|
22
|
+
# returns an error message including the missing path
|
22
23
|
def to_s
|
23
24
|
"The path ('#{@path}') you supplied for the Saxon .jar files doesn't exist, sorry"
|
24
25
|
end
|
@@ -31,65 +32,77 @@ module Saxon
|
|
31
32
|
@path = path
|
32
33
|
end
|
33
34
|
|
35
|
+
# returns an error message including the path looked in and jars we were looking for
|
34
36
|
def to_s
|
35
37
|
"One of saxon9he.jar, saxon9pe.jar, or saxon9ee.jar must be present in the path ('#{@path}') you supplied, sorry"
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
LOAD_SEMAPHORE.synchronize do
|
46
|
-
if Saxon::S9API.const_defined?(:Processor)
|
41
|
+
class << self
|
42
|
+
# Are the Saxon jars missing from the Classpath?
|
43
|
+
# @return [Boolean] true if the Jars are not on the Classpath
|
44
|
+
def jars_not_on_classpath?
|
45
|
+
begin
|
46
|
+
Java::net.sf.saxon.s9api.Processor
|
47
47
|
false
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
48
|
+
rescue
|
49
|
+
true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Are the Saxon JARs on the Classpath?
|
54
|
+
# @return [Boolean] whether the Jars are on the Classpath already
|
55
|
+
def jars_on_classpath?
|
56
|
+
!jars_not_on_classpath?
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param saxon_home [String, Pathname] the path to the dir containing
|
60
|
+
# Saxon's .jars. Defaults to the vendored Saxon HE
|
61
|
+
# @return [true, false] Returns true if Saxon had not been loaded and
|
62
|
+
# is now, and false if Saxon was already loaded
|
63
|
+
def load!(saxon_home = nil)
|
64
|
+
return false if instance_variable_defined?(:@saxon_loaded) && @saxon_loaded
|
65
|
+
LOAD_SEMAPHORE.synchronize do
|
66
|
+
if Saxon::S9API.const_defined?(:Processor)
|
67
|
+
false
|
68
|
+
else
|
69
|
+
if jars_not_on_classpath?
|
70
|
+
if saxon_home.nil?
|
71
|
+
require 'saxon-rb_jars'
|
72
|
+
else
|
73
|
+
saxon_home = Pathname.new(saxon_home)
|
74
|
+
raise NoJarsError, saxon_home unless saxon_home.directory?
|
75
|
+
jars = [main_jar(saxon_home)].compact
|
76
|
+
raise MissingJarError if jars.empty?
|
77
|
+
jars += extra_jars(saxon_home)
|
58
78
|
|
59
|
-
|
79
|
+
add_jars_to_classpath!(saxon_home, jars)
|
80
|
+
end
|
60
81
|
end
|
61
|
-
end
|
62
82
|
|
63
|
-
|
64
|
-
|
83
|
+
require_relative 'jruby_bug_6197_workaround'
|
84
|
+
@saxon_loaded = true
|
85
|
+
true
|
86
|
+
end
|
65
87
|
end
|
66
88
|
end
|
67
|
-
end
|
68
89
|
|
69
|
-
|
70
|
-
|
71
|
-
def self.main_jar(path)
|
72
|
-
['saxon9he.jar', 'saxon9pe.jar', 'saxon9ee.jar'].map { |jar| path.join(jar) }.find { |jar| jar.file? }
|
73
|
-
end
|
90
|
+
private
|
74
91
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
([icu] + optional).compact
|
79
|
-
end
|
92
|
+
def main_jar(path)
|
93
|
+
['saxon9he.jar', 'saxon9pe.jar', 'saxon9ee.jar'].map { |jar| path.join(jar) }.find { |jar| jar.file? }
|
94
|
+
end
|
80
95
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
rescue
|
86
|
-
true
|
96
|
+
def extra_jars(path)
|
97
|
+
optional = ['saxon9-unpack.jar', 'saxon9-sql.jar'].map { |jar| path.join(jar) }.select { |jar| jar.file? }
|
98
|
+
icu = path.children.find { |jar| jar.extname == '.jar' && !jar.basename.to_s.match(/^saxon-icu|^icu4j/).nil? }
|
99
|
+
([icu] + optional).compact
|
87
100
|
end
|
88
|
-
end
|
89
101
|
|
90
|
-
|
91
|
-
|
92
|
-
|
102
|
+
def add_jars_to_classpath!(saxon_home, jars)
|
103
|
+
jars.each do |jar|
|
104
|
+
$CLASSPATH << jar.to_s
|
105
|
+
end
|
93
106
|
end
|
94
107
|
end
|
95
108
|
end
|
data/lib/saxon/processor.rb
CHANGED
@@ -46,12 +46,14 @@ module Saxon
|
|
46
46
|
@s9_processor = s9_processor
|
47
47
|
end
|
48
48
|
|
49
|
-
# Generate a new DocumentBuilder that uses this Processor.
|
50
|
-
#
|
49
|
+
# Generate a new DocumentBuilder that uses this Processor. Sharing
|
50
|
+
# DocumentBuilders across threads is not safe.
|
51
51
|
#
|
52
|
+
# @yield A DocumentBuilder configuration DSL block, see
|
53
|
+
# {Saxon::DocumentBuilder.create}
|
52
54
|
# @return [Saxon::DocumentBuilder] A new Saxon::DocumentBuilder
|
53
|
-
def document_builder
|
54
|
-
Saxon::DocumentBuilder.
|
55
|
+
def document_builder(&block)
|
56
|
+
Saxon::DocumentBuilder.create(self, &block)
|
55
57
|
end
|
56
58
|
|
57
59
|
# Declare custom collations for use by XSLT, XPath, and XQuery processors
|
data/lib/saxon/s9api.rb
CHANGED
@@ -11,28 +11,28 @@ module Saxon
|
|
11
11
|
# alternate location for them, if they don't want to use the bundled Saxon
|
12
12
|
# HE
|
13
13
|
def const_missing(name)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
CLASS_IMPORT_SEMAPHORE.synchronize {
|
15
|
+
return const_get(name) if const_defined?(name)
|
16
|
+
Saxon::Loader.load!
|
17
|
+
begin
|
18
|
+
const_set(name, imported_classes.const_get(name))
|
19
|
+
rescue NameError
|
20
|
+
msg = "uninitialized constant Saxon::S9API::#{name}"
|
21
|
+
e = NameError.new(msg, name)
|
22
|
+
raise e
|
23
|
+
end
|
24
|
+
}
|
22
25
|
end
|
23
26
|
|
24
27
|
private
|
25
28
|
|
26
29
|
def imported_classes
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
java_import Java::net.sf.saxon.lib.ParseOptions
|
34
|
-
}
|
35
|
-
end
|
30
|
+
@imported_classes ||= Module.new {
|
31
|
+
include_package 'net.sf.saxon.s9api'
|
32
|
+
java_import Java::net.sf.saxon.Configuration
|
33
|
+
java_import Java::net.sf.saxon.lib.FeatureKeys
|
34
|
+
java_import Java::net.sf.saxon.lib.ParseOptions
|
35
|
+
}
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
data/lib/saxon/version.rb
CHANGED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'saxon/s9api'
|
2
|
+
|
3
|
+
module Saxon
|
4
|
+
module Version
|
5
|
+
# The version of the underlying Saxon library, which we need to discover at
|
6
|
+
# runtime based on what version is on the Classpath
|
7
|
+
class Library
|
8
|
+
# The loaded version of the Saxon Java library
|
9
|
+
#
|
10
|
+
# @return [Saxon::Version::Library] the version of the loaded library
|
11
|
+
def self.loaded_version
|
12
|
+
Saxon::Loader.load!
|
13
|
+
|
14
|
+
sv = Java::net.sf.saxon.Version
|
15
|
+
new(sv.getProductVersion, sv.getStructuredVersionNumber, sv.softwareEdition)
|
16
|
+
end
|
17
|
+
|
18
|
+
include Comparable
|
19
|
+
|
20
|
+
# @return [String] the version string (e.g. '9.9.1.6', '10.0')
|
21
|
+
attr_reader :version
|
22
|
+
# @return [String] the version components (e.g. <tt>[9, 9, 1, 6]</tt>, <tt>[10, 0]</tt>)
|
23
|
+
attr_reader :components
|
24
|
+
# @return [Symbol] the edition (+:he+, +:pe+, or +:ee+)
|
25
|
+
attr_reader :edition
|
26
|
+
|
27
|
+
# @param version [String] the version string
|
28
|
+
# @param components [Array<Integer>] the version components separated
|
29
|
+
# @param edition [String, Symbol] the name of the Saxon edition (e.g. +:he+, +'HE'+)
|
30
|
+
def initialize(version, components, edition)
|
31
|
+
@version = version.dup.freeze
|
32
|
+
@components = components.dup.freeze
|
33
|
+
@edition = edition.downcase.to_sym
|
34
|
+
end
|
35
|
+
|
36
|
+
# Comparison against another instance
|
37
|
+
#
|
38
|
+
# @param other [Saxon::Version::Library] the other version to compare against
|
39
|
+
# @return [Integer] -1 for less, 1 for greater, 0 for equal
|
40
|
+
def <=>(other)
|
41
|
+
return false unless other.is_a?(self.class)
|
42
|
+
|
43
|
+
n_components = [self.components.length, other.components.length].max
|
44
|
+
(0..(n_components - 1)).reduce(0, &comparator(other))
|
45
|
+
end
|
46
|
+
|
47
|
+
# Pessimistic comparison à la rubygems +~>+: do I satisfy the other
|
48
|
+
# version if considered as a pessimistic version constraint
|
49
|
+
#
|
50
|
+
# @param pessimistic_version [Saxon::Version::Library] the version to
|
51
|
+
# compare pessimistically
|
52
|
+
# @return [Boolean] do I satisfy the constraint?
|
53
|
+
def pessimistic_compare(pessimistic_version)
|
54
|
+
pessimistic_components = pessimistic_version.components
|
55
|
+
pessimistic_components = pessimistic_components + [0] if pessimistic_components.length == 1
|
56
|
+
locked = pessimistic_components[0..-2]
|
57
|
+
locked = locked.zip(components[0..locked.length])
|
58
|
+
variable = [pessimistic_components[-1], components[locked.length]]
|
59
|
+
|
60
|
+
locked_ok = locked.all? { |check, mine|
|
61
|
+
check == mine
|
62
|
+
}
|
63
|
+
|
64
|
+
return false unless locked_ok
|
65
|
+
|
66
|
+
check, mine = variable
|
67
|
+
mine >= check
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [String] the version string
|
71
|
+
def to_s
|
72
|
+
version
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def comparator(other)
|
78
|
+
->(cmp, i) {
|
79
|
+
return cmp unless cmp == 0
|
80
|
+
|
81
|
+
mine = self.components[i].nil? ? 0 : self.components[i]
|
82
|
+
theirs = other.components[i].nil? ? 0 : other.components[i]
|
83
|
+
|
84
|
+
mine <=> theirs
|
85
|
+
}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -20,6 +20,7 @@ module Saxon
|
|
20
20
|
# them imlpicitly through the XDM::AtomicValue creation process doesn't really
|
21
21
|
# work. They need to be created explicitly and then handed in to be wrapped.
|
22
22
|
class CannotCreateQNameFromString < StandardError
|
23
|
+
# returns an error message
|
23
24
|
def to_s
|
24
25
|
"QName XDM::AtomicValues must be created using an instance of Saxon::QName, not a string like 'prefix:name': Prefix URI binding is undefined at this point"
|
25
26
|
end
|
@@ -29,17 +30,23 @@ module Saxon
|
|
29
30
|
# isn't a way to create these outside of parsing an XML document within
|
30
31
|
# Saxon, so attempting to do so raises this error.
|
31
32
|
class NotationCannotBeDirectlyCreated < StandardError
|
32
|
-
|
33
|
+
# returns an error message
|
34
|
+
def to_s
|
33
35
|
"xs:NOTATION XDM::AtomicValues cannot be directly created outside of XML parsing."
|
34
36
|
end
|
35
37
|
end
|
36
38
|
|
37
|
-
# ItemType representing QNames
|
38
|
-
XS_QNAME = ItemType.get_type('xs:QName')
|
39
|
-
# ItemType representing NOTATION
|
40
|
-
XS_NOTATION = ItemType.get_type('xs:NOTATION')
|
41
|
-
|
42
39
|
class << self
|
40
|
+
# ItemType representing QNames
|
41
|
+
def xs_qname
|
42
|
+
@xs_qname ||= ItemType.get_type('xs:QName')
|
43
|
+
end
|
44
|
+
|
45
|
+
# ItemType representing NOTATION
|
46
|
+
def xs_notation
|
47
|
+
@xs_notation ||= ItemType.get_type('xs:NOTATION')
|
48
|
+
end
|
49
|
+
|
43
50
|
# Convert a single Ruby value into an XDM::AtomicValue
|
44
51
|
#
|
45
52
|
# If no explicit {ItemType} is passed, the correct type is guessed based
|
@@ -69,8 +76,8 @@ module Saxon
|
|
69
76
|
|
70
77
|
item_type = ItemType.get_type(item_type)
|
71
78
|
|
72
|
-
return new(Saxon::S9API::XdmAtomicValue.new(value.to_java)) if item_type ==
|
73
|
-
raise NotationCannotBeDirectlyCreated if item_type ==
|
79
|
+
return new(Saxon::S9API::XdmAtomicValue.new(value.to_java)) if item_type == xs_qname && value_is_qname?(value)
|
80
|
+
raise NotationCannotBeDirectlyCreated if item_type == xs_notation
|
74
81
|
|
75
82
|
value_lexical_string = item_type.lexical_string(value)
|
76
83
|
new(new_s9_xdm_atomic_value(value_lexical_string, item_type))
|
@@ -89,7 +96,7 @@ module Saxon
|
|
89
96
|
# @return [Saxon::XDM::AtomicValue]
|
90
97
|
def from_lexical_string(value, item_type)
|
91
98
|
item_type = ItemType.get_type(item_type)
|
92
|
-
raise CannotCreateQNameFromString if item_type ==
|
99
|
+
raise CannotCreateQNameFromString if item_type == xs_qname
|
93
100
|
new(new_s9_xdm_atomic_value(value.to_s, item_type))
|
94
101
|
end
|
95
102
|
|
data/lib/saxon/xdm/node.rb
CHANGED
@@ -85,6 +85,36 @@ module Saxon
|
|
85
85
|
def axis_iterator(axis)
|
86
86
|
AxisIterator.new(self, axis)
|
87
87
|
end
|
88
|
+
|
89
|
+
# Use Saxon's naive XDM Node serialisation to serialize the node and its
|
90
|
+
# descendants. Saxon uses a new Serializer with default options to
|
91
|
+
# serialize the node. In particular, if the Node was produced by an XSLT
|
92
|
+
# that used +<xsl:character-map>+ through +<xsl:output>+ to modify the
|
93
|
+
# contents, then they *WILL* *NOT* have been applied.
|
94
|
+
#
|
95
|
+
# +<xsl:output>+ has its effect at serialization time, not at XDM tree
|
96
|
+
# creation time, so it won't be applied until you serialize the document.
|
97
|
+
#
|
98
|
+
# Even then, unless you use a {Serializer} configured with the XSLT's
|
99
|
+
# +<xsl:output>+. We make a properly configured serializer available in
|
100
|
+
# the result of any XSLT transform (a {Saxon::XSLT::Invocation}), e.g.:
|
101
|
+
#
|
102
|
+
# result = xslt.apply_templates(input)
|
103
|
+
# result.to_s
|
104
|
+
# # or
|
105
|
+
# result.serialize('/path/to/output.xml')
|
106
|
+
#
|
107
|
+
# You can also get that {Serializer} directly from {XSLT::Executable},
|
108
|
+
# should you want to use the serialization options from a particular XSLT
|
109
|
+
# to serialize arbitrary XDM values:
|
110
|
+
#
|
111
|
+
# serializer = xslt.serializer
|
112
|
+
# serializer.serialize(an_xdm_value)
|
113
|
+
#
|
114
|
+
# @see http://www.saxonica.com/documentation9.9/index.html#!javadoc/net.sf.saxon.s9api/XdmNode@toString
|
115
|
+
def to_s
|
116
|
+
s9_xdm_node.toString
|
117
|
+
end
|
88
118
|
end
|
89
119
|
end
|
90
120
|
end
|