mediawiki-gateway 0.1.0

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.
Files changed (47) hide show
  1. data/.gitignore +3 -0
  2. data/README +12 -0
  3. data/Rakefile +40 -0
  4. data/VERSION +1 -0
  5. data/config/hosts.yml +17 -0
  6. data/doc/classes/MediaWiki.html +189 -0
  7. data/doc/classes/MediaWiki/Config.html +269 -0
  8. data/doc/classes/MediaWiki/Gateway.html +952 -0
  9. data/doc/created.rid +1 -0
  10. data/doc/files/README_txt.html +117 -0
  11. data/doc/files/media_wiki/config_rb.html +108 -0
  12. data/doc/files/media_wiki/gateway_rb.html +113 -0
  13. data/doc/files/media_wiki/utils_rb.html +101 -0
  14. data/doc/files/script/create_page_rb.html +115 -0
  15. data/doc/files/script/delete_book_rb.html +108 -0
  16. data/doc/files/script/export_xml_rb.html +114 -0
  17. data/doc/files/script/get_page_rb.html +114 -0
  18. data/doc/files/script/import_xml_rb.html +114 -0
  19. data/doc/files/script/undelete_page_rb.html +101 -0
  20. data/doc/files/script/upload_commons_rb.html +109 -0
  21. data/doc/files/script/upload_file_rb.html +115 -0
  22. data/doc/fr_class_index.html +29 -0
  23. data/doc/fr_file_index.html +38 -0
  24. data/doc/fr_method_index.html +49 -0
  25. data/doc/index.html +24 -0
  26. data/doc/rdoc-style.css +208 -0
  27. data/lib/media_wiki.rb +3 -0
  28. data/lib/media_wiki/config.rb +69 -0
  29. data/lib/media_wiki/gateway.rb +307 -0
  30. data/lib/media_wiki/utils.rb +18 -0
  31. data/mediawiki-gateway.gemspec +88 -0
  32. data/script/create_page.rb +14 -0
  33. data/script/delete_book.rb +14 -0
  34. data/script/export_xml.rb +14 -0
  35. data/script/get_page.rb +12 -0
  36. data/script/import_xml.rb +14 -0
  37. data/script/run_fake_media_wiki.rb +8 -0
  38. data/script/undelete_page.rb +15 -0
  39. data/script/upload_commons.rb +42 -0
  40. data/script/upload_file.rb +14 -0
  41. data/spec/fake_media_wiki/api_pages.rb +131 -0
  42. data/spec/fake_media_wiki/app.rb +262 -0
  43. data/spec/fake_media_wiki/query_handling.rb +112 -0
  44. data/spec/gateway_spec.old +535 -0
  45. data/spec/gateway_spec.rb +653 -0
  46. data/spec/import-test-data.xml +68 -0
  47. metadata +115 -0
data/doc/index.html ADDED
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="iso-8859-1"?>
2
+ <!DOCTYPE html
3
+ PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
4
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
5
+
6
+ <!--
7
+
8
+ RDoc Documentation
9
+
10
+ -->
11
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
12
+ <head>
13
+ <title>RDoc Documentation</title>
14
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
15
+ </head>
16
+ <frameset rows="20%, 80%">
17
+ <frameset cols="25%,35%,45%">
18
+ <frame src="fr_file_index.html" title="Files" name="Files" />
19
+ <frame src="fr_class_index.html" name="Classes" />
20
+ <frame src="fr_method_index.html" name="Methods" />
21
+ </frameset>
22
+ <frame src="files/README_txt.html" name="docwin" />
23
+ </frameset>
24
+ </html>
@@ -0,0 +1,208 @@
1
+
2
+ body {
3
+ font-family: Verdana,Arial,Helvetica,sans-serif;
4
+ font-size: 90%;
5
+ margin: 0;
6
+ margin-left: 40px;
7
+ padding: 0;
8
+ background: white;
9
+ }
10
+
11
+ h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
12
+ h1 { font-size: 150%; }
13
+ h2,h3,h4 { margin-top: 1em; }
14
+
15
+ a { background: #eef; color: #039; text-decoration: none; }
16
+ a:hover { background: #039; color: #eef; }
17
+
18
+ /* Override the base stylesheet's Anchor inside a table cell */
19
+ td > a {
20
+ background: transparent;
21
+ color: #039;
22
+ text-decoration: none;
23
+ }
24
+
25
+ /* and inside a section title */
26
+ .section-title > a {
27
+ background: transparent;
28
+ color: #eee;
29
+ text-decoration: none;
30
+ }
31
+
32
+ /* === Structural elements =================================== */
33
+
34
+ div#index {
35
+ margin: 0;
36
+ margin-left: -40px;
37
+ padding: 0;
38
+ font-size: 90%;
39
+ }
40
+
41
+
42
+ div#index a {
43
+ margin-left: 0.7em;
44
+ }
45
+
46
+ div#index .section-bar {
47
+ margin-left: 0px;
48
+ padding-left: 0.7em;
49
+ background: #ccc;
50
+ font-size: small;
51
+ }
52
+
53
+
54
+ div#classHeader, div#fileHeader {
55
+ width: auto;
56
+ color: white;
57
+ padding: 0.5em 1.5em 0.5em 1.5em;
58
+ margin: 0;
59
+ margin-left: -40px;
60
+ border-bottom: 3px solid #006;
61
+ }
62
+
63
+ div#classHeader a, div#fileHeader a {
64
+ background: inherit;
65
+ color: white;
66
+ }
67
+
68
+ div#classHeader td, div#fileHeader td {
69
+ background: inherit;
70
+ color: white;
71
+ }
72
+
73
+
74
+ div#fileHeader {
75
+ background: #057;
76
+ }
77
+
78
+ div#classHeader {
79
+ background: #048;
80
+ }
81
+
82
+
83
+ .class-name-in-header {
84
+ font-size: 180%;
85
+ font-weight: bold;
86
+ }
87
+
88
+
89
+ div#bodyContent {
90
+ padding: 0 1.5em 0 1.5em;
91
+ }
92
+
93
+ div#description {
94
+ padding: 0.5em 1.5em;
95
+ background: #efefef;
96
+ border: 1px dotted #999;
97
+ }
98
+
99
+ div#description h1,h2,h3,h4,h5,h6 {
100
+ color: #125;;
101
+ background: transparent;
102
+ }
103
+
104
+ div#validator-badges {
105
+ text-align: center;
106
+ }
107
+ div#validator-badges img { border: 0; }
108
+
109
+ div#copyright {
110
+ color: #333;
111
+ background: #efefef;
112
+ font: 0.75em sans-serif;
113
+ margin-top: 5em;
114
+ margin-bottom: 0;
115
+ padding: 0.5em 2em;
116
+ }
117
+
118
+
119
+ /* === Classes =================================== */
120
+
121
+ table.header-table {
122
+ color: white;
123
+ font-size: small;
124
+ }
125
+
126
+ .type-note {
127
+ font-size: small;
128
+ color: #DEDEDE;
129
+ }
130
+
131
+ .xxsection-bar {
132
+ background: #eee;
133
+ color: #333;
134
+ padding: 3px;
135
+ }
136
+
137
+ .section-bar {
138
+ color: #333;
139
+ border-bottom: 1px solid #999;
140
+ margin-left: -20px;
141
+ }
142
+
143
+
144
+ .section-title {
145
+ background: #79a;
146
+ color: #eee;
147
+ padding: 3px;
148
+ margin-top: 2em;
149
+ margin-left: -30px;
150
+ border: 1px solid #999;
151
+ }
152
+
153
+ .top-aligned-row { vertical-align: top }
154
+ .bottom-aligned-row { vertical-align: bottom }
155
+
156
+ /* --- Context section classes ----------------------- */
157
+
158
+ .context-row { }
159
+ .context-item-name { font-family: monospace; font-weight: bold; color: black; }
160
+ .context-item-value { font-size: small; color: #448; }
161
+ .context-item-desc { color: #333; padding-left: 2em; }
162
+
163
+ /* --- Method classes -------------------------- */
164
+ .method-detail {
165
+ background: #efefef;
166
+ padding: 0;
167
+ margin-top: 0.5em;
168
+ margin-bottom: 1em;
169
+ border: 1px dotted #ccc;
170
+ }
171
+ .method-heading {
172
+ color: black;
173
+ background: #ccc;
174
+ border-bottom: 1px solid #666;
175
+ padding: 0.2em 0.5em 0 0.5em;
176
+ }
177
+ .method-signature { color: black; background: inherit; }
178
+ .method-name { font-weight: bold; }
179
+ .method-args { font-style: italic; }
180
+ .method-description { padding: 0 0.5em 0 0.5em; }
181
+
182
+ /* --- Source code sections -------------------- */
183
+
184
+ a.source-toggle { font-size: 90%; }
185
+ div.method-source-code {
186
+ background: #262626;
187
+ color: #ffdead;
188
+ margin: 1em;
189
+ padding: 0.5em;
190
+ border: 1px dashed #999;
191
+ overflow: hidden;
192
+ }
193
+
194
+ div.method-source-code pre { color: #ffdead; overflow: hidden; }
195
+
196
+ /* --- Ruby keyword styles --------------------- */
197
+
198
+ .standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
199
+
200
+ .ruby-constant { color: #7fffd4; background: transparent; }
201
+ .ruby-keyword { color: #00ffff; background: transparent; }
202
+ .ruby-ivar { color: #eedd82; background: transparent; }
203
+ .ruby-operator { color: #00ffee; background: transparent; }
204
+ .ruby-identifier { color: #ffdead; background: transparent; }
205
+ .ruby-node { color: #ffa07a; background: transparent; }
206
+ .ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
207
+ .ruby-regexp { color: #ffa07a; background: transparent; }
208
+ .ruby-value { color: #7fffd4; background: transparent; }
data/lib/media_wiki.rb ADDED
@@ -0,0 +1,3 @@
1
+ require File.dirname(__FILE__) + '/media_wiki/config'
2
+ require File.dirname(__FILE__) + '/media_wiki/utils'
3
+ require File.dirname(__FILE__) + '/media_wiki/gateway'
@@ -0,0 +1,69 @@
1
+ require 'optparse'
2
+ require 'yaml'
3
+
4
+ module MediaWiki
5
+
6
+ class Config
7
+
8
+ attr_reader :article, :desc, :file, :pw, :summary, :target, :url, :user
9
+
10
+ def initialize(args, type = "read")
11
+ @summary = "Automated edit via MediaWiki::Gateway"
12
+ @opts = OptionParser.new do |opts|
13
+ opts.banner = "Usage: [options]"
14
+
15
+ opts.on("-h", "--host HOST", "Use preconfigured HOST in config/hosts.yml") do |host_id|
16
+ yaml = YAML.load_file('config/hosts.yml')
17
+ if yaml.include? host_id
18
+ host = yaml[host_id]
19
+ @url = host['url']
20
+ @pw = host['pw']
21
+ @user = host['user']
22
+ else
23
+ raise "Host #{host_id} not found in config/hosts.yml"
24
+ end
25
+ end
26
+
27
+ if type == "upload"
28
+ opts.on("-d", "--description DESCRIPTION", "Description of file to upload") do |desc|
29
+ @desc = desc
30
+ end
31
+ opts.on("-t", "--target-file TARGET-FILE", "Target file name to upload to") do |target|
32
+ @target = target
33
+ end
34
+ else
35
+ opts.on("-a", "--article ARTICLE", "Name of article in Wiki") do |article|
36
+ @article = article
37
+ end
38
+ end
39
+
40
+ opts.on("-n", "--username USERNAME", "Username for login") do |user|
41
+ @user = user
42
+ end
43
+
44
+ opts.on("-p", "--password PASSWORD", "Password for login") do |pw|
45
+ @pw = pw
46
+ end
47
+
48
+ if type != "read"
49
+ opts.on("-s", "--summary SUMMARY", "Edit summary for this change") do |summary|
50
+ @summary = summary
51
+ end
52
+ end
53
+
54
+ opts.on("-u", "--url URL", "MediaWiki API URL") do |url|
55
+ @url = url
56
+ end
57
+ end
58
+ @opts.parse!
59
+ abort("URL (-u) or valid host (-h) is mandatory.") unless @url
60
+ end
61
+
62
+ def abort(error)
63
+ puts "Error: #{error}\n\n#{@opts.to_s}"
64
+ exit
65
+ end
66
+
67
+ end
68
+ end
69
+
@@ -0,0 +1,307 @@
1
+ require 'rubygems'
2
+ require 'logger'
3
+ require 'rest_client'
4
+ require 'rexml/document'
5
+ require 'uri'
6
+
7
+ module MediaWiki
8
+
9
+ class Gateway
10
+
11
+ # Set up a MediaWiki::Gateway for a given MediaWiki installation
12
+ #
13
+ # [url] Path to API of target MediaWiki (eg. "http://en.wikipedia.org/w/api.php")
14
+ # [loglevel] Log level to use (optional, defaults to Logger::WARN)
15
+ def initialize(url, loglevel = Logger::WARN)
16
+ @log = Logger.new(STDERR)
17
+ @log.level = loglevel
18
+ @wiki_url = url
19
+ @headers = { "User-Agent" => "MediaWiki::Gateway/#{MediaWiki.version}" }
20
+ @cookies = {}
21
+ end
22
+
23
+ attr_reader :base_url
24
+
25
+ # Login to MediaWiki
26
+ #
27
+ # [username] Username
28
+ # [password] Password
29
+ # [domain] Domain for authentication plugin logins (eg. LDAP), optional -- defaults to 'local' if not given
30
+ #
31
+ # Throws error if login fails
32
+ def login(username, password, domain = 'local')
33
+ form_data = {'action' => 'login', 'lgname' => username, 'lgpassword' => password, 'lgdomain' => domain}
34
+ make_api_request(form_data)
35
+ @password = password
36
+ @username = username
37
+ end
38
+
39
+ # Fetch MediaWiki page in MediaWiki format
40
+ #
41
+ # [page_title] Page title to fetch
42
+ #
43
+ # Returns nil if the page does not exist
44
+ def get(page_title)
45
+ form_data = {'action' => 'query', 'prop' => 'revisions', 'rvprop' => 'content', 'titles' => page_title}
46
+ page = make_api_request(form_data).elements["query/pages/page"]
47
+ if ! page or page.attributes["missing"]
48
+ nil
49
+ else
50
+ page.elements["revisions/rev"].text
51
+ end
52
+ end
53
+
54
+ # Render a MediaWiki page as HTML
55
+ #
56
+ # [page_title] Page title to fetch
57
+ #
58
+ # Returns nil if the page does not exist
59
+ def render(page_title)
60
+ form_data = {'action' => 'parse', 'page' => page_title}
61
+ parsed = make_api_request(form_data).elements["parse"]
62
+ if parsed.attributes["revid"] != '0'
63
+ return parsed.elements["text"].text.gsub(/<!--(.|\s)*?-->/, '')
64
+ else
65
+ nil
66
+ end
67
+ end
68
+
69
+ # Create a new page, or overwrite an existing one
70
+ #
71
+ # [title] Page title to create or overwrite, string
72
+ # [content] Content for the page, string
73
+ # [options] Hash of additional options
74
+ #
75
+ # Options:
76
+ # * [overwrite] Allow overwriting existing pages
77
+ # * [summary] Edit summary for history, string
78
+ # * [token] Use this existing edit token instead requesting a new one (useful for bulk loads)
79
+ def create(title, content, options={})
80
+ form_data = {'action' => 'edit', 'title' => title, 'text' => content, 'summary' => (options[:summary] || ""), 'token' => get_token('edit', title)}
81
+ form_data['createonly'] = "" unless options[:overwrite]
82
+ make_api_request(form_data)
83
+ end
84
+
85
+ # Delete one page. (MediaWiki API does not support deleting multiple pages at a time.)
86
+ #
87
+ # [title] Title of page to delete
88
+ def delete(title)
89
+ form_data = {'action' => 'delete', 'title' => title, 'token' => get_token('delete', title)}
90
+ make_api_request(form_data)
91
+ end
92
+
93
+ # Undelete all revisions of one page.
94
+ #
95
+ # [title] Title of page to undelete
96
+ #
97
+ # Returns number of revisions undeleted.
98
+ def undelete(title)
99
+ token = get_undelete_token(title)
100
+ if token
101
+ form_data = {'action' => 'undelete', 'title' => title, 'token' => token }
102
+ xml = make_api_request(form_data)
103
+ xml.elements["undelete"].attributes["revisions"].to_i
104
+ else
105
+ 0 # No revisions to undelete
106
+ end
107
+ end
108
+
109
+ # Get a list of matching page titles
110
+ #
111
+ # [key] Search key, matched as a prefix (^key.*). May contain or equal a namespace.
112
+ #
113
+ # Returns array of page titles (empty if no matches)
114
+ def list(key)
115
+ titles = []
116
+ apfrom = nil
117
+ key, namespace = key.split(":", 2).reverse
118
+ namespace = namespaces_by_prefix[namespace] || 0
119
+ begin
120
+ form_data =
121
+ {'action' => 'query',
122
+ 'list' => 'allpages',
123
+ 'apfrom' => apfrom,
124
+ 'apprefix' => key,
125
+ 'aplimit' => 500, # max allowed by API
126
+ 'apnamespace' => namespace}
127
+ res = make_api_request(form_data)
128
+ apfrom = res.elements['query-continue'] ? res.elements['query-continue/allpages'].attributes['apfrom'] : nil
129
+ titles += REXML::XPath.match(res, "//p").map { |x| x.attributes["title"] }
130
+ end while apfrom
131
+ titles
132
+ end
133
+
134
+ # Get a list of pages with matching content in given namespaces
135
+ #
136
+ # [key] Search key
137
+ # [namespaces] Array of namespace names to search (defaults to NS_MAIN only)
138
+ # [limit] Max number of hits to return
139
+ #
140
+ # Returns array of page titles (empty if no matches)
141
+ def search(key, namespaces=nil, limit=10)
142
+ titles = []
143
+ form_data = { 'action' => 'query',
144
+ 'list' => 'search',
145
+ 'srwhat' => 'text',
146
+ 'srsearch' => key,
147
+ 'srlimit' => limit}
148
+ if namespaces
149
+ namespaces = [ namespaces ] unless namespaces.kind_of? Array
150
+ form_data['srnamespace'] = namespaces.map! do |ns| namespaces_by_prefix[ns] end.join('|')
151
+ end
152
+ titles += REXML::XPath.match(make_api_request(form_data), "//p").map { |x| x.attributes["title"] }
153
+ end
154
+
155
+ # Upload file to MediaWiki
156
+ # Requires Mediawiki 1.16+
157
+ #
158
+ # [path] Path to file to upload
159
+ # [options] Hash of additional options
160
+ #
161
+ # Options:
162
+ # * [description] Description of this file
163
+ # * [target] Target filename, defaults to local name if not given
164
+ # * [summary] Edit summary for history
165
+ def upload(path, options={})
166
+ comment = (options[:summary] || "Uploaded by MediaWiki::Gateway")
167
+ file = File.new(path)
168
+ filename = (options[:target] || File.basename(path))
169
+ form_data = { 'action' => 'upload',
170
+ 'filename' => filename,
171
+ 'file' => file,
172
+ 'token' => get_token('edit', filename),
173
+ 'text' => (options[:description] || options[:summary]),
174
+ 'comment' => comment}
175
+ make_api_request(form_data)
176
+ end
177
+
178
+ # Imports a MediaWiki XML dump
179
+ #
180
+ # [xml] String or array of page names to fetch
181
+ #
182
+ # Returns XML array <api><import><page/><page/>...
183
+ # <page revisions="1"> (or more) means successfully imported
184
+ # <page revisions="0"> means duplicate, not imported
185
+ def import(xmlfile)
186
+ form_data = { "action" => "import",
187
+ "xml" => File.new(xmlfile),
188
+ "token" => get_token('import', 'Main Page'), # NB: dummy page name
189
+ "format" => 'xml' }
190
+ make_api_request(form_data)
191
+ end
192
+
193
+ # Exports a page or set of pages
194
+ #
195
+ # [page_titles] String or array of page titles to fetch
196
+ #
197
+ # Returns MediaWiki XML dump
198
+ def export(page_titles)
199
+ form_data = {'action' => 'query', 'titles' => [page_titles].join('|'), 'export' => nil, 'exportnowrap' => nil}
200
+ return make_api_request(form_data)
201
+ end
202
+
203
+ # Get a list of all known namespaces
204
+ #
205
+ # Returns array of namespaces (name => id)
206
+ def namespaces_by_prefix
207
+ form_data = { 'action' => 'query', 'meta' => 'siteinfo', 'siprop' => 'namespaces' }
208
+ res = make_api_request(form_data)
209
+ REXML::XPath.match(res, "//ns").inject(Hash.new) do |namespaces, namespace|
210
+ prefix = namespace.attributes["canonical"] || ""
211
+ namespaces[prefix] = namespace.attributes["id"].to_i
212
+ namespaces
213
+ end
214
+ end
215
+
216
+ # Get a list of all installed (and registered) extensions
217
+ #
218
+ # Returns array of extensions (name => version)
219
+ def extensions
220
+ form_data = { 'action' => 'query', 'meta' => 'siteinfo', 'siprop' => 'extensions' }
221
+ res = make_api_request(form_data)
222
+ REXML::XPath.match(res, "//ext").inject(Hash.new) do |extensions, extension|
223
+ name = extension.attributes["name"] || ""
224
+ extensions[name] = extension.attributes["version"]
225
+ extensions
226
+ end
227
+ end
228
+
229
+ # Execute Semantic Mediawiki query
230
+ #
231
+ # [query] Semantic Mediawiki query
232
+ # [params] Array of additional parameters or options, eg. mainlabel=Foo or ?Place (optional)
233
+ #
234
+ # Returns result as an HTML string
235
+ def semantic_query(query, params = [])
236
+ params << "format=list"
237
+ form_data = { 'action' => 'parse', 'prop' => 'text', 'text' => "{{#ask:#{query}|#{params.join('|')}}}" }
238
+ xml = make_api_request(form_data)
239
+ return xml.elements["parse/text"].text
240
+ end
241
+
242
+ private
243
+
244
+ # Fetch token (type 'delete', 'edit', 'import')
245
+ def get_token(type, page_titles)
246
+ form_data = {'action' => 'query', 'prop' => 'info', 'intoken' => type, 'titles' => page_titles}
247
+ res = make_api_request(form_data)
248
+ token = res.elements["query/pages/page"].attributes[type + "token"]
249
+ raise "User is not permitted to perform this operation: #{type}" if token.nil?
250
+ token
251
+ end
252
+
253
+ def get_undelete_token(page_titles)
254
+ form_data = {'action' => 'query', 'list' => 'deletedrevs', 'prop' => 'info', 'drprop' => 'token', 'titles' => page_titles}
255
+ res = make_api_request(form_data)
256
+ if res.elements["query/deletedrevs/page"]
257
+ token = res.elements["query/deletedrevs/page"].attributes["token"]
258
+ raise "User is not permitted to perform this operation: #{type}" if token.nil?
259
+ token
260
+ else
261
+ nil
262
+ end
263
+ end
264
+
265
+ # Make generic request to API
266
+ #
267
+ # [form_data] hash or string of attributes to post
268
+ #
269
+ # Returns XML document
270
+ def make_api_request(form_data)
271
+ form_data['format'] = 'xml' if form_data.kind_of? Hash
272
+ @log.debug("REQ: #{form_data.inspect}, #{@cookies.inspect}")
273
+ RestClient.post(@wiki_url, form_data, @headers.merge({:cookies => @cookies})) do |response, &block|
274
+ # Check response for errors and return XML
275
+ raise "API error, bad response: #{response}" unless response.code >= 200 and response.code < 300
276
+ doc = get_response(response.dup)
277
+ if(form_data['action'] == 'login')
278
+ login_result = doc.elements["login"].attributes['result']
279
+ @cookies.merge!(response.cookies)
280
+ case login_result
281
+ when "Success" then # do nothing
282
+ when "NeedToken" then make_api_request(form_data.merge('lgtoken' => doc.elements["login"].attributes["token"]))
283
+ else raise "Login failed: " + login_result
284
+ end
285
+ end
286
+ return doc
287
+ end
288
+
289
+ end
290
+
291
+ # Get API XML response
292
+ # If there are errors, print and bail out
293
+ # Otherwise return XML root
294
+ def get_response(res)
295
+ doc = REXML::Document.new(res).root
296
+ @log.debug("RES: #{doc}")
297
+ raise "API error, response does not contain Mediawiki API XML: #{res}" unless [ "api", "mediawiki" ].include? doc.name
298
+ if doc.elements["error"]
299
+ code = doc.elements["error"].attributes["code"]
300
+ info = doc.elements["error"].attributes["info"]
301
+ raise "API error: code '#{code}', info '#{info}'"
302
+ end
303
+ doc
304
+ end
305
+
306
+ end
307
+ end