diamond-mechanize 2.1
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/CHANGELOG.rdoc +718 -0
- data/EXAMPLES.rdoc +187 -0
- data/FAQ.rdoc +11 -0
- data/GUIDE.rdoc +163 -0
- data/LICENSE.rdoc +20 -0
- data/Manifest.txt +159 -0
- data/README.rdoc +64 -0
- data/Rakefile +49 -0
- data/lib/mechanize.rb +1079 -0
- data/lib/mechanize/content_type_error.rb +13 -0
- data/lib/mechanize/cookie.rb +232 -0
- data/lib/mechanize/cookie_jar.rb +194 -0
- data/lib/mechanize/download.rb +59 -0
- data/lib/mechanize/element_matcher.rb +36 -0
- data/lib/mechanize/file.rb +65 -0
- data/lib/mechanize/file_connection.rb +17 -0
- data/lib/mechanize/file_request.rb +26 -0
- data/lib/mechanize/file_response.rb +74 -0
- data/lib/mechanize/file_saver.rb +39 -0
- data/lib/mechanize/form.rb +543 -0
- data/lib/mechanize/form/button.rb +6 -0
- data/lib/mechanize/form/check_box.rb +12 -0
- data/lib/mechanize/form/field.rb +54 -0
- data/lib/mechanize/form/file_upload.rb +21 -0
- data/lib/mechanize/form/hidden.rb +3 -0
- data/lib/mechanize/form/image_button.rb +19 -0
- data/lib/mechanize/form/keygen.rb +34 -0
- data/lib/mechanize/form/multi_select_list.rb +94 -0
- data/lib/mechanize/form/option.rb +50 -0
- data/lib/mechanize/form/radio_button.rb +55 -0
- data/lib/mechanize/form/reset.rb +3 -0
- data/lib/mechanize/form/select_list.rb +44 -0
- data/lib/mechanize/form/submit.rb +3 -0
- data/lib/mechanize/form/text.rb +3 -0
- data/lib/mechanize/form/textarea.rb +3 -0
- data/lib/mechanize/headers.rb +23 -0
- data/lib/mechanize/history.rb +82 -0
- data/lib/mechanize/http.rb +8 -0
- data/lib/mechanize/http/agent.rb +1004 -0
- data/lib/mechanize/http/auth_challenge.rb +59 -0
- data/lib/mechanize/http/auth_realm.rb +31 -0
- data/lib/mechanize/http/content_disposition_parser.rb +188 -0
- data/lib/mechanize/http/www_authenticate_parser.rb +155 -0
- data/lib/mechanize/monkey_patch.rb +16 -0
- data/lib/mechanize/page.rb +440 -0
- data/lib/mechanize/page/base.rb +7 -0
- data/lib/mechanize/page/frame.rb +27 -0
- data/lib/mechanize/page/image.rb +30 -0
- data/lib/mechanize/page/label.rb +20 -0
- data/lib/mechanize/page/link.rb +98 -0
- data/lib/mechanize/page/meta_refresh.rb +68 -0
- data/lib/mechanize/parser.rb +173 -0
- data/lib/mechanize/pluggable_parsers.rb +144 -0
- data/lib/mechanize/redirect_limit_reached_error.rb +19 -0
- data/lib/mechanize/redirect_not_get_or_head_error.rb +21 -0
- data/lib/mechanize/response_code_error.rb +21 -0
- data/lib/mechanize/response_read_error.rb +27 -0
- data/lib/mechanize/robots_disallowed_error.rb +28 -0
- data/lib/mechanize/test_case.rb +663 -0
- data/lib/mechanize/unauthorized_error.rb +3 -0
- data/lib/mechanize/unsupported_scheme_error.rb +6 -0
- data/lib/mechanize/util.rb +101 -0
- data/test/data/htpasswd +1 -0
- data/test/data/server.crt +16 -0
- data/test/data/server.csr +12 -0
- data/test/data/server.key +15 -0
- data/test/data/server.pem +15 -0
- data/test/htdocs/alt_text.html +10 -0
- data/test/htdocs/bad_form_test.html +9 -0
- data/test/htdocs/button.jpg +0 -0
- data/test/htdocs/canonical_uri.html +9 -0
- data/test/htdocs/dir with spaces/foo.html +1 -0
- data/test/htdocs/empty_form.html +6 -0
- data/test/htdocs/file_upload.html +26 -0
- data/test/htdocs/find_link.html +41 -0
- data/test/htdocs/form_multi_select.html +16 -0
- data/test/htdocs/form_multival.html +37 -0
- data/test/htdocs/form_no_action.html +18 -0
- data/test/htdocs/form_no_input_name.html +16 -0
- data/test/htdocs/form_order_test.html +11 -0
- data/test/htdocs/form_select.html +16 -0
- data/test/htdocs/form_set_fields.html +14 -0
- data/test/htdocs/form_test.html +188 -0
- data/test/htdocs/frame_referer_test.html +10 -0
- data/test/htdocs/frame_test.html +30 -0
- data/test/htdocs/google.html +13 -0
- data/test/htdocs/index.html +6 -0
- data/test/htdocs/link with space.html +5 -0
- data/test/htdocs/meta_cookie.html +11 -0
- data/test/htdocs/no_title_test.html +6 -0
- data/test/htdocs/noindex.html +9 -0
- data/test/htdocs/rails_3_encoding_hack_form_test.html +27 -0
- data/test/htdocs/relative/tc_relative_links.html +21 -0
- data/test/htdocs/robots.html +8 -0
- data/test/htdocs/robots.txt +2 -0
- data/test/htdocs/tc_bad_charset.html +9 -0
- data/test/htdocs/tc_bad_links.html +5 -0
- data/test/htdocs/tc_base_link.html +8 -0
- data/test/htdocs/tc_blank_form.html +11 -0
- data/test/htdocs/tc_charset.html +6 -0
- data/test/htdocs/tc_checkboxes.html +19 -0
- data/test/htdocs/tc_encoded_links.html +5 -0
- data/test/htdocs/tc_field_precedence.html +11 -0
- data/test/htdocs/tc_follow_meta.html +8 -0
- data/test/htdocs/tc_form_action.html +48 -0
- data/test/htdocs/tc_links.html +19 -0
- data/test/htdocs/tc_meta_in_body.html +9 -0
- data/test/htdocs/tc_pretty_print.html +17 -0
- data/test/htdocs/tc_referer.html +16 -0
- data/test/htdocs/tc_relative_links.html +19 -0
- data/test/htdocs/tc_textarea.html +23 -0
- data/test/htdocs/test_click.html +11 -0
- data/test/htdocs/unusual______.html +5 -0
- data/test/test_mechanize.rb +1164 -0
- data/test/test_mechanize_cookie.rb +451 -0
- data/test/test_mechanize_cookie_jar.rb +483 -0
- data/test/test_mechanize_download.rb +43 -0
- data/test/test_mechanize_file.rb +61 -0
- data/test/test_mechanize_file_connection.rb +21 -0
- data/test/test_mechanize_file_request.rb +19 -0
- data/test/test_mechanize_file_saver.rb +21 -0
- data/test/test_mechanize_form.rb +875 -0
- data/test/test_mechanize_form_check_box.rb +38 -0
- data/test/test_mechanize_form_encoding.rb +114 -0
- data/test/test_mechanize_form_field.rb +63 -0
- data/test/test_mechanize_form_file_upload.rb +20 -0
- data/test/test_mechanize_form_image_button.rb +12 -0
- data/test/test_mechanize_form_keygen.rb +32 -0
- data/test/test_mechanize_form_multi_select_list.rb +84 -0
- data/test/test_mechanize_form_option.rb +55 -0
- data/test/test_mechanize_form_radio_button.rb +78 -0
- data/test/test_mechanize_form_select_list.rb +76 -0
- data/test/test_mechanize_form_textarea.rb +52 -0
- data/test/test_mechanize_headers.rb +35 -0
- data/test/test_mechanize_history.rb +103 -0
- data/test/test_mechanize_http_agent.rb +1225 -0
- data/test/test_mechanize_http_auth_challenge.rb +39 -0
- data/test/test_mechanize_http_auth_realm.rb +49 -0
- data/test/test_mechanize_http_content_disposition_parser.rb +118 -0
- data/test/test_mechanize_http_www_authenticate_parser.rb +146 -0
- data/test/test_mechanize_link.rb +80 -0
- data/test/test_mechanize_page.rb +118 -0
- data/test/test_mechanize_page_encoding.rb +182 -0
- data/test/test_mechanize_page_frame.rb +16 -0
- data/test/test_mechanize_page_link.rb +390 -0
- data/test/test_mechanize_page_meta_refresh.rb +127 -0
- data/test/test_mechanize_parser.rb +289 -0
- data/test/test_mechanize_pluggable_parser.rb +52 -0
- data/test/test_mechanize_redirect_limit_reached_error.rb +24 -0
- data/test/test_mechanize_redirect_not_get_or_head_error.rb +14 -0
- data/test/test_mechanize_subclass.rb +22 -0
- data/test/test_mechanize_util.rb +103 -0
- data/test/test_multi_select.rb +119 -0
- metadata +216 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
##
|
|
2
|
+
# An image element on an HTML page
|
|
3
|
+
|
|
4
|
+
class Mechanize::Page::Image
|
|
5
|
+
attr_reader :node
|
|
6
|
+
attr_reader :page
|
|
7
|
+
|
|
8
|
+
def initialize(node, page)
|
|
9
|
+
@node = node
|
|
10
|
+
@page = page
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def src
|
|
14
|
+
@node['src']
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def url
|
|
18
|
+
case src
|
|
19
|
+
when %r{^https?://}
|
|
20
|
+
src
|
|
21
|
+
else
|
|
22
|
+
if page.bases[0]
|
|
23
|
+
(page.bases[0].href + src).to_s
|
|
24
|
+
else
|
|
25
|
+
(page.uri + src).to_s
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
##
|
|
2
|
+
# A form label on an HTML page
|
|
3
|
+
|
|
4
|
+
class Mechanize::Page::Label
|
|
5
|
+
attr_reader :node
|
|
6
|
+
attr_reader :text
|
|
7
|
+
attr_reader :page
|
|
8
|
+
alias :to_s :text
|
|
9
|
+
|
|
10
|
+
def initialize(node, page)
|
|
11
|
+
@node = node
|
|
12
|
+
@text = node.inner_text
|
|
13
|
+
@page = page
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def for
|
|
17
|
+
(id = @node['for']) && page.search("##{id}") || nil
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
##
|
|
2
|
+
# This class encapsulates links. It contains the text and the URI for
|
|
3
|
+
# 'a' tags parsed out of an HTML page. If the link contains an image,
|
|
4
|
+
# the alt text will be used for that image.
|
|
5
|
+
#
|
|
6
|
+
# For example, the text for the following links with both be 'Hello World':
|
|
7
|
+
#
|
|
8
|
+
# <a href="http://example">Hello World</a>
|
|
9
|
+
# <a href="http://example"><img src="test.jpg" alt="Hello World"></a>
|
|
10
|
+
|
|
11
|
+
class Mechanize::Page::Link
|
|
12
|
+
attr_reader :node
|
|
13
|
+
attr_reader :href
|
|
14
|
+
attr_reader :attributes
|
|
15
|
+
attr_reader :page
|
|
16
|
+
alias :referer :page
|
|
17
|
+
|
|
18
|
+
def initialize(node, mech, page)
|
|
19
|
+
@node = node
|
|
20
|
+
@attributes = node
|
|
21
|
+
@href = node['href']
|
|
22
|
+
@mech = mech
|
|
23
|
+
@page = page
|
|
24
|
+
@text = nil
|
|
25
|
+
@uri = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Click on this link
|
|
29
|
+
def click
|
|
30
|
+
@mech.click self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# This method is a shorthand to get link's DOM id.
|
|
34
|
+
# Common usage:
|
|
35
|
+
# page.link_with(:dom_id => "links_exact_id")
|
|
36
|
+
def dom_id
|
|
37
|
+
node['id']
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# This method is a shorthand to get a link's DOM class
|
|
41
|
+
# Common usage:
|
|
42
|
+
# page.link_with(:dom_class => "links_exact_class")
|
|
43
|
+
def dom_class
|
|
44
|
+
node['class']
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def pretty_print(q) # :nodoc:
|
|
48
|
+
q.object_group(self) {
|
|
49
|
+
q.breakable; q.pp text
|
|
50
|
+
q.breakable; q.pp href
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
alias inspect pretty_inspect # :nodoc:
|
|
55
|
+
|
|
56
|
+
# A list of words in the rel attribute, all lower-cased.
|
|
57
|
+
def rel
|
|
58
|
+
@rel ||= (val = attributes['rel']) ? val.downcase.split(' ') : []
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Test if the rel attribute includes +kind+.
|
|
62
|
+
def rel? kind
|
|
63
|
+
rel.include? kind
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The text content of this link
|
|
67
|
+
def text
|
|
68
|
+
return @text if @text
|
|
69
|
+
|
|
70
|
+
@text = @node.inner_text
|
|
71
|
+
|
|
72
|
+
# If there is no text, try to find an image and use it's alt text
|
|
73
|
+
if (@text.nil? or @text.empty?) and imgs = @node.search('img') then
|
|
74
|
+
@text = imgs.map do |e|
|
|
75
|
+
e['alt']
|
|
76
|
+
end.join
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
@text
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
alias :to_s :text
|
|
83
|
+
|
|
84
|
+
# A URI for the #href for this link. The link is first parsed as a raw
|
|
85
|
+
# link. If that fails parsing an escaped link is attepmted.
|
|
86
|
+
|
|
87
|
+
def uri
|
|
88
|
+
@uri ||= if @href then
|
|
89
|
+
begin
|
|
90
|
+
URI.parse @href
|
|
91
|
+
rescue URI::InvalidURIError
|
|
92
|
+
URI.parse WEBrick::HTTPUtils.escape @href
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
end
|
|
98
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
##
|
|
2
|
+
# This class encapsulates a meta element with a refresh http-equiv. Mechanize
|
|
3
|
+
# treats meta refresh elements just like 'a' tags. MetaRefresh objects will
|
|
4
|
+
# contain links, but most likely will have no text.
|
|
5
|
+
|
|
6
|
+
class Mechanize::Page::MetaRefresh < Mechanize::Page::Link
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Time to wait before next refresh
|
|
10
|
+
|
|
11
|
+
attr_reader :delay
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# This MetaRefresh links did not contain a url= in the content attribute and
|
|
15
|
+
# links to itself.
|
|
16
|
+
|
|
17
|
+
attr_reader :link_self
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# Matches the content attribute of a meta refresh element. After the match:
|
|
21
|
+
#
|
|
22
|
+
# $1:: delay
|
|
23
|
+
# $3:: url
|
|
24
|
+
|
|
25
|
+
CONTENT_REGEXP = /^\s*(\d+\.?\d*)(;|;\s*url=\s*['"]?(\S*?)['"]?)?\s*$/i
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Parses the delay and url from the content attribute of a meta refresh
|
|
29
|
+
# element. Parse requires the uri of the current page to infer a url when
|
|
30
|
+
# no url is specified.
|
|
31
|
+
#
|
|
32
|
+
# Returns an array of [delay, url]. (both in string)
|
|
33
|
+
#
|
|
34
|
+
# Returns nil if the delay and url cannot be parsed.
|
|
35
|
+
|
|
36
|
+
def self.parse content, base_uri
|
|
37
|
+
return unless content =~ CONTENT_REGEXP
|
|
38
|
+
|
|
39
|
+
link_self = $3.nil? || $3.empty?
|
|
40
|
+
delay, refresh_uri = $1, $3
|
|
41
|
+
|
|
42
|
+
dest = base_uri
|
|
43
|
+
dest += refresh_uri if refresh_uri
|
|
44
|
+
|
|
45
|
+
return delay, dest, link_self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.from_node node, page, uri
|
|
49
|
+
http_equiv = node['http-equiv']
|
|
50
|
+
return unless http_equiv and http_equiv.downcase == 'refresh'
|
|
51
|
+
|
|
52
|
+
delay, uri, link_self = parse node['content'], uri
|
|
53
|
+
|
|
54
|
+
return unless delay
|
|
55
|
+
|
|
56
|
+
new node, page, delay, uri.to_s, link_self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def initialize node, page, delay, href, link_self = false
|
|
60
|
+
super node, page.mech, page
|
|
61
|
+
|
|
62
|
+
@delay = delay =~ /\./ ? delay.to_f : delay.to_i
|
|
63
|
+
@href = href
|
|
64
|
+
@link_self = link_self
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
##
|
|
2
|
+
# The parser module provides standard methods for accessing the headers and
|
|
3
|
+
# content of a response that are shared across pluggable parsers.
|
|
4
|
+
|
|
5
|
+
module Mechanize::Parser
|
|
6
|
+
|
|
7
|
+
extend Forwardable
|
|
8
|
+
|
|
9
|
+
special_filenames = Regexp.union %w[
|
|
10
|
+
AUX
|
|
11
|
+
COM1
|
|
12
|
+
COM2
|
|
13
|
+
COM3
|
|
14
|
+
COM4
|
|
15
|
+
COM5
|
|
16
|
+
COM6
|
|
17
|
+
COM7
|
|
18
|
+
COM8
|
|
19
|
+
COM9
|
|
20
|
+
CON
|
|
21
|
+
LPT1
|
|
22
|
+
LPT2
|
|
23
|
+
LPT3
|
|
24
|
+
LPT4
|
|
25
|
+
LPT5
|
|
26
|
+
LPT6
|
|
27
|
+
LPT7
|
|
28
|
+
LPT8
|
|
29
|
+
LPT9
|
|
30
|
+
NUL
|
|
31
|
+
PRN
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Special filenames that must be escaped
|
|
36
|
+
|
|
37
|
+
SPECIAL_FILENAMES = /\A#{special_filenames}/i
|
|
38
|
+
|
|
39
|
+
##
|
|
40
|
+
# The URI this file was retrieved from
|
|
41
|
+
|
|
42
|
+
attr_accessor :uri
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# The Mechanize::Headers for this file
|
|
46
|
+
|
|
47
|
+
attr_accessor :response
|
|
48
|
+
|
|
49
|
+
alias header response
|
|
50
|
+
|
|
51
|
+
##
|
|
52
|
+
# The HTTP response code
|
|
53
|
+
|
|
54
|
+
attr_accessor :code
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# :method: [](header)
|
|
58
|
+
#
|
|
59
|
+
# Access HTTP +header+ by name
|
|
60
|
+
|
|
61
|
+
def_delegator :header, :[], :[]
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
# :method: []=(header, value)
|
|
65
|
+
#
|
|
66
|
+
# Set HTTP +header+ to +value+
|
|
67
|
+
|
|
68
|
+
def_delegator :header, :[]=, :[]=
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# :method: key?(header)
|
|
72
|
+
#
|
|
73
|
+
# Is the named +header+ present?
|
|
74
|
+
|
|
75
|
+
def_delegator :header, :key?, :key?
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# :method: each
|
|
79
|
+
#
|
|
80
|
+
# Enumerate HTTP headers
|
|
81
|
+
|
|
82
|
+
def_delegator :header, :each, :each
|
|
83
|
+
|
|
84
|
+
##
|
|
85
|
+
# :method: each
|
|
86
|
+
#
|
|
87
|
+
# Enumerate HTTP headers in capitalized (canonical) form
|
|
88
|
+
|
|
89
|
+
def_delegator :header, :canonical_each, :canonical_each
|
|
90
|
+
|
|
91
|
+
##
|
|
92
|
+
# Extracts the filename from a Content-Disposition header in the #response
|
|
93
|
+
# or from the URI. If +full_path+ is true the filename will include the
|
|
94
|
+
# host name and path to the resource, otherwise a filename in the current
|
|
95
|
+
# directory is given.
|
|
96
|
+
|
|
97
|
+
def extract_filename full_path = @full_path
|
|
98
|
+
handled = false
|
|
99
|
+
|
|
100
|
+
if @uri then
|
|
101
|
+
uri = @uri
|
|
102
|
+
uri += 'index.html' if uri.path.end_with? '/'
|
|
103
|
+
|
|
104
|
+
path = uri.path.split(/\//)
|
|
105
|
+
filename = path.pop || 'index.html'
|
|
106
|
+
else
|
|
107
|
+
path = []
|
|
108
|
+
filename = 'index.html'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Set the filename
|
|
112
|
+
if disposition = @response['content-disposition'] then
|
|
113
|
+
content_disposition =
|
|
114
|
+
Mechanize::HTTP::ContentDispositionParser.parse disposition
|
|
115
|
+
|
|
116
|
+
if content_disposition then
|
|
117
|
+
filename = content_disposition.filename
|
|
118
|
+
filename = filename.split(/[\\\/]/).last
|
|
119
|
+
handled = true
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
if not handled and @uri then
|
|
124
|
+
filename << '.html' unless filename =~ /\./
|
|
125
|
+
filename << "?#{@uri.query}" if @uri.query
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if SPECIAL_FILENAMES =~ filename then
|
|
129
|
+
filename = "_#{filename}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
filename = filename.tr "\x00-\x20<>:\"/\\|?*", '_'
|
|
133
|
+
|
|
134
|
+
@filename = if full_path then
|
|
135
|
+
File.join @uri.host, path, filename
|
|
136
|
+
else
|
|
137
|
+
filename
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
##
|
|
142
|
+
# Creates a Mechanize::Header from the Net::HTTPResponse +response+.
|
|
143
|
+
#
|
|
144
|
+
# This allows the Net::HTTPResponse to be garbage collected sooner.
|
|
145
|
+
|
|
146
|
+
def fill_header response
|
|
147
|
+
@response = Mechanize::Headers.new
|
|
148
|
+
|
|
149
|
+
response.each { |k,v|
|
|
150
|
+
@response[k] = v
|
|
151
|
+
} if response
|
|
152
|
+
|
|
153
|
+
@response
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
##
|
|
157
|
+
# Finds a free filename based on +filename+, but is not race-free
|
|
158
|
+
|
|
159
|
+
def find_free_name filename
|
|
160
|
+
filename = @filename unless filename
|
|
161
|
+
|
|
162
|
+
number = 1
|
|
163
|
+
|
|
164
|
+
while File.exist? filename do
|
|
165
|
+
filename = "#{@filename}.#{number}"
|
|
166
|
+
number += 1
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
filename
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
end
|
|
173
|
+
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
require 'mechanize/file'
|
|
2
|
+
require 'mechanize/file_saver'
|
|
3
|
+
require 'mechanize/page'
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# This class is used to register and maintain pluggable parsers for Mechanize
|
|
7
|
+
# to use.
|
|
8
|
+
#
|
|
9
|
+
# Mechanize allows different parsers for different content types. Mechanize
|
|
10
|
+
# uses PluggableParser to determine which parser to use for any content type.
|
|
11
|
+
# To use your own pluggable parser or to change the default pluggable parsers,
|
|
12
|
+
# register them with this class.
|
|
13
|
+
#
|
|
14
|
+
# The default parser for unregistered content types is Mechanize::File.
|
|
15
|
+
#
|
|
16
|
+
# The module Mechanize::Parser provides basic functionality for any content
|
|
17
|
+
# type, so you may use it in custom parsers you write. For small files you
|
|
18
|
+
# wish to perform in-memory operations on, you should subclass
|
|
19
|
+
# Mechanize::File. For large files you should subclass Mechanize::Download as
|
|
20
|
+
# the content is only loaded into memory in small chunks.
|
|
21
|
+
#
|
|
22
|
+
# == Example
|
|
23
|
+
#
|
|
24
|
+
# To create your own parser, just create a class that takes four parameters in
|
|
25
|
+
# the constructor. Here is an example of registering a pluggable parser that
|
|
26
|
+
# handles CSV files:
|
|
27
|
+
#
|
|
28
|
+
# require 'csv'
|
|
29
|
+
#
|
|
30
|
+
# class CSVParser < Mechanize::File
|
|
31
|
+
# attr_reader :csv
|
|
32
|
+
#
|
|
33
|
+
# def initialize uri = nil, response = nil, body = nil, code = nil
|
|
34
|
+
# super uri, response, body, code
|
|
35
|
+
# @csv = CSV.parse body
|
|
36
|
+
# end
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# agent = Mechanize.new
|
|
40
|
+
# agent.pluggable_parser.csv = CSVParser
|
|
41
|
+
# agent.get('http://example.com/test.csv') # => CSVParser
|
|
42
|
+
#
|
|
43
|
+
# Now any response with a content type of 'text/csv' will initialize a
|
|
44
|
+
# CSVParser and return that object to the caller.
|
|
45
|
+
#
|
|
46
|
+
# To register a pluggable parser for a content type that pluggable parser does
|
|
47
|
+
# not know about, use the hash syntax:
|
|
48
|
+
#
|
|
49
|
+
# agent.pluggable_parser['text/something'] = SomeClass
|
|
50
|
+
#
|
|
51
|
+
# To set the default parser, use #default:
|
|
52
|
+
#
|
|
53
|
+
# agent.pluggable_parser.default = Mechanize::Download
|
|
54
|
+
#
|
|
55
|
+
# Now all unknown content types will be saved to disk and not loaded into
|
|
56
|
+
# memory.
|
|
57
|
+
|
|
58
|
+
class Mechanize::PluggableParser
|
|
59
|
+
|
|
60
|
+
CONTENT_TYPES = {
|
|
61
|
+
:html => 'text/html',
|
|
62
|
+
:wap => 'application/vnd.wap.xhtml+xml',
|
|
63
|
+
:xhtml => 'application/xhtml+xml',
|
|
64
|
+
:pdf => 'application/pdf',
|
|
65
|
+
:csv => 'text/csv',
|
|
66
|
+
:xml => 'text/xml',
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
attr_accessor :default
|
|
70
|
+
|
|
71
|
+
def initialize
|
|
72
|
+
@parsers = {
|
|
73
|
+
CONTENT_TYPES[:html] => Mechanize::Page,
|
|
74
|
+
CONTENT_TYPES[:xhtml] => Mechanize::Page,
|
|
75
|
+
CONTENT_TYPES[:wap] => Mechanize::Page,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@default = Mechanize::File
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
##
|
|
82
|
+
# Returns the parser registered for the given +content_type+
|
|
83
|
+
|
|
84
|
+
def parser(content_type)
|
|
85
|
+
content_type.nil? ? default : @parsers[content_type] || default
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def register_parser(content_type, klass) # :nodoc:
|
|
89
|
+
@parsers[content_type] = klass
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
# Registers +klass+ as the parser for text/html and application/xhtml+xml
|
|
94
|
+
# content
|
|
95
|
+
|
|
96
|
+
def html=(klass)
|
|
97
|
+
register_parser(CONTENT_TYPES[:html], klass)
|
|
98
|
+
register_parser(CONTENT_TYPES[:xhtml], klass)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
##
|
|
102
|
+
# Registers +klass+ as the parser for application/xhtml+xml content
|
|
103
|
+
|
|
104
|
+
def xhtml=(klass)
|
|
105
|
+
register_parser(CONTENT_TYPES[:xhtml], klass)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
##
|
|
109
|
+
# Registers +klass+ as the parser for application/pdf content
|
|
110
|
+
|
|
111
|
+
def pdf=(klass)
|
|
112
|
+
register_parser(CONTENT_TYPES[:pdf], klass)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
##
|
|
116
|
+
# Registers +klass+ as the parser for text/csv content
|
|
117
|
+
|
|
118
|
+
def csv=(klass)
|
|
119
|
+
register_parser(CONTENT_TYPES[:csv], klass)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
##
|
|
123
|
+
# Registers +klass+ as the parser for text/xml content
|
|
124
|
+
|
|
125
|
+
def xml=(klass)
|
|
126
|
+
register_parser(CONTENT_TYPES[:xml], klass)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
##
|
|
130
|
+
# Retrieves the parser for +content_type+ content
|
|
131
|
+
|
|
132
|
+
def [](content_type)
|
|
133
|
+
@parsers[content_type]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
##
|
|
137
|
+
# Sets the parser for +content_type+ content to +klass+
|
|
138
|
+
|
|
139
|
+
def []=(content_type, klass)
|
|
140
|
+
@parsers[content_type] = klass
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
end
|
|
144
|
+
|