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.
@@ -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
  }
@@ -0,0 +1,5 @@
1
+ unless Java::net::sf::saxon::s9api::Xslt30Transformer.instance_methods.include?(:setInitialMode)
2
+ class Java::net::sf::saxon::s9api::Xslt30Transformer
3
+ java_alias :setInitialMode, :setInitialMode, [Java::net::sf::saxon::s9api::QName]
4
+ end
5
+ end
@@ -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
- # @param saxon_home [String, Pathname] the path to the dir containing
40
- # Saxon's .jars. Defaults to the vendored Saxon HE
41
- # @return [true, false] Returns true if Saxon had not been loaded and
42
- # is now, and false if Saxon was already loaded
43
- def self.load!(saxon_home = nil)
44
- return false if instance_variable_defined?(:@saxon_loaded) && @saxon_loaded
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
- else
49
- if jars_not_on_classpath?
50
- if saxon_home.nil?
51
- require 'saxon-rb_jars'
52
- else
53
- saxon_home = Pathname.new(saxon_home)
54
- raise NoJarsError, saxon_home unless saxon_home.directory?
55
- jars = [main_jar(saxon_home)].compact
56
- raise MissingJarError if jars.empty?
57
- jars += extra_jars(saxon_home)
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
- add_jars_to_classpath!(saxon_home, jars)
79
+ add_jars_to_classpath!(saxon_home, jars)
80
+ end
60
81
  end
61
- end
62
82
 
63
- @saxon_loaded = true
64
- true
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
- private
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
- def self.extra_jars(path)
76
- optional = ['saxon9-unpack.jar', 'saxon9-sql.jar'].map { |jar| path.join(jar) }.select { |jar| jar.file? }
77
- icu = path.children.find { |jar| jar.extname == '.jar' && !jar.basename.to_s.match(/^saxon-icu|^icu4j/).nil? }
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
- def self.jars_not_on_classpath?
82
- begin
83
- Java::net.sf.saxon.s9api.Processor
84
- false
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
- def self.add_jars_to_classpath!(saxon_home, jars)
91
- jars.each do |jar|
92
- $CLASSPATH << jar.to_s
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
@@ -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
- # Sharing DocumentBuilders across threads is not recommended/
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.new(@s9_processor.newDocumentBuilder)
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
@@ -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
- Saxon::Loader.load!
15
- begin
16
- const_set(name, imported_classes.const_get(name))
17
- rescue NameError
18
- msg = "uninitialized constant Saxon::S9API::#{name}"
19
- e = NameError.new(msg, name)
20
- raise e
21
- end
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
- return @imported_classes if instance_variable_defined?(:@imported_classes)
28
- CLASS_IMPORT_SEMAPHORE.synchronize do
29
- @imported_classes = Module.new {
30
- include_package 'net.sf.saxon.s9api'
31
- java_import Java::net.sf.saxon.Configuration
32
- java_import Java::net.sf.saxon.lib.FeatureKeys
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
@@ -1,3 +1,9 @@
1
+ require 'saxon/version/library'
2
+
1
3
  module Saxon
2
- VERSION = "0.6.0"
4
+ # Provides the saxon-rb and underlying Saxon library versions
5
+ module Version
6
+ # The version of the saxon-rb gem (not of Saxon itself)
7
+ WRAPPER = "0.8.0"
8
+ end
3
9
  end
@@ -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
- def to_s
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 == XS_QNAME && value_is_qname?(value)
73
- raise NotationCannotBeDirectlyCreated if item_type == XS_NOTATION
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 == XS_QNAME
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
 
@@ -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