jruby-existdb 0.4
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.
- data/conf.xml +812 -0
- data/lib/existdb.rb +74 -0
- data/lib/existdb/classwrap.rb +70 -0
- data/lib/existdb/collection.rb +98 -0
- data/lib/existdb/dom/active_record.rb +82 -0
- data/lib/existdb/dom/mapper.rb +285 -0
- data/lib/existdb/embedded.rb +84 -0
- data/lib/existdb/index_factory.rb +120 -0
- data/lib/existdb/meta.rb +41 -0
- data/lib/existdb/resource/base.rb +84 -0
- data/lib/existdb/resource/binary.rb +21 -0
- data/lib/existdb/resource/xml.rb +23 -0
- data/lib/existdb/resource_set.rb +34 -0
- data/lib/existdb/xql_factory.rb +191 -0
- data/lib/existdb/xquery_service.rb +11 -0
- data/lib/jars/antlr-2.7.7.jar +0 -0
- data/lib/jars/commons-collections-3.2.1.jar +0 -0
- data/lib/jars/commons-logging-1.1.1.jar +0 -0
- data/lib/jars/commons-pool-1.5.1.jar +0 -0
- data/lib/jars/endorsed/resolver-1.2.jar +0 -0
- data/lib/jars/endorsed/serializer-2.9.1.jar +0 -0
- data/lib/jars/endorsed/xalan-2.7.1.jar +0 -0
- data/lib/jars/endorsed/xercesImpl-2.9.1.jar +0 -0
- data/lib/jars/endorsed/xml-apis-1.3.04.jar +0 -0
- data/lib/jars/exist-lucene-module.jar +0 -0
- data/lib/jars/exist-modules.jar +0 -0
- data/lib/jars/exist.jar +0 -0
- data/lib/jars/extensions/exist-lucene-module.jar +0 -0
- data/lib/jars/extensions/exist-ngram-module.jar +0 -0
- data/lib/jars/extensions/exist-versioning.jar +0 -0
- data/lib/jars/extensions/lucene-core-2.4.1.jar +0 -0
- data/lib/jars/extensions/lucene-regex-2.4.1.jar +0 -0
- data/lib/jars/jgroups-all-2.2.6.jar +0 -0
- data/lib/jars/jta-1.1.jar +0 -0
- data/lib/jars/log4j-1.2.15.jar +0 -0
- data/lib/jars/lucene-core-2.4.1.jar +0 -0
- data/lib/jars/lucene-regex-2.4.1.jar +0 -0
- data/lib/jars/quartz-1.6.5.jar +0 -0
- data/lib/jars/sunxacml-1.2.jar +0 -0
- data/lib/jars/xmldb.jar +0 -0
- data/lib/jars/xmlrpc-client-3.1.2.jar +0 -0
- data/lib/jars/xmlrpc-common-3.1.2.jar +0 -0
- data/lib/jars/xmlrpc-server-3.1.2.jar +0 -0
- data/log4j.xml +190 -0
- metadata +104 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module ExistDB
|
|
2
|
+
class Embedded
|
|
3
|
+
include Singleton
|
|
4
|
+
|
|
5
|
+
attr_accessor :username, :password, :url, :properties
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@username ||= 'admin'
|
|
9
|
+
@password ||= ''
|
|
10
|
+
@properties ||= {'create-database' => 'true'}
|
|
11
|
+
@url ||= "exist:///db"
|
|
12
|
+
|
|
13
|
+
at_exit do
|
|
14
|
+
stop if running?
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def start
|
|
19
|
+
return false if running?
|
|
20
|
+
@impl = org.exist.xmldb.DatabaseImpl.new
|
|
21
|
+
@properties.each{ |key, value| @impl.setProperty(key.to_s, value.to_s) }
|
|
22
|
+
org.xmldb.api.DatabaseManager.registerDatabase(@impl)
|
|
23
|
+
@base_collection = @impl.getCollection(@url, @username, @password)
|
|
24
|
+
@database_instance_manager = @base_collection.getService('DatabaseInstanceManager', '1.0')
|
|
25
|
+
@collection_manager = @base_collection.getService('CollectionManager', '1.0')
|
|
26
|
+
|
|
27
|
+
@running = true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def running?
|
|
31
|
+
@running
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
alias :started? :running?
|
|
35
|
+
|
|
36
|
+
def stop
|
|
37
|
+
return false if stopped?
|
|
38
|
+
@database_instance_manager.shutdown
|
|
39
|
+
@running = false
|
|
40
|
+
true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def stopped?
|
|
44
|
+
not running?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def db
|
|
48
|
+
raise InstanceNotRunning if stopped?
|
|
49
|
+
ClassWrap[ @base_collection ]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def inspect
|
|
53
|
+
"#<#{self.class}:#{self.hash.to_s(16)} #{running? ? 'running' : 'stopped'}>"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def xquery_service
|
|
57
|
+
raise InstanceNotRunning if stopped?
|
|
58
|
+
ClassWrap[ @base_collection.getService('XQueryService', '1.0') ]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# org.exist.storage.BrokerPool
|
|
62
|
+
def broker_pool
|
|
63
|
+
raise InstanceNotRunning if stopped?
|
|
64
|
+
org.exist.storage.BrokerPool.getInstance(@impl.getName)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# org.exist.storage.txn.Txn
|
|
68
|
+
def transaction # :yields: transaction
|
|
69
|
+
raise InstanceNotRunning if stopped?
|
|
70
|
+
mgr = broker_pool.getTransactionManager
|
|
71
|
+
txn = mgr.beginTransaction
|
|
72
|
+
begin
|
|
73
|
+
yield txn
|
|
74
|
+
mgr.commit(txn)
|
|
75
|
+
rescue
|
|
76
|
+
mgr.abort(txn)
|
|
77
|
+
raise
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class InstanceNotRunning < Exception; end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
module ExistDB
|
|
2
|
+
module IndexFactory
|
|
3
|
+
class << self
|
|
4
|
+
def configure(*opts, &block)
|
|
5
|
+
cfg = IndexFactory.new(*opts, &block)
|
|
6
|
+
cfg.configure
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Example:
|
|
11
|
+
# ExistDB::IndexFactory.configure do
|
|
12
|
+
# collection '/db/PartList'
|
|
13
|
+
# range 'qtyOnHand' => Fixnum, 'qtyOnOrder' => Fixnum
|
|
14
|
+
# lucene '//part', 'name', 'description'
|
|
15
|
+
# end
|
|
16
|
+
|
|
17
|
+
class IndexFactory
|
|
18
|
+
|
|
19
|
+
include Meta
|
|
20
|
+
|
|
21
|
+
attr_writer :collection
|
|
22
|
+
|
|
23
|
+
def initialize(*options, &block)
|
|
24
|
+
initialize_with_options(options, [:collection])
|
|
25
|
+
self.instance_eval(&block)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_s
|
|
29
|
+
wrapper do
|
|
30
|
+
ranges.to_s + lucenes.to_s
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def range(*targets)
|
|
35
|
+
@ranges ||= Array.new
|
|
36
|
+
targets.each do |target|
|
|
37
|
+
if target.is_a?(Hash) then
|
|
38
|
+
target.each do |key, value|
|
|
39
|
+
tgt_name = key.to_s
|
|
40
|
+
tgt_type = tgt_name.index('/') ? 'path' : 'qname'
|
|
41
|
+
index_type = type_convert(value)
|
|
42
|
+
@ranges << %|<create #{tgt_type}="#{tgt_name}" type="#{index_type}"/>|
|
|
43
|
+
end
|
|
44
|
+
elsif target.respond_to?(:to_s) then
|
|
45
|
+
tgt_name = target.to_s
|
|
46
|
+
tgt_type = tgt_name.index('/') ? 'path' : 'qname'
|
|
47
|
+
@ranges << %|<create #{tgt_type}="#{tgt_name}"/>|
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def lucene(*targets)
|
|
53
|
+
@lucenes ||= Array.new
|
|
54
|
+
targets.each do |target|
|
|
55
|
+
if target.is_a?(Hash) then
|
|
56
|
+
target.each do |key, value|
|
|
57
|
+
tgt_name = key.to_s
|
|
58
|
+
tgt_type = tgt_name.index('/') ? 'path' : 'qname'
|
|
59
|
+
index_type = type_convert(value)
|
|
60
|
+
@lucenes << %|<text #{tgt_type}="#{tgt_name}" type="#{index_type}"/>|
|
|
61
|
+
end
|
|
62
|
+
elsif target.respond_to?(:to_s) then
|
|
63
|
+
tgt_name = target.to_s
|
|
64
|
+
tgt_type = tgt_name.index('/') ? 'path' : 'qname'
|
|
65
|
+
@lucenes << %|<text #{tgt_type}="#{tgt_name}"/>|
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def collection(col = nil)
|
|
71
|
+
@collection = col if col
|
|
72
|
+
return @collection
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def configure
|
|
76
|
+
raise "Specify a collection" unless @collection
|
|
77
|
+
col = collection.create_collection(configuration_collection_name)
|
|
78
|
+
res = Resource.new(:parent => col, :name => configuration_file, :xml => to_s)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def ranges
|
|
84
|
+
@ranges && @ranges.join('')
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def lucenes
|
|
88
|
+
if @lucenes then
|
|
89
|
+
%|<lucene><analyzer class="org.apache.lucene.analysis.standard.StandardAnalyzer"/><analyzer id="ws" class="org.apache.lucene.analysis.WhitespaceAnalyzer"/>#{ @lucenes.join('') }</lucene>|
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def wrapper
|
|
94
|
+
%|<collection xmlns="http://exist-db.org/collection-config/1.0"><index>| +
|
|
95
|
+
yield +
|
|
96
|
+
%|</index></collection>|
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def type_convert(data_type)
|
|
100
|
+
data_type = data_type.to_s
|
|
101
|
+
{
|
|
102
|
+
'String' => 'xs:string',
|
|
103
|
+
'Fixnum' => 'xs:int',
|
|
104
|
+
'Bignum' => 'xs:int',
|
|
105
|
+
'Numeric' => 'xs:int',
|
|
106
|
+
'Float' => 'xs:float'
|
|
107
|
+
}[data_type]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def configuration_file
|
|
111
|
+
'collection.xconf'
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def configuration_collection_name
|
|
115
|
+
"/db/system/config#{@collection}" if @collection
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
data/lib/existdb/meta.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module ExistDB
|
|
2
|
+
module Meta
|
|
3
|
+
# Metaprogramming conviences
|
|
4
|
+
|
|
5
|
+
def initialize_with_options(options, ordered_options)
|
|
6
|
+
# Usage:
|
|
7
|
+
# class MyClass
|
|
8
|
+
# include Meta
|
|
9
|
+
# attr_accessor :opt1, :opt2
|
|
10
|
+
# def initialize(*options)
|
|
11
|
+
# initialize_with_options(options, [:opt1, :opt2])
|
|
12
|
+
# end
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# obj = MyClass.new(:opt1 => 'foo', :opt2 => 'bar')
|
|
16
|
+
# # OR
|
|
17
|
+
# obj = MyClass.new('foo', 'bar')
|
|
18
|
+
# # MyClass.new now accepts named or ordered params!
|
|
19
|
+
named_or_ordered_options(options, ordered_options).each do |key, value|
|
|
20
|
+
if key.is_a?(Symbol) or key.is_a?(String) then
|
|
21
|
+
key = "#{key}=".to_sym
|
|
22
|
+
self.send(key, value) if self.respond_to?(key)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def named_or_ordered_options(options, ordered_options)
|
|
29
|
+
hash = Hash.new
|
|
30
|
+
options.each_with_index do |option, i|
|
|
31
|
+
if option.is_a?(Hash) then
|
|
32
|
+
hash.merge!(option)
|
|
33
|
+
else
|
|
34
|
+
key = ordered_options[i]
|
|
35
|
+
hash[key] = option
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
return hash
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module ExistDB
|
|
2
|
+
module Resource
|
|
3
|
+
|
|
4
|
+
class << self
|
|
5
|
+
def new(*options)
|
|
6
|
+
if options.size == 1 and not options.first.is_a?(Hash) then
|
|
7
|
+
obj = options.first
|
|
8
|
+
ClassWrap[obj]
|
|
9
|
+
else
|
|
10
|
+
if options.any?{|opt| opt.is_a?(Hash) && ( opt[:xml] || opt[:type] == 'XMLResource' ) } then
|
|
11
|
+
Xml.new(*options)
|
|
12
|
+
elsif options.any?{|opt| opt.is_a?(Hash) && ( opt[:binary] || opt[:type] == 'BinaryResource' ) } then
|
|
13
|
+
Binary.new(*options)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Base
|
|
20
|
+
extend ClassWrappingForwardable
|
|
21
|
+
delegate_to_java(
|
|
22
|
+
:content= => :setContent,
|
|
23
|
+
:content => :getContent,
|
|
24
|
+
:length => :getContentLength,
|
|
25
|
+
:created => :getCreationTime,
|
|
26
|
+
:last_modified => :getLastModificationTime,
|
|
27
|
+
:resource_type => :getResourceType,
|
|
28
|
+
:parent => :getParentCollection,
|
|
29
|
+
:name => :getId
|
|
30
|
+
)
|
|
31
|
+
alias :to_s :content
|
|
32
|
+
alias :size :length
|
|
33
|
+
|
|
34
|
+
def initialize(*opts)
|
|
35
|
+
options = Hash.new
|
|
36
|
+
if opts.size == 1 and not opts.first.is_a?(Hash) then
|
|
37
|
+
@obj = opts.first
|
|
38
|
+
else
|
|
39
|
+
opts.each do |opt|
|
|
40
|
+
if opt.is_a?(Hash) then
|
|
41
|
+
options.merge!(opt)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
collection = ClassUnwrap[ options[:parent] ]
|
|
46
|
+
data = nil
|
|
47
|
+
if options[:binary]
|
|
48
|
+
type = "BinaryResource"
|
|
49
|
+
data = options[:binary]
|
|
50
|
+
elsif options[:xml]
|
|
51
|
+
type = "XMLResource"
|
|
52
|
+
data = options[:xml]
|
|
53
|
+
else
|
|
54
|
+
type = options[:type]
|
|
55
|
+
data = options[:content]
|
|
56
|
+
end
|
|
57
|
+
type ||= "XMLResource"
|
|
58
|
+
|
|
59
|
+
@obj = collection.createResource(options[:name], type)
|
|
60
|
+
if data then
|
|
61
|
+
self.content = data
|
|
62
|
+
self.save
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def inspect
|
|
69
|
+
"#<#{self.class}:0x#{self.hash.to_s(16)} name=#{self.name.inspect}>"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def save
|
|
73
|
+
collection = @obj.getParentCollection
|
|
74
|
+
collection.storeResource( @obj )
|
|
75
|
+
true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def delete
|
|
79
|
+
parent.delete(self)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module ExistDB
|
|
2
|
+
module Resource
|
|
3
|
+
class Binary < Base
|
|
4
|
+
|
|
5
|
+
def content=(data)
|
|
6
|
+
bytes = data.to_s.to_java_bytes
|
|
7
|
+
@obj.setContent(bytes)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def content
|
|
11
|
+
to_io.read
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_io
|
|
15
|
+
input_stream = @obj.getStreamContent
|
|
16
|
+
return Java.java_to_ruby(org.jruby.RubyIO.new(JRuby.runtime, input_stream).java_object)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module ExistDB
|
|
2
|
+
module Resource
|
|
3
|
+
class Xml < Base
|
|
4
|
+
|
|
5
|
+
def xquery(*opts)
|
|
6
|
+
parent.xquery.query(self, *opts)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def compile(*opts)
|
|
10
|
+
parent.xquery.compile(*opts)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def execute(*opts)
|
|
14
|
+
parent.xquery.execute(self, *opts)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def dom
|
|
18
|
+
@obj.getContentAsDOM
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module ExistDB
|
|
2
|
+
class ResourceSet
|
|
3
|
+
|
|
4
|
+
include Enumerable
|
|
5
|
+
extend ClassWrappingForwardable
|
|
6
|
+
delegate_to_java(
|
|
7
|
+
:size => :getSize,
|
|
8
|
+
:[] => :getResource,
|
|
9
|
+
:join => :getMembersAsResource,
|
|
10
|
+
:add => :addResource
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
def initialize(java_obj)
|
|
14
|
+
@obj = java_obj
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def each
|
|
18
|
+
for i in (0 .. (size - 1))
|
|
19
|
+
yield self[i]
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
def [](*resources)
|
|
25
|
+
set = new( org.exist.xmldb.MapResourceSet.new )
|
|
26
|
+
resources.each do |resource|
|
|
27
|
+
set.add(resource)
|
|
28
|
+
end
|
|
29
|
+
return set
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
module ExistDB
|
|
2
|
+
module XQLFactory
|
|
3
|
+
|
|
4
|
+
class << self
|
|
5
|
+
|
|
6
|
+
# Shorthand for ExistDB::XQLFactory::XQLFactory.new( options ).xquery
|
|
7
|
+
|
|
8
|
+
def Build(*opts, &block)
|
|
9
|
+
XQLFactory.new(*opts, &block).xquery
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# This is used by the XQLFactory for remapping nodes
|
|
14
|
+
#
|
|
15
|
+
# This could possibly be simplified by using XSLT if there was a Ruby DSL for XSLT
|
|
16
|
+
#
|
|
17
|
+
# The primary goal is to keep the barrier to entry low for rubyists who have limited XML experience
|
|
18
|
+
class Path
|
|
19
|
+
|
|
20
|
+
attr_reader :to_s, :to_a
|
|
21
|
+
def initialize(path)
|
|
22
|
+
if path.is_a?(String) then
|
|
23
|
+
@to_s = path
|
|
24
|
+
@to_a = path.split('/')
|
|
25
|
+
elsif path.is_a?(Array) then
|
|
26
|
+
@to_s = path.join('/')
|
|
27
|
+
@to_a = path
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def name
|
|
32
|
+
@to_a.last
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def context
|
|
36
|
+
@to_a[0..-2]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def <=>(path)
|
|
40
|
+
to_a <=> path.to_a
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def common(path)
|
|
44
|
+
a = Array.new
|
|
45
|
+
self.to_a.each_index do |i|
|
|
46
|
+
if self.to_a[i].nil? or
|
|
47
|
+
path.to_a[i].nil? or
|
|
48
|
+
self.to_a[i] != path.to_a[i] then
|
|
49
|
+
break
|
|
50
|
+
else
|
|
51
|
+
a << self.to_a[i]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
a << ''
|
|
55
|
+
return Path.new(a)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def switch_context(path)
|
|
59
|
+
output = String.new
|
|
60
|
+
common_path = self.common(path)
|
|
61
|
+
diff = self.context.size - common_path.context.size
|
|
62
|
+
if diff > 0 then
|
|
63
|
+
output << self.context.last(diff).reverse.map{ |node| "</#{node}>"}.join
|
|
64
|
+
end
|
|
65
|
+
diff = path.context.size - common_path.context.size
|
|
66
|
+
if diff > 0 then
|
|
67
|
+
output << path.context.last(diff).map{ |node| "<#{node}>"}.join
|
|
68
|
+
end
|
|
69
|
+
return output
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# This is an attempt to create a Ruby DSL for the Most Common XQuery use cases.
|
|
75
|
+
class XQLFactory
|
|
76
|
+
|
|
77
|
+
include Meta
|
|
78
|
+
attr_accessor :start, :max, :doc,
|
|
79
|
+
:node_xpath, :sort, :return_attributes,
|
|
80
|
+
:return_tag, :node_remap, :search, :ftquery
|
|
81
|
+
|
|
82
|
+
# Accepts Ordered or Named Parameters for any of the attr_accessors
|
|
83
|
+
#
|
|
84
|
+
# Ordered Options -- :doc, :start, :max, :sort
|
|
85
|
+
#
|
|
86
|
+
# E.g.
|
|
87
|
+
#
|
|
88
|
+
# <tt>
|
|
89
|
+
# xql = XQLFactory.new("doc('http://example.com')", 5, 10, :node_xpath => '//a').xquery
|
|
90
|
+
# </tt>
|
|
91
|
+
#
|
|
92
|
+
# Would create an XQuery statement to find the fifth through tenth links on example.com
|
|
93
|
+
#
|
|
94
|
+
# See also ExistDB::XQLFactory.Build as a shorthand way of calling this.
|
|
95
|
+
|
|
96
|
+
def initialize(*options)
|
|
97
|
+
initialize_with_options(options, [:doc, :start, :max, :sort])
|
|
98
|
+
yield self if block_given?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def limit_statement
|
|
102
|
+
if start or max then
|
|
103
|
+
"let $scope := subsequence($scope, #{start || 1}, #{max || 'count($scope)'})\n"
|
|
104
|
+
else
|
|
105
|
+
''
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def sort_statement
|
|
110
|
+
if sort then
|
|
111
|
+
if sort.is_a?(String) then
|
|
112
|
+
"order by $node/#{sort}"
|
|
113
|
+
elsif sort.is_a?(Hash) then
|
|
114
|
+
"order by $node/#{sort.keys.first} #{sort_direction(sort.values.first)}"
|
|
115
|
+
elsif sort.is_a?(Array) then
|
|
116
|
+
"order by " + sort.map{ |h| "$node/#{h.keys.first} #{sort_direction(h.values.first)}" }.join(', ')
|
|
117
|
+
end
|
|
118
|
+
else
|
|
119
|
+
''
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def sort_direction(dir)
|
|
124
|
+
dir = dir.to_s.downcase
|
|
125
|
+
if %w|asc ascending|.include?(dir)
|
|
126
|
+
return :ascending
|
|
127
|
+
elsif %|desc descending|.include?(dir)
|
|
128
|
+
return :descending
|
|
129
|
+
else
|
|
130
|
+
return :ascending
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def search_statement
|
|
135
|
+
"[contains(., #{ search.inspect })]" if search
|
|
136
|
+
"[ft:query(., #{ ftquery.inspect })]" if ftquery
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def init_statement
|
|
140
|
+
raise "doc attribute required" if doc.nil? or doc.to_s.empty?
|
|
141
|
+
raise "node_xpath attribute required" if node_xpath.nil? or node_xpath.empty?
|
|
142
|
+
"let $scope := for $node in #{doc if doc.is_a?(String)}#{node_xpath}#{search_statement} #{sort_statement} return $node\n"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def return_tag
|
|
146
|
+
@return_tag || 'xml'
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def return_tag=(tag)
|
|
150
|
+
@return_tag = tag
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def return_statement
|
|
154
|
+
if return_attributes_statement.empty? and @return_tag.nil? then
|
|
155
|
+
"return $scope"
|
|
156
|
+
else
|
|
157
|
+
"return <#{return_tag}#{return_attributes_statement}> { $scope } </#{return_tag}>"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def return_attributes_statement
|
|
162
|
+
return '' if return_attributes.nil? or return_attributes.empty?
|
|
163
|
+
' ' + return_attributes.keys.map{ |key|
|
|
164
|
+
"#{key}=#{return_attributes[key].to_s.inspect}"
|
|
165
|
+
}.join(' ')
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def node_remap_statement
|
|
169
|
+
return '' if node_remap.nil? or node_remap.empty?
|
|
170
|
+
output = "let $scope := for $node in $scope return\n"
|
|
171
|
+
current_context = Path.new('')
|
|
172
|
+
node_remap.to_a.map{|a|
|
|
173
|
+
a[1] = Path.new(a[1]); a
|
|
174
|
+
}.sort.each do |a|
|
|
175
|
+
(src_path, dest_path) = a
|
|
176
|
+
output << current_context.switch_context(dest_path)
|
|
177
|
+
current_context = dest_path
|
|
178
|
+
output << "<#{dest_path.name}>{ $node#{src_path} }</#{dest_path.name}>"
|
|
179
|
+
end
|
|
180
|
+
output << current_context.switch_context( Path.new('') )
|
|
181
|
+
output << "\n"
|
|
182
|
+
return output
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def xquery
|
|
186
|
+
init_statement + limit_statement + node_remap_statement + return_statement
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|