solr-ruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/solr.rb +18 -0
- data/lib/solr/connection.rb +158 -0
- data/lib/solr/document.rb +71 -0
- data/lib/solr/exception.rb +13 -0
- data/lib/solr/field.rb +35 -0
- data/lib/solr/request.rb +24 -0
- data/lib/solr/request/add_document.rb +63 -0
- data/lib/solr/request/base.rb +29 -0
- data/lib/solr/request/commit.rb +21 -0
- data/lib/solr/request/delete.rb +50 -0
- data/lib/solr/request/dismax.rb +43 -0
- data/lib/solr/request/index_info.rb +17 -0
- data/lib/solr/request/optimize.rb +21 -0
- data/lib/solr/request/ping.rb +36 -0
- data/lib/solr/request/select.rb +54 -0
- data/lib/solr/request/standard.rb +105 -0
- data/lib/solr/request/update.rb +22 -0
- data/lib/solr/response.rb +24 -0
- data/lib/solr/response/add_document.rb +17 -0
- data/lib/solr/response/base.rb +42 -0
- data/lib/solr/response/commit.rb +32 -0
- data/lib/solr/response/delete.rb +13 -0
- data/lib/solr/response/dismax.rb +8 -0
- data/lib/solr/response/index_info.rb +26 -0
- data/lib/solr/response/optimize.rb +14 -0
- data/lib/solr/response/ping.rb +28 -0
- data/lib/solr/response/ruby.rb +42 -0
- data/lib/solr/response/standard.rb +60 -0
- data/lib/solr/response/xml.rb +37 -0
- data/lib/solr/xml.rb +47 -0
- data/test/unit/add_document_test.rb +40 -0
- data/test/unit/commit_test.rb +41 -0
- data/test/unit/connection_test.rb +55 -0
- data/test/unit/delete_test.rb +56 -0
- data/test/unit/dismax_request_test.rb +26 -0
- data/test/unit/document_test.rb +59 -0
- data/test/unit/field_test.rb +42 -0
- data/test/unit/ping_test.rb +51 -0
- data/test/unit/request_test.rb +61 -0
- data/test/unit/response_test.rb +42 -0
- data/test/unit/solr_mock_base.rb +40 -0
- data/test/unit/standard_request_test.rb +108 -0
- data/test/unit/standard_response_test.rb +174 -0
- data/test/unit/suite.rb +25 -0
- metadata +92 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
2
|
+
# (the "License"); you may not use this file except in compliance with
|
3
|
+
# the License. You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
|
13
|
+
class Solr::Response::IndexInfo < Solr::Response::Ruby
|
14
|
+
def initialize(ruby_code)
|
15
|
+
super(ruby_code)
|
16
|
+
end
|
17
|
+
|
18
|
+
def num_docs
|
19
|
+
return @data['index']['numDocs']
|
20
|
+
end
|
21
|
+
|
22
|
+
def field_names
|
23
|
+
return @data['fields'].keys
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
2
|
+
# (the "License"); you may not use this file except in compliance with
|
3
|
+
# the License. You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
|
13
|
+
class Solr::Response::Optimize < Solr::Response::Commit
|
14
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
2
|
+
# (the "License"); you may not use this file except in compliance with
|
3
|
+
# the License. You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
|
13
|
+
require 'rexml/xpath'
|
14
|
+
|
15
|
+
class Solr::Response::Ping < Solr::Response::Xml
|
16
|
+
|
17
|
+
def initialize(xml)
|
18
|
+
super(xml)
|
19
|
+
@ok = REXML::XPath.first(@doc, './solr/ping') ? true : false
|
20
|
+
end
|
21
|
+
|
22
|
+
# returns true or false depending on whether the ping
|
23
|
+
# was successful or not
|
24
|
+
def ok?
|
25
|
+
@ok
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
2
|
+
# (the "License"); you may not use this file except in compliance with
|
3
|
+
# the License. You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
|
13
|
+
class Solr::Response::Ruby < Solr::Response::Base
|
14
|
+
attr_reader :data
|
15
|
+
|
16
|
+
def initialize(ruby_code)
|
17
|
+
super(ruby_code)
|
18
|
+
begin
|
19
|
+
#TODO: what about pulling up data/header/response to ResponseBase,
|
20
|
+
# or maybe a new middle class like SelectResponseBase since
|
21
|
+
# all Select queries return this same sort of stuff??
|
22
|
+
# XML (&wt=xml) and Ruby (&wt=ruby) responses contain exactly the same structure.
|
23
|
+
# a goal of solrb is to make it irrelevant which gets used under the hood,
|
24
|
+
# but favor Ruby responses.
|
25
|
+
@data = eval(ruby_code)
|
26
|
+
@header = @data['responseHeader']
|
27
|
+
raise "response should be a hash" unless @data.kind_of? Hash
|
28
|
+
raise "response header missing" unless @header.kind_of? Hash
|
29
|
+
rescue SyntaxError => e
|
30
|
+
raise Solr::Exception.new("invalid ruby code: #{e}")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def ok?
|
35
|
+
@header['status'] == 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def query_time
|
39
|
+
@header['QTime']
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
2
|
+
# (the "License"); you may not use this file except in compliance with
|
3
|
+
# the License. You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
|
13
|
+
class Solr::Response::Standard < Solr::Response::Ruby
|
14
|
+
FacetValue = Struct.new(:name, :value)
|
15
|
+
include Enumerable
|
16
|
+
|
17
|
+
def initialize(ruby_code)
|
18
|
+
super(ruby_code)
|
19
|
+
@response = @data['response']
|
20
|
+
raise "response section missing" unless @response.kind_of? Hash
|
21
|
+
end
|
22
|
+
|
23
|
+
def total_hits
|
24
|
+
@response['numFound']
|
25
|
+
end
|
26
|
+
|
27
|
+
def start
|
28
|
+
@response['start']
|
29
|
+
end
|
30
|
+
|
31
|
+
def hits
|
32
|
+
@response['docs']
|
33
|
+
end
|
34
|
+
|
35
|
+
def max_score
|
36
|
+
@response['maxScore']
|
37
|
+
end
|
38
|
+
|
39
|
+
def field_facets(field)
|
40
|
+
facets = []
|
41
|
+
values = @data['facet_counts']['facet_fields'][field]
|
42
|
+
0.upto(values.size / 2 - 1) do |i|
|
43
|
+
n = i * 2
|
44
|
+
facets << FacetValue.new(values[n], values[n+1])
|
45
|
+
end
|
46
|
+
|
47
|
+
facets
|
48
|
+
end
|
49
|
+
|
50
|
+
def highlighted(id, field)
|
51
|
+
@data['highlighting'][id.to_s][field.to_s]
|
52
|
+
end
|
53
|
+
|
54
|
+
# supports enumeration of hits
|
55
|
+
# TODO revisit - should this iterate through *all* hits by re-requesting more?
|
56
|
+
def each
|
57
|
+
@response['docs'].each {|hit| yield hit}
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
2
|
+
# (the "License"); you may not use this file except in compliance with
|
3
|
+
# the License. You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
|
13
|
+
require 'rexml/document'
|
14
|
+
require 'solr/exception'
|
15
|
+
|
16
|
+
class Solr::Response::Xml < Solr::Response::Base
|
17
|
+
attr_reader :doc, :status_code, :status_message
|
18
|
+
|
19
|
+
def initialize(xml)
|
20
|
+
super(xml)
|
21
|
+
# parse the xml
|
22
|
+
@doc = REXML::Document.new(xml)
|
23
|
+
# look for the result code and string
|
24
|
+
result = REXML::XPath.first(@doc, './result')
|
25
|
+
if result
|
26
|
+
@status_code = result.attributes['status']
|
27
|
+
@status_message = result.text
|
28
|
+
end
|
29
|
+
rescue REXML::ParseException => e
|
30
|
+
raise Solr::Exception.new("invalid response xml: #{e}")
|
31
|
+
end
|
32
|
+
|
33
|
+
def ok?
|
34
|
+
return @status_code == '0'
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/lib/solr/xml.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
2
|
+
# (the "License"); you may not use this file except in compliance with
|
3
|
+
# the License. You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
|
13
|
+
module Solr::XML
|
14
|
+
end
|
15
|
+
|
16
|
+
begin
|
17
|
+
|
18
|
+
# If we can load rubygems and libxml-ruby...
|
19
|
+
require 'rubygems'
|
20
|
+
require 'xml/libxml'
|
21
|
+
|
22
|
+
# then make a few modifications to XML::Node so it can stand in for REXML::Element
|
23
|
+
class XML::Node
|
24
|
+
# element.add_element(another_element) should work
|
25
|
+
alias_method :add_element, :<<
|
26
|
+
|
27
|
+
# element.attributes['blah'] should work
|
28
|
+
def attributes
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# element.text = "blah" should work
|
33
|
+
def text=(x)
|
34
|
+
self << x.to_s
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# And use XML::Node for our XML generation
|
39
|
+
Solr::XML::Element = XML::Node
|
40
|
+
|
41
|
+
rescue LoadError => e # If we can't load either rubygems or libxml-ruby
|
42
|
+
|
43
|
+
# Just use REXML.
|
44
|
+
require 'rexml/document'
|
45
|
+
Solr::XML::Element = REXML::Element
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
2
|
+
# (the "License"); you may not use this file except in compliance with
|
3
|
+
# the License. You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
|
13
|
+
require 'solr_mock_base'
|
14
|
+
|
15
|
+
class AddDocumentTest < SolrMockBaseTestCase
|
16
|
+
|
17
|
+
def test_add_document_response
|
18
|
+
conn = Solr::Connection.new('http://localhost:9999/solr')
|
19
|
+
set_post_return('<result status="0"></result>')
|
20
|
+
doc = {:id => '123', :text => 'Tlon, Uqbar, Orbis Tertius'}
|
21
|
+
response = conn.send(Solr::Request::AddDocument.new(doc))
|
22
|
+
assert_equal true, response.ok?
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_bad_add_document_response
|
26
|
+
conn = Solr::Connection.new('http://localhost:9999/solr')
|
27
|
+
set_post_return('<result status="400"></result>')
|
28
|
+
doc = {:id => '123', :text => 'Tlon, Uqbar, Orbis Tertius'}
|
29
|
+
response = conn.send(Solr::Request::AddDocument.new(doc))
|
30
|
+
assert_equal false, response.ok?
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_shorthand
|
34
|
+
conn = Solr::Connection.new('http://localhost:9999/solr')
|
35
|
+
set_post_return('<result status="0"></result>')
|
36
|
+
doc = {:id => '123', :text => 'Tlon, Uqbar, Orbis Tertius'}
|
37
|
+
assert_equal true, conn.add(:id => '123', :text => 'Tlon, Uqbar, Orbis Tetius')
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
2
|
+
# (the "License"); you may not use this file except in compliance with
|
3
|
+
# the License. You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
|
13
|
+
require 'solr_mock_base'
|
14
|
+
|
15
|
+
class CommitTest < SolrMockBaseTestCase
|
16
|
+
|
17
|
+
def test_commit
|
18
|
+
xml = '<result status="0"></result>'
|
19
|
+
conn = Solr::Connection.new('http://localhost:9999/solr')
|
20
|
+
set_post_return(xml)
|
21
|
+
response = conn.send(Solr::Request::Commit.new)
|
22
|
+
assert_kind_of Solr::Response::Commit, response
|
23
|
+
assert true, response.ok?
|
24
|
+
|
25
|
+
# test shorthand
|
26
|
+
assert_equal true, conn.commit
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_invalid_commit
|
30
|
+
xml = '<foo>bar</foo>'
|
31
|
+
conn = Solr::Connection.new('http://localhost:9999/solr')
|
32
|
+
set_post_return(xml)
|
33
|
+
response = conn.send(Solr::Request::Commit.new)
|
34
|
+
assert_kind_of Solr::Response::Commit, response
|
35
|
+
assert_equal false, response.ok?
|
36
|
+
|
37
|
+
# test shorthand
|
38
|
+
assert_equal false, conn.commit
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
2
|
+
# (the "License"); you may not use this file except in compliance with
|
3
|
+
# the License. You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
|
13
|
+
require 'test/unit'
|
14
|
+
require 'solr'
|
15
|
+
require 'solr_mock_base'
|
16
|
+
|
17
|
+
class ConnectionTest < SolrMockBaseTestCase
|
18
|
+
def test_mock
|
19
|
+
connection = Connection.new("http://localhost:9999")
|
20
|
+
set_post_return("foo")
|
21
|
+
assert_equal "foo", connection.post(Solr::Request::AddDocument.new)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_bad_url
|
25
|
+
assert_raise(RuntimeError) do
|
26
|
+
Connection.new("ftp://localhost:9999")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_connection_initialize
|
31
|
+
connection = Solr::Connection.new("http://localhost:8983/solr")
|
32
|
+
assert_equal 'localhost', connection.url.host
|
33
|
+
assert_equal 8983, connection.url.port
|
34
|
+
assert_equal '/solr', connection.url.path
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_non_standard_context
|
38
|
+
connection = Solr::Connection.new("http://localhost:8983/index")
|
39
|
+
assert_equal '/index', connection.url.path
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_xml_response
|
43
|
+
connection = Connection.new("http://localhost:9999")
|
44
|
+
set_post_return "<bogus/>"
|
45
|
+
response = connection.send(Solr::Request::Ping.new)
|
46
|
+
assert_equal "<bogus/>", response.raw_response
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_ruby_response
|
50
|
+
connection = Connection.new("http://localhost:9999")
|
51
|
+
set_post_return "{'responseHeader' => {}, 'response' => {}}"
|
52
|
+
response = connection.send(Solr::Request::Standard.new(:query => 'foo'))
|
53
|
+
assert_equal({'responseHeader' => {}, 'response' => {}}, response.data)
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
2
|
+
# (the "License"); you may not use this file except in compliance with
|
3
|
+
# the License. You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
|
13
|
+
require 'solr_mock_base'
|
14
|
+
|
15
|
+
class DeleteTest < SolrMockBaseTestCase
|
16
|
+
|
17
|
+
def test_delete_request
|
18
|
+
request = Solr::Request::Delete.new(:id => '123')
|
19
|
+
assert_match(/<delete>[\s]*<id>123<\/id>[\s]*<\/delete>/m, request.to_s)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_delete_by_query_request
|
23
|
+
request = Solr::Request::Delete.new(:query => 'name:summers')
|
24
|
+
assert_match(/<delete>[\s]*<query>name:summers<\/query>[\s]*<\/delete>/m, request.to_s)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_delete_response
|
28
|
+
conn = Solr::Connection.new 'http://localhost:9999/solr'
|
29
|
+
set_post_return('<result status="0"></result>')
|
30
|
+
response = conn.send(Solr::Request::Delete.new(:id => 123))
|
31
|
+
assert_equal true, response.ok?
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_bad_delete_request
|
35
|
+
assert_raise(Solr::Exception) do
|
36
|
+
Solr::Request::Delete.new(:bogus => :param)
|
37
|
+
end
|
38
|
+
|
39
|
+
assert_raise(Solr::Exception) do
|
40
|
+
Solr::Request::Delete.new(:id => 529, :query => "id:529")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_bad_delete_response
|
45
|
+
conn = Solr::Connection.new 'http://localhost:9999/solr'
|
46
|
+
set_post_return('<result status="400">uhoh</result>')
|
47
|
+
response = conn.send(Solr::Request::Delete.new(:id => 123))
|
48
|
+
assert_equal false, response.ok?
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_delete_by_i18n_query_request
|
52
|
+
request = Solr::Request::Delete.new(:query => 'ëäïöü')
|
53
|
+
assert_match(/<delete>[\s]*<query>ëäïöü<\/query>[\s]*<\/delete>/m, request.to_s)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|