actionview 4.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +274 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +34 -0
- data/lib/action_view.rb +97 -0
- data/lib/action_view/base.rb +205 -0
- data/lib/action_view/buffers.rb +49 -0
- data/lib/action_view/context.rb +36 -0
- data/lib/action_view/dependency_tracker.rb +93 -0
- data/lib/action_view/digestor.rb +116 -0
- data/lib/action_view/flows.rb +76 -0
- data/lib/action_view/helpers.rb +64 -0
- data/lib/action_view/helpers/active_model_helper.rb +49 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +322 -0
- data/lib/action_view/helpers/asset_url_helper.rb +355 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +203 -0
- data/lib/action_view/helpers/cache_helper.rb +200 -0
- data/lib/action_view/helpers/capture_helper.rb +216 -0
- data/lib/action_view/helpers/controller_helper.rb +25 -0
- data/lib/action_view/helpers/csrf_helper.rb +30 -0
- data/lib/action_view/helpers/date_helper.rb +1075 -0
- data/lib/action_view/helpers/debug_helper.rb +39 -0
- data/lib/action_view/helpers/form_helper.rb +1876 -0
- data/lib/action_view/helpers/form_options_helper.rb +843 -0
- data/lib/action_view/helpers/form_tag_helper.rb +746 -0
- data/lib/action_view/helpers/javascript_helper.rb +75 -0
- data/lib/action_view/helpers/number_helper.rb +425 -0
- data/lib/action_view/helpers/output_safety_helper.rb +38 -0
- data/lib/action_view/helpers/record_tag_helper.rb +108 -0
- data/lib/action_view/helpers/rendering_helper.rb +90 -0
- data/lib/action_view/helpers/sanitize_helper.rb +256 -0
- data/lib/action_view/helpers/tag_helper.rb +176 -0
- data/lib/action_view/helpers/tags.rb +41 -0
- data/lib/action_view/helpers/tags/base.rb +148 -0
- data/lib/action_view/helpers/tags/check_box.rb +64 -0
- data/lib/action_view/helpers/tags/checkable.rb +16 -0
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +44 -0
- data/lib/action_view/helpers/tags/collection_helpers.rb +85 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +36 -0
- data/lib/action_view/helpers/tags/collection_select.rb +28 -0
- data/lib/action_view/helpers/tags/color_field.rb +25 -0
- data/lib/action_view/helpers/tags/date_field.rb +13 -0
- data/lib/action_view/helpers/tags/date_select.rb +72 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +22 -0
- data/lib/action_view/helpers/tags/datetime_local_field.rb +19 -0
- data/lib/action_view/helpers/tags/datetime_select.rb +8 -0
- data/lib/action_view/helpers/tags/email_field.rb +8 -0
- data/lib/action_view/helpers/tags/file_field.rb +8 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +29 -0
- data/lib/action_view/helpers/tags/hidden_field.rb +8 -0
- data/lib/action_view/helpers/tags/label.rb +65 -0
- data/lib/action_view/helpers/tags/month_field.rb +13 -0
- data/lib/action_view/helpers/tags/number_field.rb +18 -0
- data/lib/action_view/helpers/tags/password_field.rb +12 -0
- data/lib/action_view/helpers/tags/radio_button.rb +31 -0
- data/lib/action_view/helpers/tags/range_field.rb +8 -0
- data/lib/action_view/helpers/tags/search_field.rb +24 -0
- data/lib/action_view/helpers/tags/select.rb +41 -0
- data/lib/action_view/helpers/tags/tel_field.rb +8 -0
- data/lib/action_view/helpers/tags/text_area.rb +18 -0
- data/lib/action_view/helpers/tags/text_field.rb +29 -0
- data/lib/action_view/helpers/tags/time_field.rb +13 -0
- data/lib/action_view/helpers/tags/time_select.rb +8 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +20 -0
- data/lib/action_view/helpers/tags/url_field.rb +8 -0
- data/lib/action_view/helpers/tags/week_field.rb +13 -0
- data/lib/action_view/helpers/text_helper.rb +447 -0
- data/lib/action_view/helpers/translation_helper.rb +111 -0
- data/lib/action_view/helpers/url_helper.rb +625 -0
- data/lib/action_view/layouts.rb +426 -0
- data/lib/action_view/locale/en.yml +56 -0
- data/lib/action_view/log_subscriber.rb +44 -0
- data/lib/action_view/lookup_context.rb +249 -0
- data/lib/action_view/model_naming.rb +12 -0
- data/lib/action_view/path_set.rb +77 -0
- data/lib/action_view/railtie.rb +49 -0
- data/lib/action_view/record_identifier.rb +84 -0
- data/lib/action_view/renderer/abstract_renderer.rb +47 -0
- data/lib/action_view/renderer/partial_renderer.rb +492 -0
- data/lib/action_view/renderer/renderer.rb +50 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +103 -0
- data/lib/action_view/renderer/template_renderer.rb +96 -0
- data/lib/action_view/rendering.rb +145 -0
- data/lib/action_view/routing_url_for.rb +109 -0
- data/lib/action_view/tasks/dependencies.rake +17 -0
- data/lib/action_view/template.rb +340 -0
- data/lib/action_view/template/error.rb +141 -0
- data/lib/action_view/template/handlers.rb +53 -0
- data/lib/action_view/template/handlers/builder.rb +26 -0
- data/lib/action_view/template/handlers/erb.rb +145 -0
- data/lib/action_view/template/handlers/raw.rb +11 -0
- data/lib/action_view/template/resolver.rb +329 -0
- data/lib/action_view/template/text.rb +34 -0
- data/lib/action_view/template/types.rb +57 -0
- data/lib/action_view/test_case.rb +272 -0
- data/lib/action_view/testing/resolvers.rb +50 -0
- data/lib/action_view/vendor/html-scanner.rb +20 -0
- data/lib/action_view/vendor/html-scanner/html/document.rb +68 -0
- data/lib/action_view/vendor/html-scanner/html/node.rb +532 -0
- data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +188 -0
- data/lib/action_view/vendor/html-scanner/html/selector.rb +830 -0
- data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +107 -0
- data/lib/action_view/vendor/html-scanner/html/version.rb +11 -0
- data/lib/action_view/version.rb +11 -0
- data/lib/action_view/view_paths.rb +96 -0
- metadata +218 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module ActionView #:nodoc:
|
2
|
+
# = Action View Text Template
|
3
|
+
class Template
|
4
|
+
class Text #:nodoc:
|
5
|
+
attr_accessor :type
|
6
|
+
|
7
|
+
def initialize(string, type = nil)
|
8
|
+
@string = string.to_s
|
9
|
+
@type = Types[type] || type if type
|
10
|
+
@type ||= Types[:text]
|
11
|
+
end
|
12
|
+
|
13
|
+
def identifier
|
14
|
+
'text template'
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
'text template'
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_str
|
22
|
+
@string
|
23
|
+
end
|
24
|
+
|
25
|
+
def render(*args)
|
26
|
+
to_str
|
27
|
+
end
|
28
|
+
|
29
|
+
def formats
|
30
|
+
[@type.to_sym]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
3
|
+
|
4
|
+
module ActionView
|
5
|
+
class Template
|
6
|
+
class Types
|
7
|
+
class Type
|
8
|
+
cattr_accessor :types
|
9
|
+
self.types = Set.new
|
10
|
+
|
11
|
+
def self.register(*t)
|
12
|
+
types.merge(t.map { |type| type.to_s })
|
13
|
+
end
|
14
|
+
|
15
|
+
register :html, :text, :js, :css, :xml, :json
|
16
|
+
|
17
|
+
def self.[](type)
|
18
|
+
return type if type.is_a?(self)
|
19
|
+
|
20
|
+
if type.is_a?(Symbol) || types.member?(type.to_s)
|
21
|
+
new(type)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :symbol
|
26
|
+
|
27
|
+
def initialize(symbol)
|
28
|
+
@symbol = symbol.to_sym
|
29
|
+
end
|
30
|
+
|
31
|
+
delegate :to_s, :to_sym, :to => :symbol
|
32
|
+
alias to_str to_s
|
33
|
+
|
34
|
+
def ref
|
35
|
+
to_sym || to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def ==(type)
|
39
|
+
return false if type.blank?
|
40
|
+
symbol.to_sym == type.to_sym
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
cattr_accessor :type_klass
|
45
|
+
|
46
|
+
def self.delegate_to(klass)
|
47
|
+
self.type_klass = klass
|
48
|
+
end
|
49
|
+
|
50
|
+
delegate_to Type
|
51
|
+
|
52
|
+
def self.[](type)
|
53
|
+
type_klass[type]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
require 'active_support/core_ext/module/remove_method'
|
2
|
+
require 'action_controller'
|
3
|
+
require 'action_controller/test_case'
|
4
|
+
require 'action_view'
|
5
|
+
|
6
|
+
module ActionView
|
7
|
+
# = Action View Test Case
|
8
|
+
class TestCase < ActiveSupport::TestCase
|
9
|
+
class TestController < ActionController::Base
|
10
|
+
include ActionDispatch::TestProcess
|
11
|
+
|
12
|
+
attr_accessor :request, :response, :params
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_writer :controller_path
|
16
|
+
end
|
17
|
+
|
18
|
+
def controller_path=(path)
|
19
|
+
self.class.controller_path=(path)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
super
|
24
|
+
self.class.controller_path = ""
|
25
|
+
@request = ActionController::TestRequest.new
|
26
|
+
@response = ActionController::TestResponse.new
|
27
|
+
|
28
|
+
@request.env.delete('PATH_INFO')
|
29
|
+
@params = {}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module Behavior
|
34
|
+
extend ActiveSupport::Concern
|
35
|
+
|
36
|
+
include ActionDispatch::Assertions, ActionDispatch::TestProcess
|
37
|
+
include ActionController::TemplateAssertions
|
38
|
+
include ActionView::Context
|
39
|
+
|
40
|
+
include ActionDispatch::Routing::PolymorphicRoutes
|
41
|
+
|
42
|
+
include AbstractController::Helpers
|
43
|
+
include ActionView::Helpers
|
44
|
+
include ActionView::RecordIdentifier
|
45
|
+
include ActionView::RoutingUrlFor
|
46
|
+
|
47
|
+
include ActiveSupport::Testing::ConstantLookup
|
48
|
+
|
49
|
+
delegate :lookup_context, :to => :controller
|
50
|
+
attr_accessor :controller, :output_buffer, :rendered
|
51
|
+
|
52
|
+
module ClassMethods
|
53
|
+
def tests(helper_class)
|
54
|
+
case helper_class
|
55
|
+
when String, Symbol
|
56
|
+
self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize
|
57
|
+
when Module
|
58
|
+
self.helper_class = helper_class
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def determine_default_helper_class(name)
|
63
|
+
determine_constant_from_test_name(name) do |constant|
|
64
|
+
Module === constant && !(Class === constant)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def helper_method(*methods)
|
69
|
+
# Almost a duplicate from ActionController::Helpers
|
70
|
+
methods.flatten.each do |method|
|
71
|
+
_helpers.module_eval <<-end_eval
|
72
|
+
def #{method}(*args, &block) # def current_user(*args, &block)
|
73
|
+
_test_case.send(%(#{method}), *args, &block) # _test_case.send(%(current_user), *args, &block)
|
74
|
+
end # end
|
75
|
+
end_eval
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
attr_writer :helper_class
|
80
|
+
|
81
|
+
def helper_class
|
82
|
+
@helper_class ||= determine_default_helper_class(name)
|
83
|
+
end
|
84
|
+
|
85
|
+
def new(*)
|
86
|
+
include_helper_modules!
|
87
|
+
super
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def include_helper_modules!
|
93
|
+
helper(helper_class) if helper_class
|
94
|
+
include _helpers
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
def setup_with_controller
|
100
|
+
@controller = ActionView::TestCase::TestController.new
|
101
|
+
@request = @controller.request
|
102
|
+
@output_buffer = ActiveSupport::SafeBuffer.new
|
103
|
+
@rendered = ''
|
104
|
+
|
105
|
+
make_test_case_available_to_view!
|
106
|
+
say_no_to_protect_against_forgery!
|
107
|
+
end
|
108
|
+
|
109
|
+
def config
|
110
|
+
@controller.config if @controller.respond_to?(:config)
|
111
|
+
end
|
112
|
+
|
113
|
+
def render(options = {}, local_assigns = {}, &block)
|
114
|
+
view.assign(view_assigns)
|
115
|
+
@rendered << output = view.render(options, local_assigns, &block)
|
116
|
+
output
|
117
|
+
end
|
118
|
+
|
119
|
+
def rendered_views
|
120
|
+
@_rendered_views ||= RenderedViewsCollection.new
|
121
|
+
end
|
122
|
+
|
123
|
+
class RenderedViewsCollection
|
124
|
+
def initialize
|
125
|
+
@rendered_views ||= Hash.new { |hash, key| hash[key] = [] }
|
126
|
+
end
|
127
|
+
|
128
|
+
def add(view, locals)
|
129
|
+
@rendered_views[view] ||= []
|
130
|
+
@rendered_views[view] << locals
|
131
|
+
end
|
132
|
+
|
133
|
+
def locals_for(view)
|
134
|
+
@rendered_views[view]
|
135
|
+
end
|
136
|
+
|
137
|
+
def rendered_views
|
138
|
+
@rendered_views.keys
|
139
|
+
end
|
140
|
+
|
141
|
+
def view_rendered?(view, expected_locals)
|
142
|
+
locals_for(view).any? do |actual_locals|
|
143
|
+
expected_locals.all? {|key, value| value == actual_locals[key] }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
included do
|
149
|
+
setup :setup_with_controller
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
# Support the selector assertions
|
155
|
+
#
|
156
|
+
# Need to experiment if this priority is the best one: rendered => output_buffer
|
157
|
+
def response_from_page
|
158
|
+
HTML::Document.new(@rendered.blank? ? @output_buffer : @rendered).root
|
159
|
+
end
|
160
|
+
|
161
|
+
def say_no_to_protect_against_forgery!
|
162
|
+
_helpers.module_eval do
|
163
|
+
remove_possible_method :protect_against_forgery?
|
164
|
+
def protect_against_forgery?
|
165
|
+
false
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def make_test_case_available_to_view!
|
171
|
+
test_case_instance = self
|
172
|
+
_helpers.module_eval do
|
173
|
+
unless private_method_defined?(:_test_case)
|
174
|
+
define_method(:_test_case) { test_case_instance }
|
175
|
+
private :_test_case
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
module Locals
|
181
|
+
attr_accessor :rendered_views
|
182
|
+
|
183
|
+
def render(options = {}, local_assigns = {})
|
184
|
+
case options
|
185
|
+
when Hash
|
186
|
+
if block_given?
|
187
|
+
rendered_views.add options[:layout], options[:locals]
|
188
|
+
elsif options.key?(:partial)
|
189
|
+
rendered_views.add options[:partial], options[:locals]
|
190
|
+
end
|
191
|
+
else
|
192
|
+
rendered_views.add options, local_assigns
|
193
|
+
end
|
194
|
+
|
195
|
+
super
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# The instance of ActionView::Base that is used by +render+.
|
200
|
+
def view
|
201
|
+
@view ||= begin
|
202
|
+
view = @controller.view_context
|
203
|
+
view.singleton_class.send :include, _helpers
|
204
|
+
view.extend(Locals)
|
205
|
+
view.rendered_views = self.rendered_views
|
206
|
+
view.output_buffer = self.output_buffer
|
207
|
+
view
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
alias_method :_view, :view
|
212
|
+
|
213
|
+
INTERNAL_IVARS = [
|
214
|
+
:@NAME,
|
215
|
+
:@failures,
|
216
|
+
:@assertions,
|
217
|
+
:@__io__,
|
218
|
+
:@_assertion_wrapped,
|
219
|
+
:@_assertions,
|
220
|
+
:@_result,
|
221
|
+
:@_routes,
|
222
|
+
:@controller,
|
223
|
+
:@_layouts,
|
224
|
+
:@_files,
|
225
|
+
:@_rendered_views,
|
226
|
+
:@method_name,
|
227
|
+
:@output_buffer,
|
228
|
+
:@_partials,
|
229
|
+
:@passed,
|
230
|
+
:@rendered,
|
231
|
+
:@request,
|
232
|
+
:@routes,
|
233
|
+
:@tagged_logger,
|
234
|
+
:@_templates,
|
235
|
+
:@options,
|
236
|
+
:@test_passed,
|
237
|
+
:@view,
|
238
|
+
:@view_context_class
|
239
|
+
]
|
240
|
+
|
241
|
+
def _user_defined_ivars
|
242
|
+
instance_variables - INTERNAL_IVARS
|
243
|
+
end
|
244
|
+
|
245
|
+
# Returns a Hash of instance variables and their values, as defined by
|
246
|
+
# the user in the test case, which are then assigned to the view being
|
247
|
+
# rendered. This is generally intended for internal use and extension
|
248
|
+
# frameworks.
|
249
|
+
def view_assigns
|
250
|
+
Hash[_user_defined_ivars.map do |ivar|
|
251
|
+
[ivar[1..-1].to_sym, instance_variable_get(ivar)]
|
252
|
+
end]
|
253
|
+
end
|
254
|
+
|
255
|
+
def _routes
|
256
|
+
@controller._routes if @controller.respond_to?(:_routes)
|
257
|
+
end
|
258
|
+
|
259
|
+
def method_missing(selector, *args)
|
260
|
+
if @controller.respond_to?(:_routes) &&
|
261
|
+
( @controller._routes.named_routes.helpers.include?(selector) ||
|
262
|
+
@controller._routes.mounted_helpers.method_defined?(selector) )
|
263
|
+
@controller.__send__(selector, *args)
|
264
|
+
else
|
265
|
+
super
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
include Behavior
|
271
|
+
end
|
272
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'action_view/template/resolver'
|
2
|
+
|
3
|
+
module ActionView #:nodoc:
|
4
|
+
# Use FixtureResolver in your tests to simulate the presence of files on the
|
5
|
+
# file system. This is used internally by Rails' own test suite, and is
|
6
|
+
# useful for testing extensions that have no way of knowing what the file
|
7
|
+
# system will look like at runtime.
|
8
|
+
class FixtureResolver < PathResolver
|
9
|
+
attr_reader :hash
|
10
|
+
|
11
|
+
def initialize(hash = {}, pattern=nil)
|
12
|
+
super(pattern)
|
13
|
+
@hash = hash
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
@hash.keys.join(', ')
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def query(path, exts, formats)
|
23
|
+
query = ""
|
24
|
+
EXTENSIONS.each_key do |ext|
|
25
|
+
query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)'
|
26
|
+
end
|
27
|
+
query = /^(#{Regexp.escape(path)})#{query}$/
|
28
|
+
|
29
|
+
templates = []
|
30
|
+
@hash.each do |_path, array|
|
31
|
+
source, updated_at = array
|
32
|
+
next unless _path =~ query
|
33
|
+
handler, format = extract_handler_and_format(_path, formats)
|
34
|
+
templates << Template.new(source, _path, handler,
|
35
|
+
:virtual_path => path.virtual, :format => format, :updated_at => updated_at)
|
36
|
+
end
|
37
|
+
|
38
|
+
templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class NullResolver < PathResolver
|
43
|
+
def query(path, exts, formats)
|
44
|
+
handler, format = extract_handler_and_format(path, formats)
|
45
|
+
[ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format)]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/html-scanner"
|
2
|
+
|
3
|
+
module HTML
|
4
|
+
extend ActiveSupport::Autoload
|
5
|
+
|
6
|
+
eager_autoload do
|
7
|
+
autoload :CDATA, 'html/node'
|
8
|
+
autoload :Document, 'html/document'
|
9
|
+
autoload :FullSanitizer, 'html/sanitizer'
|
10
|
+
autoload :LinkSanitizer, 'html/sanitizer'
|
11
|
+
autoload :Node, 'html/node'
|
12
|
+
autoload :Sanitizer, 'html/sanitizer'
|
13
|
+
autoload :Selector, 'html/selector'
|
14
|
+
autoload :Tag, 'html/node'
|
15
|
+
autoload :Text, 'html/node'
|
16
|
+
autoload :Tokenizer, 'html/tokenizer'
|
17
|
+
autoload :Version, 'html/version'
|
18
|
+
autoload :WhiteListSanitizer, 'html/sanitizer'
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'html/tokenizer'
|
2
|
+
require 'html/node'
|
3
|
+
require 'html/selector'
|
4
|
+
require 'html/sanitizer'
|
5
|
+
|
6
|
+
module HTML #:nodoc:
|
7
|
+
# A top-level HTML document. You give it a body of text, and it will parse that
|
8
|
+
# text into a tree of nodes.
|
9
|
+
class Document #:nodoc:
|
10
|
+
|
11
|
+
# The root of the parsed document.
|
12
|
+
attr_reader :root
|
13
|
+
|
14
|
+
# Create a new Document from the given text.
|
15
|
+
def initialize(text, strict=false, xml=false)
|
16
|
+
tokenizer = Tokenizer.new(text)
|
17
|
+
@root = Node.new(nil)
|
18
|
+
node_stack = [ @root ]
|
19
|
+
while token = tokenizer.next
|
20
|
+
node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token, strict)
|
21
|
+
|
22
|
+
node_stack.last.children << node unless node.tag? && node.closing == :close
|
23
|
+
if node.tag?
|
24
|
+
if node_stack.length > 1 && node.closing == :close
|
25
|
+
if node_stack.last.name == node.name
|
26
|
+
if node_stack.last.children.empty?
|
27
|
+
node_stack.last.children << Text.new(node_stack.last, node.line, node.position, "")
|
28
|
+
end
|
29
|
+
node_stack.pop
|
30
|
+
else
|
31
|
+
open_start = node_stack.last.position - 20
|
32
|
+
open_start = 0 if open_start < 0
|
33
|
+
close_start = node.position - 20
|
34
|
+
close_start = 0 if close_start < 0
|
35
|
+
msg = <<EOF.strip
|
36
|
+
ignoring attempt to close #{node_stack.last.name} with #{node.name}
|
37
|
+
opened at byte #{node_stack.last.position}, line #{node_stack.last.line}
|
38
|
+
closed at byte #{node.position}, line #{node.line}
|
39
|
+
attributes at open: #{node_stack.last.attributes.inspect}
|
40
|
+
text around open: #{text[open_start,40].inspect}
|
41
|
+
text around close: #{text[close_start,40].inspect}
|
42
|
+
EOF
|
43
|
+
strict ? raise(msg) : warn(msg)
|
44
|
+
end
|
45
|
+
elsif !node.childless?(xml) && node.closing != :close
|
46
|
+
node_stack.push node
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Search the tree for (and return) the first node that matches the given
|
53
|
+
# conditions. The conditions are interpreted differently for different node
|
54
|
+
# types, see HTML::Text#find and HTML::Tag#find.
|
55
|
+
def find(conditions)
|
56
|
+
@root.find(conditions)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Search the tree for (and return) all nodes that match the given
|
60
|
+
# conditions. The conditions are interpreted differently for different node
|
61
|
+
# types, see HTML::Text#find and HTML::Tag#find.
|
62
|
+
def find_all(conditions)
|
63
|
+
@root.find_all(conditions)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|