saxon-rb 0.4.0-java → 0.7.2-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +429 -42
  3. data/.ruby-version +1 -1
  4. data/.yardopts +1 -0
  5. data/Gemfile +2 -2
  6. data/README.md +358 -10
  7. data/Rakefile +237 -7
  8. data/docs/templates/plugin.rb +73 -0
  9. data/lib/net/sf/saxon/Saxon-HE/{9.9.1-5/Saxon-HE-9.9.1-5.jar → 9.9.1-6/Saxon-HE-9.9.1-6.jar} +0 -0
  10. data/lib/saxon-rb.rb +0 -0
  11. data/lib/{saxon_jars.rb → saxon-rb_jars.rb} +2 -2
  12. data/lib/saxon.rb +13 -0
  13. data/lib/saxon/axis_iterator.rb +8 -1
  14. data/lib/saxon/configuration.rb +16 -13
  15. data/lib/saxon/document_builder.rb +216 -5
  16. data/lib/saxon/feature_flags.rb +11 -0
  17. data/lib/saxon/feature_flags/errors.rb +8 -0
  18. data/lib/saxon/feature_flags/helpers.rb +15 -0
  19. data/lib/saxon/feature_flags/version.rb +100 -0
  20. data/lib/saxon/item_type.rb +129 -89
  21. data/lib/saxon/item_type/lexical_string_conversion.rb +214 -59
  22. data/lib/saxon/item_type/value_to_ruby.rb +25 -0
  23. data/lib/saxon/loader.rb +6 -1
  24. data/lib/saxon/nokogiri.rb +78 -0
  25. data/lib/saxon/occurrence_indicator.rb +32 -3
  26. data/lib/saxon/processor.rb +50 -5
  27. data/lib/saxon/qname.rb +37 -2
  28. data/lib/saxon/s9api.rb +5 -0
  29. data/lib/saxon/sequence_type.rb +131 -0
  30. data/lib/saxon/serializer.rb +3 -137
  31. data/lib/saxon/serializer/destination.rb +80 -0
  32. data/lib/saxon/serializer/object.rb +93 -0
  33. data/lib/saxon/serializer/output_properties.rb +83 -0
  34. data/lib/saxon/source.rb +207 -71
  35. data/lib/saxon/version.rb +7 -1
  36. data/lib/saxon/version/library.rb +89 -0
  37. data/lib/saxon/xdm.rb +7 -0
  38. data/lib/saxon/xdm/array.rb +16 -0
  39. data/lib/saxon/xdm/atomic_value.rb +10 -2
  40. data/lib/saxon/xdm/empty_sequence.rb +13 -0
  41. data/lib/saxon/xdm/external_object.rb +1 -0
  42. data/lib/saxon/xdm/function_item.rb +1 -0
  43. data/lib/saxon/xdm/item.rb +7 -0
  44. data/lib/saxon/xdm/map.rb +38 -0
  45. data/lib/saxon/xdm/node.rb +50 -1
  46. data/lib/saxon/xdm/sequence_like.rb +15 -0
  47. data/lib/saxon/xdm/value.rb +21 -5
  48. data/lib/saxon/xpath.rb +9 -0
  49. data/lib/saxon/xpath/compiler.rb +37 -2
  50. data/lib/saxon/xpath/executable.rb +53 -28
  51. data/lib/saxon/xpath/static_context.rb +25 -40
  52. data/lib/saxon/xpath/variable_declaration.rb +16 -49
  53. data/lib/saxon/xslt.rb +12 -0
  54. data/lib/saxon/xslt/compiler.rb +75 -6
  55. data/lib/saxon/xslt/evaluation_context.rb +30 -4
  56. data/lib/saxon/xslt/executable.rb +206 -29
  57. data/lib/saxon/xslt/invocation.rb +97 -0
  58. data/saxon-rb.gemspec +3 -3
  59. metadata +22 -10
  60. data/saxon.gemspec +0 -30
@@ -5,11 +5,19 @@ module Saxon
5
5
  # A collection of lamba-like objects for converting XDM::AtomicValues into
6
6
  # appropriate Ruby values
7
7
  module ValueToRuby
8
+ # Regexp patterns to assist in converting XDM typed values into Ruby
9
+ # native types
8
10
  module Patterns
11
+ # @see https://www.w3.org/TR/xmlschema-2/#dateTime-lexical-representation
9
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/
10
13
  end
11
14
 
15
+ # Helper class that converts XDM Date/Time strings into something Ruby's {Time} class can
16
+ # handle
12
17
  class DateTimeConvertor
18
+ # Convert an XDM Date/Time value into a native Ruby {Time} object.
19
+ # @return [Time, String] the converted Time, or the original XDM lexical
20
+ # string if conversion isn't possible
13
21
  def self.call(xdm_atomic_value)
14
22
  new(xdm_atomic_value).convert
15
23
  end
@@ -21,11 +29,15 @@ module Saxon
21
29
  @match = ValueToRuby::Patterns::DATE_TIME.match(@timestring)
22
30
  end
23
31
 
32
+ # Return a {Time} instance if possible, otherwise return the string value from the XDM.
33
+ # @return [Time, String] the converted Time, or the original string
24
34
  def convert
25
35
  return timestring if match.nil?
26
36
  Time.new(*integer_parts, decimal_part, tz_part)
27
37
  end
28
38
 
39
+ private
40
+
29
41
  def integer_parts
30
42
  match[1..5].map { |n| Integer(n, 10) }
31
43
  end
@@ -40,35 +52,48 @@ module Saxon
40
52
  end
41
53
  end
42
54
 
55
+ # A collection of Lambdas (or objects responding to +#call+) for
56
+ # converting XDM typed values to native Ruby types.
43
57
  module Convertors
58
+ # @see https://www.w3.org/TR/xmlschema-2/#integer
44
59
  INTEGER = INT = SHORT = LONG = UNSIGNED_INT = UNSIGNED_SHORT = UNSIGNED_LONG = \
45
60
  POSITIVE_INTEGER = NON_POSITIVE_INTEGER = NEGATIVE_INTEGER = NON_NEGATIVE_INTEGER = ->(xdm_atomic_value) {
46
61
  xdm_atomic_value.to_java.getValue
47
62
  }
63
+ # @see https://www.w3.org/TR/xmlschema-2/#decimal
48
64
  DECIMAL = ->(xdm_atomic_value) {
49
65
  BigDecimal(xdm_atomic_value.to_s)
50
66
  }
67
+ # @see https://www.w3.org/TR/xmlschema-2/#float
51
68
  FLOAT = DOUBLE = ->(xdm_atomic_value) {
52
69
  xdm_atomic_value.to_java.getValue
53
70
  }
71
+ # @see https://www.w3.org/TR/xmlschema-2/#dateTime
54
72
  DATE_TIME = DATE_TIME_STAMP = ValueToRuby::DateTimeConvertor
73
+ # @see https://www.w3.org/TR/xmlschema-2/#boolean
55
74
  BOOLEAN = ->(xdm_atomic_value) {
56
75
  xdm_atomic_value.to_java.getValue
57
76
  }
77
+ # @see https://www.w3.org/TR/xmlschema-2/#NOTATION
78
+ # @see https://www.w3.org/TR/xmlschema-2/#QName
58
79
  NOTATION = QNAME = ->(xdm_atomic_value) {
59
80
  Saxon::QName.new(xdm_atomic_value.to_java.getQNameValue)
60
81
  }
82
+ # @see https://www.w3.org/TR/xmlschema-2/#base64Binary
61
83
  BASE64_BINARY = ->(xdm_atomic_value) {
62
84
  Base64.decode64(xdm_atomic_value.to_s)
63
85
  }
86
+ # @see https://www.w3.org/TR/xmlschema-2/#hexBinary
64
87
  HEX_BINARY = ->(xdm_atomic_value) {
65
88
  bytes = []
66
89
  xdm_atomic_value.to_s.scan(/../) { |x| bytes << x.hex.chr(Encoding::ASCII_8BIT) }
67
90
  bytes.join
68
91
  }
92
+ # @see https://www.w3.org/TR/xmlschema-2/#byte
69
93
  BYTE = ->(xdm_atomic_value) {
70
94
  [xdm_atomic_value.to_java.getLongValue].pack('c')
71
95
  }
96
+ # @see https://www.w3.org/TR/xmlschema-2/#unsignedByte
72
97
  UNSIGNED_BYTE = ->(xdm_atomic_value) {
73
98
  [xdm_atomic_value.to_java.getLongValue].pack('C')
74
99
  }
@@ -6,8 +6,11 @@ module Saxon
6
6
  module S9API
7
7
  end
8
8
 
9
+ # The mechanism for adding the JARs for either the bundled Saxon HE, or an
10
+ # external Saxon HE/PE/EE version, into the CLASSPATH and requiring them.
9
11
  module Loader
10
12
  LOAD_SEMAPHORE = Mutex.new
13
+ private_constant :LOAD_SEMAPHORE
11
14
 
12
15
  # Error raised if Saxon::Loader.load! is called but the path handed
13
16
  # in does not exist or is not a directory
@@ -16,6 +19,7 @@ module Saxon
16
19
  @path = path
17
20
  end
18
21
 
22
+ # returns an error message including the missing path
19
23
  def to_s
20
24
  "The path ('#{@path}') you supplied for the Saxon .jar files doesn't exist, sorry"
21
25
  end
@@ -28,6 +32,7 @@ module Saxon
28
32
  @path = path
29
33
  end
30
34
 
35
+ # returns an error message including the path looked in and jars we were looking for
31
36
  def to_s
32
37
  "One of saxon9he.jar, saxon9pe.jar, or saxon9ee.jar must be present in the path ('#{@path}') you supplied, sorry"
33
38
  end
@@ -45,7 +50,7 @@ module Saxon
45
50
  else
46
51
  if jars_not_on_classpath?
47
52
  if saxon_home.nil?
48
- require 'saxon_jars'
53
+ require 'saxon-rb_jars'
49
54
  else
50
55
  saxon_home = Pathname.new(saxon_home)
51
56
  raise NoJarsError, saxon_home unless saxon_home.directory?
@@ -0,0 +1,78 @@
1
+ require 'saxon/xslt'
2
+
3
+ module Saxon
4
+ module XSLT
5
+ class Executable
6
+ # Provided for Nokogiri API compatibility. Cannot use many XSLT 2 and 3
7
+ # features as a result.
8
+ #
9
+ # Transform the input document by applying templates as in XSLT 1. All
10
+ # parameters will be interpreted as Strings
11
+ #
12
+ # @param doc_node [Saxon::XDM::Node] the document to transform
13
+ # @param params [Hash, Array] a Hash of param name => value, or an Array
14
+ # of param names and values of the form +['param', 'value', 'param2',
15
+ # 'value']+. The array must be of an even-numbered length
16
+ # @return [Saxon::XDM::Value] the result document
17
+ def transform(doc_node, params = {})
18
+ apply_templates(doc_node, v1_parameters(params)).xdm_value
19
+ end
20
+
21
+ # Provided for Nokogiri API compatibility. Cannot use many XSLT 2 and 3
22
+ # features as a result.
23
+ #
24
+ # Transform the input document by applying templates as in XSLT 1, and
25
+ # then serializing the result to a string using the serialization options
26
+ # set in the XSLT stylesheet's +<xsl:output/>+.
27
+ #
28
+ # @param doc_node [Saxon::XDM::Node] the document to transform
29
+ # @param params [Hash, Array] a Hash of param name => value, or an Array
30
+ # of param names and values of the form +['param', 'value', 'param2',
31
+ # 'value']+. The array must be of an even-numbered length
32
+ # @return [String] a serialization of the the result document
33
+ def apply_to(doc_node, params = {})
34
+ apply_templates(doc_node, v1_parameters(params)).to_s
35
+ end
36
+
37
+ # Provided for Nokogiri API compatibility. Cannot use many XSLT 2 and 3
38
+ # features as a result.
39
+ #
40
+ # Serialize the input document to a string using the serialization options
41
+ # set in the XSLT stylesheet's +<xsl:output/>+.
42
+ #
43
+ # @param doc_node [Saxon::XDM::Node] the document to serialize
44
+ # @return [String] a serialization of the the input document
45
+ def serialize(doc_node)
46
+ s9_transformer = @s9_xslt_executable.load30
47
+ serializer = Saxon::Serializer::Object.new(s9_transformer.newSerializer)
48
+ serializer.serialize(doc_node.to_java)
49
+ end
50
+
51
+ private
52
+
53
+ def v1_parameters(params = [])
54
+ v1_params = v1_params_hash(params).map { |qname, value|
55
+ [Saxon::QName.resolve(qname), Saxon::XDM.AtomicValue(v1_param_value(value))]
56
+ }.to_h
57
+ return {} if v1_params.empty?
58
+ {global_parameters: v1_params}
59
+ end
60
+
61
+ def v1_params_hash(params = [])
62
+ return params if params.is_a?(Hash)
63
+ params.each_slice(2).map { |k,v|
64
+ raise ArgumentError.new("Odd number of values passed as params: #{params}") if v.nil?
65
+ [k, v]
66
+ }.to_h
67
+ end
68
+
69
+ def v1_param_value(value)
70
+ if /\A(['"]).+\1\z/.match(value)
71
+ value.slice(1..-2)
72
+ else
73
+ value
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,32 +1,61 @@
1
1
  require_relative 's9api'
2
2
 
3
3
  module Saxon
4
- # Provides simple access to Saxon's OccurrenceIndicator constants,
5
- # for use in declaring variable types
4
+ # Provides simple access to Saxon's OccurrenceIndicator constants, for
5
+ # declaring restrictions on the cardinality (length) of a sequence when, for
6
+ # example, defining a variable's type
6
7
  module OccurrenceIndicator
7
8
  class << self
9
+ # One thing
8
10
  def one
9
11
  @one ||= Saxon::S9API::OccurrenceIndicator::ONE
10
12
  end
11
13
 
14
+ # One or more things
12
15
  def one_or_more
13
16
  @one_or_more ||= Saxon::S9API::OccurrenceIndicator::ONE_OR_MORE
14
17
  end
15
18
 
19
+ # no things (the empty sequence)
16
20
  def zero
17
21
  @zero ||= Saxon::S9API::OccurrenceIndicator::ZERO
18
22
  end
19
23
 
24
+ # zero or more things
20
25
  def zero_or_more
21
26
  @zero_or_more ||= Saxon::S9API::OccurrenceIndicator::ZERO_OR_MORE
22
27
  end
23
28
 
29
+ # an optional thing
24
30
  def zero_or_one
25
31
  @zero_or_one ||= Saxon::S9API::OccurrenceIndicator::ZERO_OR_ONE
26
32
  end
27
33
 
34
+ # The list of valid occurence indicator names, as symbols. These
35
+ # correspond directly to the methods returning OccurrenceIndicators in
36
+ # this module.
37
+ #
38
+ # @return [Array<Symbol>] the indicator names
28
39
  def indicator_names
29
- @indicator_names ||= (public_methods(false) - Object.public_methods - [:indicator_names])
40
+ # .refine gets added to modules that have methods, so it's not in
41
+ # Module's public_methods list
42
+ @indicator_names ||= (public_methods(false) - Module.public_methods - [:indicator_names, :get_indicator, :refine])
43
+ end
44
+
45
+ # Return an OccurrenceIndicator given a name as a symbol. Passes through
46
+ # existing OccurrenceIndicator instances: this method is primarily for API
47
+ # use, most people will find it easier to directly call one of the
48
+ # methods, as in +OccurrenceIndicator.one+ rather than
49
+ # +OccurrenceIndicator.get_indicator(:one)+.
50
+ #
51
+ # @param indicator_name [Symbol, Saxon::S9API::OccurrenceIndicator] the name of the OccurrenceIndicator to return
52
+ # @return [Saxon::S9API::OccurrenceIndicator] the OccurrenceIndicator
53
+ def get_indicator(indicator_name)
54
+ return indicator_name if indicator_name.is_a?(Saxon::S9API::OccurrenceIndicator)
55
+ unless indicator_names.include?(indicator_name)
56
+ raise ArgumentError, "#{indicator_name.inspect} is not a valid indicator name (one of #{indicator_names.map(&:inspect).join(', ')})"
57
+ end
58
+ OccurrenceIndicator.send(indicator_name)
30
59
  end
31
60
  end
32
61
  end
@@ -4,6 +4,7 @@ require 'saxon/configuration'
4
4
  require 'saxon/document_builder'
5
5
  require 'saxon/xpath'
6
6
  require 'saxon/xslt'
7
+ require 'saxon/serializer'
7
8
 
8
9
  module Saxon
9
10
  # Saxon::Processor wraps the S9API::Processor object. This is the object
@@ -45,12 +46,14 @@ module Saxon
45
46
  @s9_processor = s9_processor
46
47
  end
47
48
 
48
- # Generate a new DocumentBuilder that uses this Processor.
49
- # Sharing DocumentBuilders across threads is not recommended/
49
+ # Generate a new DocumentBuilder that uses this Processor. Sharing
50
+ # DocumentBuilders across threads is not safe.
50
51
  #
52
+ # @yield A DocumentBuilder configuration DSL block, see
53
+ # {Saxon::DocumentBuilder.create}
51
54
  # @return [Saxon::DocumentBuilder] A new Saxon::DocumentBuilder
52
- def document_builder
53
- Saxon::DocumentBuilder.new(@s9_processor.newDocumentBuilder)
55
+ def document_builder(&block)
56
+ Saxon::DocumentBuilder.create(self, &block)
54
57
  end
55
58
 
56
59
  # Declare custom collations for use by XSLT, XPath, and XQuery processors
@@ -76,12 +79,23 @@ module Saxon
76
79
  # <tt>Processor</tt>. Sharing <tt>XSLT::Compiler</tt>s across threads is
77
80
  # fine as long as the static context is not changed.
78
81
  #
79
- # @yield An XPath compiler DSL block, see {Saxon::XSLT::Compiler.create}
82
+ # @yield An XSLT compiler DSL block, see {Saxon::XSLT::Compiler.create}
80
83
  # @return [Saxon::XSLT::Compiler] a new XSLT compiler
81
84
  def xslt_compiler(&block)
82
85
  Saxon::XSLT::Compiler.create(self, &block)
83
86
  end
84
87
 
88
+ # Generate a new +Serializer+ for directly serializing XDM Values that uses
89
+ # this +Processor+. +Serializer+s are effectively one-shot objects, and
90
+ # shouldn't be reused.
91
+ #
92
+ # @yield the block passed will be called bound to the serializer instance. See
93
+ # {Saxon::Serializer::Object.create}
94
+ # @return [Saxon::Serializer::Object]
95
+ def serializer(&block)
96
+ Saxon::Serializer::Object.create(self, &block)
97
+ end
98
+
85
99
  # @return [net.sf.saxon.s9api.Processor] The underlying Saxon processor
86
100
  def to_java
87
101
  @s9_processor
@@ -98,5 +112,36 @@ module Saxon
98
112
  def config
99
113
  @config ||= Saxon::Configuration.create(self)
100
114
  end
115
+
116
+ # Create a {DocumentBuilder} and construct a {Source} and parse some XML.
117
+ # The args are passed to {Saxon::Source.create}, and the returned {Source}
118
+ # is parsed using {DocumentBuilder#build}. If a {Source} is passed in, parse
119
+ # that. Any options in +opts+ will be ignored in that case.
120
+ #
121
+ # @param input [Saxon::Source, IO, File, String, Pathname, URI] the input to
122
+ # be turned into a {Source} and parsed.
123
+ # @param opts [Hash] for Source creation. See {Saxon::Source.create}.
124
+ # @return [Saxon::XDM::Node] the XML document
125
+ def XML(input, opts = {})
126
+ source = Source.create(input, opts)
127
+ document_builder.build(source)
128
+ end
129
+
130
+ # Construct a {Source} containing an XSLT stylesheet, create an
131
+ # {XSLT::Compiler}, and compile the source, returning the {XSLT::Executable}
132
+ # produced. If a {Source} is passed as +input+, then it will be passed
133
+ # through to the compiler and any source-related options in +opts+ will be
134
+ # ignored.
135
+ #
136
+ # @param input [Saxon::Source, IO, File, String, Pathname, URI] the input to
137
+ # be turned into a {Source} and parsed.
138
+ # @param opts [Hash] for Source creation. See {Saxon::Source.create}.
139
+ # @yield the block is executed as an {XSLT::EvaluationContext::DSL} instance
140
+ # and applied to the compiler
141
+ # @return [Saxon::XSLT::Executable] the XSLT Executable
142
+ def XSLT(input, opts = {}, &block)
143
+ source = Source.create(input, opts)
144
+ xslt_compiler(&block).compile(source)
145
+ end
101
146
  end
102
147
  end
@@ -3,16 +3,39 @@ require 'saxon/s9api'
3
3
  module Saxon
4
4
  # Represents QNames
5
5
  class QName
6
+ # Create a {QName} from a Clark-notation string.
7
+ #
8
+ # Clark-notation for QNames uses +{}+ to delimit the namespace, so for a
9
+ # QName not in a namespace it's simply +local-name+, and for one in a
10
+ # namespace it's +{http://example.org/ns}local-name+
11
+ #
12
+ # @param clark_string [String] A QName in Clark notation.
13
+ # @return [Saxon::QName] A QName
6
14
  def self.clark(clark_string)
7
15
  s9_qname = Saxon::S9API::QName.fromClarkName(clark_string)
8
16
  new(s9_qname)
9
17
  end
10
18
 
19
+ # Create a {QName} from an Expanded QName string.
20
+ #
21
+ # Expanded QNames uses +Q{}+ to delimit the namespace, so for a
22
+ # QName not in a namespace it's simply +Q{}local-name+ (or +local-name}), and for one in a
23
+ # namespace it's +Q{http://example.org/ns}local-name+
24
+ #
25
+ # @param eqname_string [String] A QName in Expanded QName notation.
26
+ # @return [Saxon::QName] A QName
11
27
  def self.eqname(eqname_string)
12
28
  s9_qname = Saxon::S9API::QName.fromEQName(eqname_string)
13
29
  new(s9_qname)
14
30
  end
15
31
 
32
+ # Create a {QName} from prefix, uri, and local name options
33
+ #
34
+ # @param opts [Hash]
35
+ # @option [String] :prefix the namespace prefix to use (optional, requires +:uri+ passed too)
36
+ # @option [String] :uri the namespace URI to use (only required for QNames in a namespace)
37
+ # @option [String] :local_name the local-part of the QName. Required.
38
+ # @return [Saxon::QName] the QName
16
39
  def self.create(opts = {})
17
40
  prefix = opts[:prefix]
18
41
  uri = opts[:uri]
@@ -32,7 +55,7 @@ module Saxon
32
55
  # it's an instance of the underlying Saxon Java QName, it'll be wrapped
33
56
  # into a {Saxon::QName}
34
57
  #
35
- # If the arg is a string, it's resolved by using {resolve_variable_name}
58
+ # If the arg is a string, it's resolved by using {resolve_qname_string}
36
59
  #
37
60
  # @param qname_or_string [String, Symbol, Saxon::QName] the qname to resolve
38
61
  # @param namespaces [Hash<String => String>] the set of namespaces as a hash of <tt>"prefix" => "namespace-uri"</tt>
@@ -48,7 +71,7 @@ module Saxon
48
71
  end
49
72
  end
50
73
 
51
- # Resolve a QName string of the form <tt>"prefix:local-name"</tt> into a
74
+ # Resolve a QName string of the form +"prefix:local-name"+ into a
52
75
  # {Saxon::QName} by looking up the namespace URI in a hash of
53
76
  # <tt>"prefix" => "namespace-uri"</tt>
54
77
  #
@@ -110,12 +133,21 @@ module Saxon
110
133
  @s9_qname.getEQName
111
134
  end
112
135
 
136
+ # Compare this QName with another. They compare equal if they have same URI
137
+ # and local name. Prefix is ignored.
138
+ #
139
+ # @param other [Saxon::QName] the QName to compare against
140
+ # @return [Boolean] whether the two compare equal
113
141
  def ==(other)
114
142
  return false unless other.is_a?(QName)
115
143
  s9_qname.equals(other.to_java)
116
144
  end
117
145
  alias_method :eql?, :==
118
146
 
147
+ # Compute a hash-code for this {QName}.
148
+ #
149
+ # Two {QNames}s with the same local name and URI will have the same hash code (and will compare using eql?).
150
+ # @see Object#hash
119
151
  def hash
120
152
  @hash ||= (local_name + uri).hash
121
153
  end
@@ -134,6 +166,8 @@ module Saxon
134
166
  s9_qname.to_s
135
167
  end
136
168
 
169
+ # Returns a more detailed string representation of the object, showing
170
+ # prefix, uri, and local_name instance variables
137
171
  def inspect
138
172
  "<Saxon::QName @prefix=#{prefix} @uri=#{uri} @local_name=#{local_name}>"
139
173
  end
@@ -145,6 +179,7 @@ module Saxon
145
179
  @qname_string, @prefix = qname_string, prefix
146
180
  end
147
181
 
182
+ # The error message reports the unbound prefix and complete QName
148
183
  def to_s
149
184
  "Namespace prefix ‘#{@prefix}’ for QName ‘#{@qname_string}’ is not bound to a URI"
150
185
  end
@@ -3,8 +3,13 @@ require 'saxon/loader'
3
3
  module Saxon
4
4
  module S9API
5
5
  CLASS_IMPORT_SEMAPHORE = Mutex.new
6
+ private_constant :CLASS_IMPORT_SEMAPHORE
6
7
 
7
8
  class << self
9
+ # Override the +const_missing+ hook in {S9API} so that we can delay
10
+ # loading the Saxon JARs until the user has had a chance to set an
11
+ # alternate location for them, if they don't want to use the bundled Saxon
12
+ # HE
8
13
  def const_missing(name)
9
14
  Saxon::Loader.load!
10
15
  begin