groupdocs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. data/.travis.yml +5 -0
  2. data/Gemfile +3 -0
  3. data/README.md +141 -0
  4. data/Rakefile +9 -0
  5. data/groupdocs.gemspec +28 -0
  6. data/lib/groupdocs.rb +53 -0
  7. data/lib/groupdocs/api.rb +3 -0
  8. data/lib/groupdocs/api/entity.rb +113 -0
  9. data/lib/groupdocs/api/helpers.rb +5 -0
  10. data/lib/groupdocs/api/helpers/access_helper.rb +56 -0
  11. data/lib/groupdocs/api/helpers/actions_helper.rb +48 -0
  12. data/lib/groupdocs/api/helpers/rest_helper.rb +89 -0
  13. data/lib/groupdocs/api/helpers/status_helper.rb +48 -0
  14. data/lib/groupdocs/api/helpers/url_helper.rb +89 -0
  15. data/lib/groupdocs/api/request.rb +73 -0
  16. data/lib/groupdocs/api/sugar/lookup.rb +48 -0
  17. data/lib/groupdocs/datasource.rb +162 -0
  18. data/lib/groupdocs/datasource/field.rb +44 -0
  19. data/lib/groupdocs/document.rb +513 -0
  20. data/lib/groupdocs/document/annotation.rb +215 -0
  21. data/lib/groupdocs/document/annotation/reply.rb +167 -0
  22. data/lib/groupdocs/document/change.rb +35 -0
  23. data/lib/groupdocs/document/field.rb +27 -0
  24. data/lib/groupdocs/document/metadata.rb +26 -0
  25. data/lib/groupdocs/document/rectangle.rb +24 -0
  26. data/lib/groupdocs/document/view.rb +36 -0
  27. data/lib/groupdocs/errors.rb +10 -0
  28. data/lib/groupdocs/job.rb +201 -0
  29. data/lib/groupdocs/questionnaire.rb +221 -0
  30. data/lib/groupdocs/questionnaire/execution.rb +120 -0
  31. data/lib/groupdocs/questionnaire/page.rb +43 -0
  32. data/lib/groupdocs/questionnaire/question.rb +75 -0
  33. data/lib/groupdocs/questionnaire/question/answer.rb +10 -0
  34. data/lib/groupdocs/storage.rb +37 -0
  35. data/lib/groupdocs/storage/file.rb +248 -0
  36. data/lib/groupdocs/storage/folder.rb +314 -0
  37. data/lib/groupdocs/storage/package.rb +42 -0
  38. data/lib/groupdocs/user.rb +50 -0
  39. data/lib/groupdocs/version.rb +3 -0
  40. data/spec/groupdocs/api/entity_spec.rb +54 -0
  41. data/spec/groupdocs/api/helpers/access_helper_spec.rb +89 -0
  42. data/spec/groupdocs/api/helpers/actions_helper_spec.rb +51 -0
  43. data/spec/groupdocs/api/helpers/rest_helper_spec.rb +187 -0
  44. data/spec/groupdocs/api/helpers/status_helper_spec.rb +85 -0
  45. data/spec/groupdocs/api/helpers/url_helper_spec.rb +93 -0
  46. data/spec/groupdocs/api/request_spec.rb +85 -0
  47. data/spec/groupdocs/datasource/field_spec.rb +50 -0
  48. data/spec/groupdocs/datasource_spec.rb +156 -0
  49. data/spec/groupdocs/document/annotation/reply_spec.rb +179 -0
  50. data/spec/groupdocs/document/annotation_spec.rb +226 -0
  51. data/spec/groupdocs/document/change_spec.rb +35 -0
  52. data/spec/groupdocs/document/field_spec.rb +31 -0
  53. data/spec/groupdocs/document/metadata_spec.rb +26 -0
  54. data/spec/groupdocs/document/rectangle_spec.rb +34 -0
  55. data/spec/groupdocs/document/view_spec.rb +36 -0
  56. data/spec/groupdocs/document_spec.rb +509 -0
  57. data/spec/groupdocs/errors_spec.rb +7 -0
  58. data/spec/groupdocs/job_spec.rb +196 -0
  59. data/spec/groupdocs/questionnaire/execution_spec.rb +136 -0
  60. data/spec/groupdocs/questionnaire/page_spec.rb +50 -0
  61. data/spec/groupdocs/questionnaire/question/answer_spec.rb +11 -0
  62. data/spec/groupdocs/questionnaire/question_spec.rb +84 -0
  63. data/spec/groupdocs/questionnaire_spec.rb +217 -0
  64. data/spec/groupdocs/storage/file_spec.rb +242 -0
  65. data/spec/groupdocs/storage/folder_spec.rb +310 -0
  66. data/spec/groupdocs/storage/package_spec.rb +41 -0
  67. data/spec/groupdocs/storage_spec.rb +27 -0
  68. data/spec/groupdocs/user_spec.rb +53 -0
  69. data/spec/groupdocs_spec.rb +56 -0
  70. data/spec/spec_helper.rb +46 -0
  71. data/spec/support/files/resume.pdf +0 -0
  72. data/spec/support/json/annotation_collaborators_set.json +16 -0
  73. data/spec/support/json/annotation_create.json +12 -0
  74. data/spec/support/json/annotation_list.json +32 -0
  75. data/spec/support/json/annotation_remove.json +9 -0
  76. data/spec/support/json/annotation_replies_create.json +9 -0
  77. data/spec/support/json/annotation_replies_get.json +25 -0
  78. data/spec/support/json/comparison_changes.json +46 -0
  79. data/spec/support/json/comparison_compare.json +8 -0
  80. data/spec/support/json/comparison_document.json +10 -0
  81. data/spec/support/json/datasource_add.json +8 -0
  82. data/spec/support/json/datasource_get.json +22 -0
  83. data/spec/support/json/datasource_remove.json +8 -0
  84. data/spec/support/json/datasource_update.json +8 -0
  85. data/spec/support/json/document_access_info_get.json +14 -0
  86. data/spec/support/json/document_convert.json +8 -0
  87. data/spec/support/json/document_datasource.json +10 -0
  88. data/spec/support/json/document_fields.json +34 -0
  89. data/spec/support/json/document_formats.json +8 -0
  90. data/spec/support/json/document_metadata.json +15 -0
  91. data/spec/support/json/document_questionnaire_create.json +9 -0
  92. data/spec/support/json/document_questionnaires.json +23 -0
  93. data/spec/support/json/document_sharers_remove.json +8 -0
  94. data/spec/support/json/document_sharers_set.json +16 -0
  95. data/spec/support/json/document_views.json +32 -0
  96. data/spec/support/json/file_compress.json +8 -0
  97. data/spec/support/json/file_copy.json +14 -0
  98. data/spec/support/json/file_delete.json +5 -0
  99. data/spec/support/json/file_move.json +14 -0
  100. data/spec/support/json/file_upload.json +8 -0
  101. data/spec/support/json/folder_create.json +8 -0
  102. data/spec/support/json/folder_delete.json +5 -0
  103. data/spec/support/json/folder_list.json +21 -0
  104. data/spec/support/json/folder_move.json +8 -0
  105. data/spec/support/json/folder_sharers_get.json +16 -0
  106. data/spec/support/json/folder_sharers_remove.json +8 -0
  107. data/spec/support/json/folder_sharers_set.json +16 -0
  108. data/spec/support/json/job_add_url.json +8 -0
  109. data/spec/support/json/job_create.json +8 -0
  110. data/spec/support/json/job_documents.json +39 -0
  111. data/spec/support/json/job_file_add.json +8 -0
  112. data/spec/support/json/job_update.json +7 -0
  113. data/spec/support/json/jobs_get.json +52 -0
  114. data/spec/support/json/package_create.json +7 -0
  115. data/spec/support/json/questionnaire_create.json +8 -0
  116. data/spec/support/json/questionnaire_datasources.json +26 -0
  117. data/spec/support/json/questionnaire_execution_create.json +9 -0
  118. data/spec/support/json/questionnaire_execution_status_set.json +8 -0
  119. data/spec/support/json/questionnaire_execution_update.json +8 -0
  120. data/spec/support/json/questionnaire_executions.json +24 -0
  121. data/spec/support/json/questionnaire_get.json +14 -0
  122. data/spec/support/json/questionnaire_remove.json +8 -0
  123. data/spec/support/json/questionnaire_update.json +8 -0
  124. data/spec/support/json/questionnaires_get.json +22 -0
  125. data/spec/support/json/storage_info.json +10 -0
  126. data/spec/support/shared_examples/api/entity.rb +37 -0
  127. data/spec/support/shared_examples/api/helpers/status_helper.rb +12 -0
  128. data/spec/support/shared_examples/api/sugar/lookup.rb +57 -0
  129. metadata +356 -0
@@ -0,0 +1,89 @@
1
+ module GroupDocs
2
+ module Api
3
+ module Helpers
4
+ module REST
5
+
6
+ DEFAULT_HEADERS = {
7
+ accept: 'application/json',
8
+ content_length: 0
9
+ }
10
+
11
+ private
12
+
13
+ #
14
+ # Prepares headers, method and payload for request.
15
+ #
16
+ # @api private
17
+ #
18
+ def prepare_request
19
+ if options[:headers].is_a?(Hash)
20
+ options[:headers].merge!(DEFAULT_HEADERS)
21
+ else
22
+ options[:headers] = DEFAULT_HEADERS.dup
23
+ end
24
+
25
+ options[:method] = options[:method].downcase
26
+
27
+ if options[:request_body] && !options[:request_body].is_a?(Object::File)
28
+ options[:request_body] = options[:request_body].to_json
29
+ options[:headers][:content_type]= 'application/json'
30
+ options[:headers][:content_length] = options[:request_body].length
31
+ end
32
+ end
33
+
34
+ #
35
+ # Sends request to API server.
36
+ #
37
+ # @api private
38
+ #
39
+ def send_request
40
+ self.response = case options[:method]
41
+ when :get, :download
42
+ resource[options[:path]].get(options[:headers])
43
+ when :post
44
+ resource[options[:path]].post(options[:request_body], options[:headers])
45
+ when :put
46
+ resource[options[:path]].put(options[:request_body], options[:headers])
47
+ when :delete
48
+ resource[options[:path]].delete(options[:headers])
49
+ else
50
+ raise GroupDocs::Errors::UnsupportedMethodError, "Unsupported HTTP method: #{options[:method].inspect}"
51
+ end
52
+ end
53
+
54
+ #
55
+ # Parses response from API server.
56
+ #
57
+ # @api private
58
+ #
59
+ def parse_response
60
+ # for DOWNLOAD requests, just return response
61
+ if options[:method] == :download
62
+ response
63
+ # for all other requests, parse JSON
64
+ else
65
+ json = JSON.parse(response, symbolize_names: true)
66
+ json[:status] == 'Ok' ? json[:result] : raise_bad_request_error(json)
67
+ end
68
+ end
69
+
70
+ #
71
+ # @raise [GroupDocs::Errors::BadResponseError]
72
+ # @api private
73
+ #
74
+ def raise_bad_request_error(json)
75
+ raise GroupDocs::Errors::BadResponseError, <<-ERR
76
+ Bad response!
77
+ Request method: #{options[:method].upcase}
78
+ Request URL: #{resource[options[:path]]}
79
+ Request body: #{options[:request_body]}
80
+ Status: #{json[:status]}
81
+ Error message: #{json[:error_message]}
82
+ Response body: #{response}
83
+ ERR
84
+ end
85
+
86
+ end # Request
87
+ end # Helpers
88
+ end # Api
89
+ end # GroupDocs
@@ -0,0 +1,48 @@
1
+ module GroupDocs
2
+ module Api
3
+ module Helpers
4
+ module Status
5
+
6
+ STATUSES = {
7
+ draft: -1,
8
+ pending: 0,
9
+ scheduled: 1,
10
+ in_progress: 2,
11
+ completed: 3,
12
+ postponed: 4,
13
+ archived: 5,
14
+ }
15
+
16
+ # @attr [Symbol] status
17
+ attr_accessor :status
18
+
19
+ #
20
+ # Sets status of the entity.
21
+ #
22
+ # @return [Symbol]
23
+ #
24
+ def status
25
+ parse_status(@status)
26
+ end
27
+
28
+ private
29
+
30
+ #
31
+ # Converts status from/to human-readable format.
32
+ #
33
+ # @param [Integer, Symbol] status
34
+ # @return [Symbol, Integer]
35
+ # @api private
36
+ #
37
+ def parse_status(status)
38
+ if status.is_a?(Integer)
39
+ STATUSES.invert[status]
40
+ else
41
+ STATUSES[status]
42
+ end or raise ArgumentError, "Unknown status: #{status.inspect}."
43
+ end
44
+
45
+ end # Status
46
+ end # Helpers
47
+ end # Api
48
+ end # GroupDocs
@@ -0,0 +1,89 @@
1
+ require 'base64'
2
+ require 'hmac-sha1'
3
+ require 'uri'
4
+ require 'cgi'
5
+
6
+ module GroupDocs
7
+ module Api
8
+ module Helpers
9
+ module URL
10
+
11
+ #
12
+ # Appends path with parameters.
13
+ #
14
+ # @param [Hash] params
15
+ #
16
+ def add_params(params)
17
+ params.each do |param, value|
18
+ value = value.join(?,) if value.is_a?(Array)
19
+ options[:path] << "#{separator}#{param}=#{value}"
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ #
26
+ # Parses path replacing {client_id} with real one.
27
+ #
28
+ # @api private
29
+ #
30
+ def parse_path
31
+ options[:path] = options[:path].sub(/\{\{client_id\}\}/, client_id)
32
+ end
33
+
34
+ #
35
+ # URL encodes path.
36
+ #
37
+ # @api private
38
+ #
39
+ def url_encode_path
40
+ options[:path] = URI.escape(options[:path])
41
+ end
42
+
43
+ #
44
+ # Adds signature to path.
45
+ #
46
+ # @api private
47
+ #
48
+ def sign_url
49
+ # calculate a hash of the path with private key
50
+ hash = HMAC::SHA1.new(private_key)
51
+ hash << options[:path]
52
+ hash = hash.digest
53
+ # convert hash to base64
54
+ hash = Base64.strict_encode64(hash)
55
+ # remove trailing '='
56
+ hash = hash.gsub(/=*$/, '')
57
+ # URL encode hash
58
+ hash = CGI.escape(hash)
59
+ # covert all hexademical characters to upper case
60
+ hash = hash.gsub(/(%[A-Fa-f0-9]{1,2})/) { |group| group.upcase }
61
+
62
+ options[:path] << "#{separator}signature=#{hash}"
63
+ end
64
+
65
+ #
66
+ # Returns separator for GET parameters.
67
+ #
68
+ # @return [String] Either ? or &
69
+ # @api private
70
+ #
71
+ def separator
72
+ options[:path].include?('?') ? '&' : '?'
73
+ end
74
+
75
+ #
76
+ # Prepends path with version number if it's set.
77
+ #
78
+ # @api private
79
+ #
80
+ def prepend_version
81
+ if GroupDocs.api_version
82
+ options[:path] = "/v#{GroupDocs.api_version}#{options[:path]}"
83
+ end
84
+ end
85
+
86
+ end # Query
87
+ end # Helpers
88
+ end # Api
89
+ end # GroupDocs
@@ -0,0 +1,73 @@
1
+ require 'rest-client'
2
+ require 'json'
3
+ require 'groupdocs/api/helpers'
4
+
5
+ module GroupDocs
6
+ module Api
7
+ class Request
8
+
9
+ include GroupDocs::Api::Helpers::Access
10
+ include GroupDocs::Api::Helpers::URL
11
+ include GroupDocs::Api::Helpers::REST
12
+
13
+ # @attr_reader [RestClient::Resource] resource Entry point for all API requests
14
+ attr_reader :resource
15
+ # @attr [String] response Response from server in JSON format
16
+ attr_accessor :response
17
+ # @attr [Hash] options Hash of options
18
+ attr_accessor :options
19
+ # @attr [Hash] access Hash of access credentials
20
+ attr_accessor :access
21
+
22
+ #
23
+ # Creates new API request.
24
+ #
25
+ # @example
26
+ # api = GroupDocs::Api::Request.new do |request|
27
+ # request[:method] = :POST
28
+ # request[:path] = "/storage/#{GroupDocs.client_id}/info"
29
+ # end
30
+ #
31
+ # @param [Hash] options
32
+ # @option options [Symbol] :method HTTP method. One of :GET, :DOWNLOAD, :POST, :PUT or :DELETE.
33
+ # @option options [String] :path Path to send request to
34
+ # @option options [Hash] :headers Additional HTTP headers
35
+ # @option options [Hash] :access Access credentials hash
36
+ # @option options [Hash, File] :request_body Payload. If hash, will be converted to JSON, if File, will be send as is.
37
+ #
38
+ # @yieldparam [Hash] options
39
+ #
40
+ def initialize(options = {}, &blk)
41
+ @options = options
42
+ yield @options if block_given?
43
+ @options[:access] ||= {}
44
+ @resource = RestClient::Resource.new(GroupDocs.api_server)
45
+ end
46
+
47
+ #
48
+ # Executes API request to server.
49
+ #
50
+ # It performs the following actions step by step:
51
+ # * Parses path (i.e. replaces client ID)
52
+ # * Prepends path with version if it's set
53
+ # * URL encodes path
54
+ # * Signs URL
55
+ # * Prepare request (add headers, converts payload to JSON, etc.)
56
+ # * Sends request to server
57
+ # * Parses response
58
+ #
59
+ # @return [Hash, String] Parsed response
60
+ #
61
+ def execute!
62
+ parse_path
63
+ prepend_version
64
+ url_encode_path
65
+ sign_url
66
+ prepare_request
67
+ send_request
68
+ parse_response
69
+ end
70
+
71
+ end # Request
72
+ end # Api
73
+ end # GroupDocs
@@ -0,0 +1,48 @@
1
+ module GroupDocs
2
+ module Api
3
+ module Sugar
4
+ module Lookup
5
+
6
+ #
7
+ # Returns first object matching given options.
8
+ #
9
+ # @param [Symbol] attribute
10
+ # @param [Integer, String, Regexp] value
11
+ # @param [Hash] access Access credentials
12
+ # @option access [String] :client_id
13
+ # @option access [String] :private_key
14
+ # @return [GroupDocs::Api::Entity] Matching entity
15
+ #
16
+ def find!(attribute, value, access = {})
17
+ find_all!(attribute, value, access).first
18
+ end
19
+
20
+ #
21
+ # Returns all objects matching given options.
22
+ #
23
+ # Each entity has to implement #all! method for this to work.
24
+ #
25
+ # @param [Symbol] attribute
26
+ # @param [Integer, String, Regexp] value
27
+ # @param [Hash] access Access credentials
28
+ # @option access [String] :client_id
29
+ # @option access [String] :private_key
30
+ # @return [Array] Array of matching entities
31
+ #
32
+ # @raise [NoMethodError] if extending class does not implement #all! class method.
33
+ #
34
+ def find_all!(attribute, value, access = {})
35
+ respond_to?(:all!) or raise NoMethodError, "#{self}.all! is not implemented - aborting."
36
+
37
+ all!('/', access).select do |object|
38
+ case value
39
+ when Regexp then object.send(attribute) =~ value
40
+ else object.send(attribute) == value
41
+ end
42
+ end
43
+ end
44
+
45
+ end # Lookup
46
+ end # Sugar
47
+ end # Api
48
+ end # GroupDocs
@@ -0,0 +1,162 @@
1
+ module GroupDocs
2
+ class DataSource < GroupDocs::Api::Entity
3
+
4
+ require 'groupdocs/datasource/field'
5
+
6
+ #
7
+ # Returns datasource by given identifier.
8
+ #
9
+ # @param [Integer] id
10
+ # @param [Hash] options
11
+ # @option options [Array] :field Array of field names to be returned. All by default
12
+ # @param [Hash] access Access credentials
13
+ # @option access [String] :client_id
14
+ # @option access [String] :private_key
15
+ # @return [GroupDocs::DataSource, nil]
16
+ #
17
+ def self.get!(id, options = {}, access = {})
18
+ api = GroupDocs::Api::Request.new do |request|
19
+ request[:access] = access
20
+ request[:method] = :GET
21
+ request[:path] = "/merge/{{client_id}}/datasources/#{id}"
22
+ end
23
+ api.add_params(options)
24
+ json = api.execute!
25
+
26
+ GroupDocs::DataSource.new(json[:datasource])
27
+ rescue GroupDocs::Errors::BadResponseError
28
+ nil
29
+ end
30
+
31
+ # @attr [Integer] id
32
+ attr_accessor :id
33
+ # @attr [String] descr
34
+ attr_accessor :descr
35
+ # @attr [Integer] questionnaire_id
36
+ attr_accessor :questionnaire_id
37
+ # @attr [Time] created_on
38
+ attr_accessor :created_on
39
+ # @attr [Time] modified_on
40
+ attr_accessor :modified_on
41
+ # @attr [Array<GroupDocs::DataSource::Field>] fields
42
+ attr_accessor :fields
43
+
44
+ # Human-readable accessors
45
+ alias_method :description, :descr
46
+ alias_method :description=, :descr=
47
+
48
+ #
49
+ # Converts timestamp which is return by API server to Time object.
50
+ #
51
+ # @return [Time]
52
+ #
53
+ def created_on
54
+ Time.at(@created_on)
55
+ end
56
+
57
+ #
58
+ # Converts timestamp which is return by API server to Time object.
59
+ #
60
+ # @return [Time]
61
+ #
62
+ def modified_on
63
+ Time.at(@modified_on)
64
+ end
65
+
66
+ #
67
+ # Converts each field to GroupDocs::DataSource::Field object.
68
+ #
69
+ # @param [Array<GroupDocs::DataSource::Field, Hash>] fields
70
+ #
71
+ def fields=(fields)
72
+ if fields
73
+ @fields = fields.map do |field|
74
+ if field.is_a?(GroupDocs::DataSource::Field)
75
+ field
76
+ else
77
+ GroupDocs::DataSource::Field.new(field)
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ #
84
+ # Adds field to datasource.
85
+ #
86
+ # @param [GroupDocs::DataSource::Field] question
87
+ # @raise [ArgumentError] if field is not GroupDocs::DataSource::Field object
88
+ #
89
+ def add_field(field)
90
+ field.is_a?(GroupDocs::DataSource::Field) or raise ArgumentError,
91
+ "Field should be GroupDocs::DataSource::Field object, received: #{field.inspect}"
92
+
93
+ @fields ||= Array.new
94
+ @fields << field
95
+ end
96
+
97
+ #
98
+ # Adds datasource.
99
+ #
100
+ # @example
101
+ # field = GroupDocs::DataSource::Field.new(field: 'test', values: %w(test test))
102
+ # datasource = GroupDocs::DataSource.new
103
+ # datasource.add_field(field)
104
+ # datasource.add!
105
+ #
106
+ # @param [Hash] access Access credentials
107
+ # @option access [String] :client_id
108
+ # @option access [String] :private_key
109
+ #
110
+ def add!(access = {})
111
+ json = GroupDocs::Api::Request.new do |request|
112
+ request[:access] = access
113
+ request[:method] = :POST
114
+ request[:path] = '/merge/{{client_id}}/datasources'
115
+ request[:request_body] = to_hash
116
+ end.execute!
117
+
118
+ self.id = json[:datasource_id]
119
+ end
120
+
121
+ #
122
+ # Updates datasource.
123
+ #
124
+ # @example
125
+ # field = GroupDocs::DataSource::Field.new(field: 'test', values: %w(test test))
126
+ # datasource = GroupDocs::DataSource.get!(180)
127
+ # datasource.add_field(field)
128
+ # datasource.update!
129
+ #
130
+ # @param [Hash] access Access credentials
131
+ # @option access [String] :client_id
132
+ # @option access [String] :private_key
133
+ #
134
+ def update!(access = {})
135
+ GroupDocs::Api::Request.new do |request|
136
+ request[:access] = access
137
+ request[:method] = :PUT
138
+ request[:path] = "/merge/{{client_id}}/datasources/#{id}"
139
+ request[:request_body] = to_hash
140
+ end.execute!
141
+ end
142
+
143
+ #
144
+ # Removes datasource.
145
+ #
146
+ # @param [Hash] access Access credentials
147
+ # @option access [String] :client_id
148
+ # @option access [String] :private_key
149
+ #
150
+ def remove!(access = {})
151
+ GroupDocs::Api::Request.new do |request|
152
+ request[:access] = access
153
+ request[:method] = :DELETE
154
+ request[:path] = "/merge/{{client_id}}/datasources/#{id}"
155
+ end.execute!
156
+ # TODO: fix this in API
157
+ rescue RestClient::BadRequest
158
+ nil
159
+ end
160
+
161
+ end # DataSource
162
+ end # GroupDocs