masterview 0.2.5 → 0.3.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.
- data/CHANGELOG +31 -1
- data/README +70 -69
- data/RELEASE_NOTES +70 -64
- data/Rakefile +26 -27
- data/TODO +13 -29
- data/doc/about.html +246 -0
- data/doc/configuration.html +49 -36
- data/doc/developer.html +423 -41
- data/doc/directives.html +139 -51
- data/doc/guide.html +19 -9
- data/doc/index.html +90 -224
- data/doc/installation.html +36 -28
- data/doc/media_list.html +30 -20
- data/doc/simple_diagram.html +3 -5
- data/doc/stylesheets/masterview.css +16 -1
- data/examples/rails_app_config/masterview/settings.rb +2 -1
- data/init.rb +1 -1
- data/lib/#ChangeLog# +6 -0
- data/lib/masterview/analyzer.rb +48 -34
- data/lib/masterview/attr_string_parser.rb +5 -1
- data/lib/masterview/case_insensitive_hash.rb +69 -0
- data/lib/masterview/{pathname_extensions.rb → core_ext/pathname.rb} +0 -0
- data/lib/masterview/{string_extensions.rb → core_ext/string.rb} +0 -0
- data/lib/masterview/deprecated/directive_base.rb +362 -0
- data/lib/masterview/directive_base.rb +201 -179
- data/lib/masterview/directive_dsl.rb +457 -0
- data/lib/masterview/directive_helpers.rb +28 -141
- data/lib/masterview/directive_load_path.rb +388 -0
- data/lib/masterview/directive_metadata.rb +377 -0
- data/lib/masterview/directive_registry.rb +259 -69
- data/lib/masterview/directives/.metadata +16 -0
- data/lib/masterview/directives/attr.rb +9 -8
- data/lib/masterview/directives/block.rb +11 -14
- data/lib/masterview/directives/check_box.rb +13 -18
- data/lib/masterview/directives/collection_select.rb +15 -29
- data/lib/masterview/directives/content.rb +9 -3
- data/lib/masterview/directives/else.rb +15 -13
- data/lib/masterview/directives/elsif.rb +14 -13
- data/lib/masterview/directives/eval.rb +20 -0
- data/lib/masterview/directives/form.rb +56 -9
- data/lib/masterview/directives/form_remote.rb +26 -0
- data/lib/masterview/directives/global_inline_erb.rb +10 -14
- data/lib/masterview/directives/hidden_field.rb +11 -20
- data/lib/masterview/directives/if.rb +13 -12
- data/lib/masterview/directives/image_tag.rb +20 -28
- data/lib/masterview/directives/import.rb +5 -12
- data/lib/masterview/directives/import_render.rb +7 -19
- data/lib/masterview/directives/insert_generated_comment.rb +8 -11
- data/lib/masterview/directives/javascript_include.rb +21 -12
- data/lib/masterview/directives/link_to.rb +14 -8
- data/lib/masterview/directives/link_to_function.rb +22 -0
- data/lib/masterview/directives/link_to_if.rb +15 -13
- data/lib/masterview/directives/link_to_remote.rb +13 -8
- data/lib/masterview/directives/omit_tag.rb +32 -16
- data/lib/masterview/directives/password_field.rb +10 -22
- data/lib/masterview/directives/radio_button.rb +11 -22
- data/lib/masterview/directives/replace.rb +7 -8
- data/lib/masterview/directives/select.rb +11 -24
- data/lib/masterview/directives/stylesheet_link.rb +20 -12
- data/lib/masterview/directives/submit.rb +11 -5
- data/lib/masterview/directives/text_area.rb +10 -23
- data/lib/masterview/directives/text_field.rb +10 -22
- data/lib/masterview/exceptions.rb +21 -0
- data/lib/masterview/extras/app/controllers/masterview_controller.rb +102 -75
- data/lib/masterview/extras/app/views/layouts/masterview_admin.rhtml +24 -23
- data/lib/masterview/extras/app/views/layouts/masterview_admin_config.rhtml +81 -0
- data/lib/masterview/extras/app/views/masterview/admin/configuration.rhtml +5 -1
- data/lib/masterview/extras/app/views/masterview/admin/create.rhtml +2 -2
- data/lib/masterview/extras/app/views/masterview/admin/directives.rhtml +5 -0
- data/lib/masterview/extras/app/views/masterview/admin/features.rhtml +5 -79
- data/lib/masterview/extras/app/views/masterview/admin/interact.rhtml +5 -0
- data/lib/masterview/extras/app/views/masterview/admin/list.rhtml +3 -71
- data/lib/masterview/extras/init_mv_admin_pages.rb +42 -23
- data/lib/masterview/filter_helpers.rb +26 -0
- data/lib/masterview/initializer.rb +99 -53
- data/lib/masterview/io.rb +19 -15
- data/lib/masterview/keyword_expander.rb +7 -2
- data/lib/masterview/masterview_info.rb +229 -23
- data/lib/masterview/masterview_version.rb +2 -2
- data/lib/masterview/parser.rb +275 -105
- data/lib/masterview/parser_helpers.rb +54 -0
- data/lib/masterview/rails_ext/action_controller_erb_direct.rb +29 -0
- data/lib/masterview/rails_ext/action_controller_reparse_checking.rb +27 -0
- data/lib/masterview/{extras/init_rails_erb_mv_direct.rb → rails_ext/action_view_erb_direct.rb} +12 -59
- data/lib/masterview/template_spec.rb +3 -2
- data/lib/masterview.rb +21 -12
- data/lib/rexml/parsers/baseparser_with_doctype_fix.rb +473 -0
- data/lib/rexml/parsers/sax2parser_with_doctype_fix.rb +243 -0
- data/test/directive_test_helper.rb +135 -0
- data/test/fixtures/directives/id_check.rb +18 -0
- data/test/fixtures/directives/test_directive_events.rb +70 -0
- data/test/test_helper.rb +18 -5
- data/test/tmp/views/layouts/product.rhtml +10 -10
- data/test/tmp/views/product/_form.rhtml +4 -4
- data/test/tmp/views/product/_product.rhtml +3 -3
- data/test/tmp/views/product/destroy.rhtml +5 -5
- data/test/tmp/views/product/edit.rhtml +4 -4
- data/test/tmp/views/product/list.rhtml +3 -3
- data/test/tmp/views/product/new.rhtml +4 -4
- data/test/tmp/views/product/show.rhtml +2 -2
- data/test/unit/attr_string_parser_test.rb +105 -0
- data/test/unit/case_insensitive_hash_mod_test.rb +104 -0
- data/test/unit/config_settings_test.rb +13 -1
- data/test/unit/default_generate_mio_filter_test.rb +3 -3
- data/test/unit/deprecated_directive_base_test.rb +30 -0
- data/test/unit/directive_attr_test.rb +111 -35
- data/test/unit/directive_base_test.rb +520 -1
- data/test/unit/directive_block_test.rb +30 -22
- data/test/unit/directive_content_test.rb +24 -11
- data/test/unit/directive_else_test.rb +18 -15
- data/test/unit/directive_elsif_test.rb +17 -15
- data/test/unit/directive_form_remote_test.rb +59 -0
- data/test/unit/directive_form_test.rb +31 -39
- data/test/unit/directive_global_inline_erb_test.rb +28 -17
- data/test/unit/directive_grid_test_notready.rb +38 -0
- data/test/unit/directive_helpers_test.rb +39 -0
- data/test/unit/directive_hidden_field_test.rb +44 -29
- data/test/unit/directive_if_test.rb +10 -7
- data/test/unit/directive_image_tag_test.rb +69 -61
- data/test/unit/directive_import_render_test.rb +28 -38
- data/test/unit/directive_import_test.rb +16 -14
- data/test/unit/directive_insert_generated_comment_test.rb +32 -0
- data/test/unit/directive_javascript_include_test.rb +40 -43
- data/test/unit/directive_link_to_function_test.rb +40 -0
- data/test/unit/directive_link_to_if_test.rb +52 -12
- data/test/unit/directive_link_to_remote_test.rb +58 -0
- data/test/unit/directive_link_to_test.rb +46 -31
- data/test/unit/directive_load_path_test.rb +257 -0
- data/test/unit/directive_metadata_test.rb +313 -0
- data/test/unit/directive_omit_tag_test.rb +73 -21
- data/test/unit/directive_password_field_test.rb +44 -38
- data/test/unit/directive_registry_test.rb +44 -0
- data/test/unit/directive_replace_test.rb +28 -12
- data/test/unit/directive_stylesheet_link_test.rb +43 -36
- data/test/unit/directive_submit_test.rb +29 -30
- data/test/unit/directive_text_area_test.rb +40 -36
- data/test/unit/directive_text_field_test.rb +44 -38
- data/test/unit/example_directive_child_events_test.rb +41 -0
- data/test/unit/example_test.rb +31 -4
- data/test/unit/file_mio_test.rb +18 -13
- data/test/unit/filter_helpers_test.rb +10 -8
- data/test/unit/find_directive_parent_test.rb +174 -0
- data/test/unit/keyword_expander_test.rb +4 -2
- data/test/unit/mio_test.rb +18 -11
- data/test/unit/mtime_string_hash_mio_tree_test.rb +5 -1
- data/test/unit/parser_test.rb +41 -29
- data/test/unit/pathname_extensions_test.rb +1 -1
- data/test/unit/run_parser_test.rb +2 -2
- data/test/unit/simplified_directive_base_test.rb +256 -0
- data/test/unit/string_hash_mio_test.rb +5 -1
- data/test/unit/template_file_watcher_test.rb +2 -2
- data/test/unit/template_test.rb +221 -46
- metadata +86 -45
- data/lib/masterview/directives/testfilter.rb +0 -55
- data/lib/masterview/extras/init_rails_reparse_checking.rb +0 -62
data/lib/masterview/parser.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
module MasterView
|
2
2
|
|
3
|
+
# The TemplateProcessing module contains the internal mechanisms
|
4
|
+
# for the MasterView template document Parser
|
5
|
+
module TemplateProcessing
|
6
|
+
|
3
7
|
class RenderLevel #contains render modes, each gen level
|
4
8
|
attr_accessor :render_modes
|
5
9
|
|
@@ -81,6 +85,11 @@ module MasterView
|
|
81
85
|
end
|
82
86
|
end
|
83
87
|
|
88
|
+
# List of directives.
|
89
|
+
#
|
90
|
+
# Constructed with the directives for a template document element,
|
91
|
+
# sorted into processing order by directive priority level
|
92
|
+
#
|
84
93
|
class DirectiveSet
|
85
94
|
attr_accessor :directives
|
86
95
|
def initialize
|
@@ -90,7 +99,7 @@ module MasterView
|
|
90
99
|
def <<(directive)
|
91
100
|
@directives << directive
|
92
101
|
@directives.flatten!
|
93
|
-
self
|
102
|
+
self
|
94
103
|
end
|
95
104
|
|
96
105
|
def determine_dcs(method_name)
|
@@ -110,10 +119,15 @@ module MasterView
|
|
110
119
|
directive.send(method_name_sym, dcs)
|
111
120
|
end
|
112
121
|
end
|
113
|
-
|
114
|
-
end
|
115
122
|
|
123
|
+
end
|
116
124
|
|
125
|
+
# Default renderer for document elements, invoked after any other directives
|
126
|
+
# on a document element have been processed.
|
127
|
+
#
|
128
|
+
# Unless preempted by a previously invoked directive, the default rendering
|
129
|
+
# is to copy the element content to the output.
|
130
|
+
#
|
117
131
|
class SimpleRenderHandler
|
118
132
|
def description
|
119
133
|
'SimpleRenderHandler is the default renderer for nodes, it should be invoked as the last directive and will output node normally'
|
@@ -122,9 +136,20 @@ module MasterView
|
|
122
136
|
def stag(dcs)
|
123
137
|
context = dcs.context
|
124
138
|
ret = []
|
139
|
+
# NOTE: although strictly speaking attribute order is not significant
|
140
|
+
# in XML, in practice some template authors may have a preferred style
|
141
|
+
# for the order in which they write attribute markup in their templates.
|
142
|
+
# In such cases, it would be nice to be able to support a config option
|
143
|
+
# to preserve original attribute order, rather than the forced sort-by-name
|
144
|
+
# as done here. However, would need to get into the parser
|
145
|
+
# and have it use a facets Dictionary rather than standard {} Hash
|
146
|
+
# when collecting element attributes so we have order available at this point.
|
147
|
+
# Hook: rexml/parsers/baseparser.rb - pull method, :start_element event
|
148
|
+
# http://facets.rubyforge.org/api/more/classes/Dictionary.html
|
149
|
+
# [DJL 18-Jul-2006]
|
125
150
|
ret << "<#{context[:tag].tag_name.to_s}" # allow for symbol tag_name
|
126
151
|
sorted_attributes = context[:tag].attributes.sort { |a,b| a[0].to_s <=> b[0].to_s } #allow for symbols using to_s
|
127
|
-
sorted_attributes.each do |name, value|
|
152
|
+
sorted_attributes.each do |name, value|
|
128
153
|
ret << " #{name.to_s}=\"#{value}\"" # allow for key to by symbol
|
129
154
|
end
|
130
155
|
ret << '>' #must output as separate string so simplify_empty_elements can find it
|
@@ -139,7 +164,7 @@ module MasterView
|
|
139
164
|
context = dcs.context
|
140
165
|
[] << '<!-- ' << context[:content_part] << ' -->'
|
141
166
|
end
|
142
|
-
|
167
|
+
|
143
168
|
def cdata(dcs)
|
144
169
|
context = dcs.context
|
145
170
|
[] << '<![CDATA[' << context[:content_part] << ']]>'
|
@@ -149,26 +174,49 @@ module MasterView
|
|
149
174
|
context = dcs.context
|
150
175
|
[] << '</' << "#{context[:tag].tag_name.to_s}>" #must output </ as separate string so simplify_empty_elements can find it
|
151
176
|
end
|
152
|
-
|
177
|
+
|
153
178
|
end
|
154
179
|
|
155
|
-
|
180
|
+
# Records information about an element in the document.
|
181
|
+
#
|
182
|
+
# A Tag is constructed when the start tag of an element is encountered.
|
183
|
+
# It tracks the name of the tag along with any attributes and
|
184
|
+
# MasterView directives attached to this element which are invoked
|
185
|
+
# during the rendering processing.
|
186
|
+
#
|
187
|
+
#--
|
188
|
+
# ISSUE: better name for this class would be Element or TemplateElement.
|
189
|
+
# Tag is something the element *has* (in addition to its attributes and content),
|
190
|
+
# it's not what it *is*. [Deb 22-Sep-2006]
|
191
|
+
#++
|
192
|
+
#
|
193
|
+
class Tag
|
156
194
|
attr_accessor :directives, :tag_name, :attributes, :mode_type, :stag, :content, :etag, :parent
|
157
|
-
|
195
|
+
attr_accessor :prolog, :renderer
|
196
|
+
def initialize(directives, tag_name, attributes, mode_type, parent, prolog, renderer)
|
158
197
|
@tag_name = tag_name
|
159
|
-
|
198
|
+
|
199
|
+
unless attributes.nil? or attributes.is_a?( CaseInsensitiveHash ) # not a CaseInsensitiveHash so make it one
|
200
|
+
@attributes = CaseInsensitiveHash.new
|
201
|
+
@attributes.replace(attributes) # copy values in
|
202
|
+
else
|
203
|
+
@attributes = attributes
|
204
|
+
end
|
205
|
+
|
160
206
|
@mode_type = mode_type
|
161
207
|
@directives = directives
|
162
208
|
@stag = []
|
163
209
|
@content = []
|
164
210
|
@etag = []
|
165
211
|
@parent = parent
|
212
|
+
@prolog = prolog
|
213
|
+
@renderer = renderer
|
166
214
|
end
|
167
215
|
|
168
|
-
# creates a tag context using tag itself and mode type, also merge in any additional
|
216
|
+
# creates a tag context using tag itself and mode type, also merge in any additional
|
169
217
|
# values passed in via values hash
|
170
218
|
def create_context( values = {} )
|
171
|
-
{
|
219
|
+
{
|
172
220
|
:tag => self,
|
173
221
|
:mode_type => @mode_type
|
174
222
|
}.merge!(values)
|
@@ -179,8 +227,6 @@ module MasterView
|
|
179
227
|
end
|
180
228
|
end
|
181
229
|
|
182
|
-
|
183
|
-
|
184
230
|
class MIOSerializer
|
185
231
|
def initialize(options)
|
186
232
|
raise RequiredArgumentMissingError.new("Required argument is missing, specify the MasterViewIO object in options[:output_mio_tree]") unless options[:output_mio_tree]
|
@@ -190,6 +236,7 @@ module MasterView
|
|
190
236
|
|
191
237
|
def serialize(render_mode, tag)
|
192
238
|
data_to_write = tag.data.join
|
239
|
+
data_to_write = tag.prolog + "\n" + data_to_write unless tag.prolog.nil? #prepend prolog if exists
|
193
240
|
@mio_tree.path(render_mode.output).write(data_to_write, @options)
|
194
241
|
end
|
195
242
|
end
|
@@ -205,9 +252,9 @@ module MasterView
|
|
205
252
|
end
|
206
253
|
end
|
207
254
|
|
208
|
-
# Serializer which simply outputs each fragment to a hash with the key representing
|
255
|
+
# Serializer which simply outputs each fragment to a hash with the key representing
|
209
256
|
# the path and the value the string contents.
|
210
|
-
# You may specify this serializer as an option to the parser (:serializer => HashSerializer.new(output_hash)).
|
257
|
+
# You may specify this serializer as an option to the parser (:serializer => HashSerializer.new(output_hash)).
|
211
258
|
# It takes an empty hash as the single constructor parameter to which the contents will be output.
|
212
259
|
class HashSerializer
|
213
260
|
def initialize( output_hash )
|
@@ -222,25 +269,42 @@ module MasterView
|
|
222
269
|
end
|
223
270
|
=end
|
224
271
|
|
272
|
+
# A Renderer is configured for a template parser to control the output
|
273
|
+
# generated from the template source document. The renderer is responsible
|
274
|
+
# for managing directive processing and output generation in response
|
275
|
+
# to document event notifications from the SAX source document parser.
|
276
|
+
#
|
225
277
|
class Renderer
|
226
278
|
include DirectiveHelpers
|
227
279
|
|
228
280
|
# Set of element names that can be simplified. Used in simplify_empty_elements.
|
229
281
|
# Only simplify elements that are specified in the DTD as being empty, collapsing others can cause parsing
|
230
|
-
# or rendering problems in browsers.
|
282
|
+
# or rendering problems in browsers.
|
231
283
|
# http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_XHTML-1.0-Strict
|
232
|
-
# base, meta, link, hr, br, param, img, area, input, col
|
284
|
+
# base, meta, link, hr, br, param, img, area, input, col
|
233
285
|
XHTMLEmptyElementNameSet = %w{ base meta link hr br param img area input col }.to_set
|
234
286
|
|
235
|
-
attr_reader :
|
236
|
-
|
287
|
+
attr_reader :options
|
288
|
+
attr_reader :template_pathname, :template_full_pathname
|
289
|
+
|
290
|
+
attr_reader :keyword_expander, :default_extension
|
291
|
+
attr_accessor :render_levels, :default_render_handler, :serializer
|
292
|
+
|
293
|
+
attr_reader :directive_load_path #:nodoc:
|
237
294
|
attr_reader :directives_registry #:nodoc:
|
238
295
|
|
239
|
-
|
296
|
+
attr_reader :mv_generate_attr, :mv_gen_partial_attr, :mv_gen_replace_attr
|
297
|
+
attr_reader :mv_insert_generated_comment_attr
|
298
|
+
|
299
|
+
# array of strings containing the xml prolog for a doc, on the root
|
300
|
+
# this might contain xml declaration and doctype, etc.
|
301
|
+
attr_accessor :prolog
|
240
302
|
|
241
303
|
def initialize( options = {} )
|
242
|
-
@@last_renderer = self; #save last renderer for convenient access
|
243
304
|
@options = options
|
305
|
+
@template_pathname = options[:template_pathname]
|
306
|
+
@template_full_pathname = IOMgr.template.path(self.template_pathname).full_pathname if self.template_pathname
|
307
|
+
|
244
308
|
@default_render_handler = SimpleRenderHandler.new
|
245
309
|
@render_levels = [
|
246
310
|
RenderLevel.new( [RenderMode.new] )
|
@@ -248,59 +312,64 @@ module MasterView
|
|
248
312
|
|
249
313
|
serializer = options[:serializer] || DefaultSerializer
|
250
314
|
self.serializer = serializer.is_a?(Class) ? serializer.new(options) : serializer #one can pass in Serializer class or an instance
|
251
|
-
|
252
|
-
self.template_pathname = options[:template_pathname]
|
253
|
-
self.template_full_pathname = IOMgr.template.path(self.template_pathname).full_pathname if self.template_pathname
|
315
|
+
|
254
316
|
@default_extension = (options[:output_mio_tree]) ? options[:output_mio_tree].default_extension : IOMgr.erb.default_extension
|
255
317
|
@keyword_expander = KeywordExpander.new
|
256
318
|
@keyword_expander.set_template_pathname(self.template_pathname, @default_extension)
|
257
|
-
|
258
|
-
# (then we don't need to keep re-checking in DirectiveRegistry or other processing)
|
259
|
-
# [DJL 04-Jul-2006]
|
260
|
-
self.directive_load_paths = ( DefaultDirectiveLoadPaths << options[:additional_directive_paths] ).flatten
|
319
|
+
self.initialize_directives( options[:directive_load_path] )
|
261
320
|
end
|
262
321
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
322
|
+
# Process the directive load_path, re-requiring any new entries
|
323
|
+
# and preparing processing context for the current template.
|
324
|
+
#
|
325
|
+
# Note that any directives that were already required (and loaded)
|
326
|
+
# will still be in memory because these are not reset.
|
327
|
+
# (i.e., there is not currently a cache vs. reload config option for
|
328
|
+
# directive implementations in the way that rails provides for class loading).
|
329
|
+
#
|
330
|
+
def initialize_directives( load_path ) #:nodoc:
|
267
331
|
|
268
|
-
|
269
|
-
# already required (and loaded) will still be in memory because these are not reset.
|
270
|
-
def directive_load_paths=( directive_paths )
|
332
|
+
using_current_load_path = (load_path == MasterView::DirectiveLoadPath.current)
|
271
333
|
|
272
|
-
|
273
|
-
#
|
274
|
-
|
275
|
-
# given that the directives load path is configurable for the client app?
|
276
|
-
# [DJL 04-Jul-2006]
|
277
|
-
directives_registry.load_directives( directive_paths ) #?? if directive_load_paths != DefaultDirectiveLoadPaths ??
|
334
|
+
# ordinary configuration sets up std load path and directives registry for the app session
|
335
|
+
# ??provide hook here for testing alt load paths and config overrides? is this right??
|
336
|
+
@directives_registry = using_current_load_path ? MasterView::DirectiveRegistry.current : MasterView::DirectiveRegistry.new
|
278
337
|
|
279
|
-
#
|
280
|
-
#
|
281
|
-
# [DJL
|
282
|
-
|
283
|
-
|
284
|
-
|
338
|
+
#ISSUE: do we really need/want to run this o n *every* parse?
|
339
|
+
# Probably ought to have some cache config option like dev/production class loading
|
340
|
+
# [DJL 02-Oct-2006]
|
341
|
+
@directives_registry.process_directives_load_path( load_path ) #?? unless using_current_load_path??
|
342
|
+
|
343
|
+
# bind the names of the standard mv template-manipulation directives
|
344
|
+
mv_ns = @directives_registry.mv_namespace_prefix
|
345
|
+
@mv_generate_attr = mv_ns+'generate'
|
346
|
+
@mv_gen_partial_attr = mv_ns+'gen_partial'
|
347
|
+
@mv_gen_replace_attr = mv_ns+'gen_replace'
|
348
|
+
@mv_insert_generated_comment_attr = mv_ns+'insert_generated_comment'
|
349
|
+
#??@mv_import_attr = mv_ns+'import'
|
350
|
+
#??@mv_import_render_attr = mv_ns+'import_render'
|
285
351
|
|
286
|
-
# only need to do the following if :additional_directive_paths or nonstd @mv_ns???
|
287
|
-
directives_registry.build_directive_maps( @mv_ns )
|
288
|
-
|
289
352
|
end
|
290
353
|
|
291
354
|
def modes
|
292
355
|
@render_levels.last.render_modes
|
293
356
|
end
|
357
|
+
|
294
358
|
def push_level(render_level)
|
295
359
|
@render_levels.push render_level
|
296
360
|
end
|
297
361
|
|
298
362
|
def push_tag(tag_name, attributes)
|
363
|
+
tag_prolog = nil
|
364
|
+
unless prolog.nil?
|
365
|
+
tag_prolog = prolog.join("\n")
|
366
|
+
self.prolog = nil # clear now that we have used it, only needs to be applied to this tag
|
367
|
+
end
|
299
368
|
modes.each do |mode|
|
300
369
|
attributes_copy = attributes.clone #these get changed in select_active_directives
|
301
370
|
directives = select_active_directives(tag_name, attributes_copy, mode)
|
302
371
|
parent = (mode.tags.empty?) ? nil : mode.tag
|
303
|
-
mode.tags.push Tag.new(directives, tag_name, attributes_copy, mode.mode_type, parent)
|
372
|
+
mode.tags.push Tag.new(directives, tag_name, attributes_copy, mode.mode_type, parent, tag_prolog, self)
|
304
373
|
mode.tag.stag << mode.render_directives(:stag)
|
305
374
|
end
|
306
375
|
end
|
@@ -314,7 +383,7 @@ module MasterView
|
|
314
383
|
end
|
315
384
|
|
316
385
|
#does not call any directives, direct output
|
317
|
-
def append_raw(raw_output)
|
386
|
+
def append_raw(raw_output)
|
318
387
|
modes.each do |mode|
|
319
388
|
if mode.tag
|
320
389
|
mode.tag.content << raw_output
|
@@ -350,10 +419,10 @@ module MasterView
|
|
350
419
|
# Simplify (collapse) empty elements so that <foo></foo> from rexml parsing ends up being <foo/> .
|
351
420
|
# Only simplify elements that are specified in the DTD as being empty, collapsing others can cause parsing
|
352
421
|
# or rendering problems in browsers. Uses constant XHTMLEmptyElementNameSet to find elements that should be
|
353
|
-
# collapsed.
|
422
|
+
# collapsed.
|
354
423
|
# http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_XHTML-1.0-Strict
|
355
424
|
# xhtml-1.0-Strict empty elements are:
|
356
|
-
# base, meta, link, hr, br, param, img, area, input, col
|
425
|
+
# base, meta, link, hr, br, param, img, area, input, col
|
357
426
|
def simplify_empty_elements(content) #relies on the fact that > and </ are individual strings and are back to back with nothing in between
|
358
427
|
ret = []
|
359
428
|
current_element_name = nil
|
@@ -361,7 +430,7 @@ module MasterView
|
|
361
430
|
last = nil
|
362
431
|
content.flatten!
|
363
432
|
content.each do |item|
|
364
|
-
if next_to_last == '>' && last == '</' && XHTMLEmptyElementNameSet.include?(current_element_name)
|
433
|
+
if next_to_last == '>' && last == '</' && XHTMLEmptyElementNameSet.include?(current_element_name)
|
365
434
|
ret.pop #remove '>'
|
366
435
|
ret.pop #remove '</'
|
367
436
|
ret << ' />' # adding in a space to make xhtml more compatible with html editors and older browsers
|
@@ -387,43 +456,116 @@ module MasterView
|
|
387
456
|
|
388
457
|
def select_active_directives(tag_name, attributes, mode)
|
389
458
|
selected = DirectiveSet.new
|
390
|
-
directive_processors = directives_registry.construct_directive_processors( attributes )
|
391
|
-
sorted_directives = directive_processors.sort do |
|
392
|
-
|
393
|
-
yval = (y.respond_to?(:priority)) ? y.priority : DirectivePriorities::Medium
|
394
|
-
xval <=> yval
|
459
|
+
directive_processors = directives_registry.construct_directive_processors( tag_name, attributes )
|
460
|
+
sorted_directives = directive_processors.sort do |dp1,dp2|
|
461
|
+
dp1.priority <=> dp2.priority
|
395
462
|
end
|
396
463
|
sorted_directives << @default_render_handler #insure this is last
|
397
464
|
selected << sorted_directives
|
398
465
|
end
|
399
466
|
|
400
|
-
|
467
|
+
# initialize (if necessary) and append string to xml_prolog array
|
468
|
+
def append_to_prolog(str)
|
469
|
+
self.prolog ||= []
|
470
|
+
self.prolog << str
|
471
|
+
end
|
401
472
|
|
473
|
+
end
|
402
474
|
|
403
|
-
|
475
|
+
# SAX parser event handler. Handles the low-level events
|
476
|
+
# from the SAX parser processing an XML document and
|
477
|
+
# passes the element tag and content data off to a
|
478
|
+
# MasterViewer::TemplateProcessing::Renderer for template processing.
|
479
|
+
#
|
480
|
+
class SAXParserListener
|
404
481
|
include REXML::SAX2Listener
|
405
482
|
include DirectiveHelpers
|
483
|
+
include ParserHelpers
|
406
484
|
|
407
485
|
def initialize( options = {} )
|
408
486
|
@renderer = Renderer.new(options)
|
409
487
|
end
|
410
488
|
|
411
489
|
def xmldecl(version, encoding, standalone)
|
412
|
-
|
490
|
+
xmldecl = []
|
491
|
+
xmldecl << '<?xml'
|
492
|
+
xmldecl << %Q[version="#{version}"] unless version.nil?
|
493
|
+
xmldecl << %Q[encoding="#{encoding}"] unless encoding.nil?
|
494
|
+
xmldecl << %Q[standalone="#{standalone}"] unless standalone.nil?
|
495
|
+
xmldecl << '?>'
|
496
|
+
@renderer.append_to_prolog( xmldecl.join(' ') )
|
413
497
|
end
|
414
498
|
|
415
499
|
def start_document
|
416
500
|
#todo
|
417
501
|
end
|
418
502
|
|
419
|
-
|
503
|
+
# Handles a doctype declaration. Any attributes of the doctype which are
|
504
|
+
# not supplied will be nil. # EG, <!DOCTYPE me PUBLIC "foo" "bar">
|
505
|
+
# @p name the name of the doctype; EG, "me"
|
506
|
+
# @p pub_sys "PUBLIC", "SYSTEM", or nil. EG, "PUBLIC"
|
507
|
+
# @p long_name the supplied long name, or nil. EG, "foo"
|
508
|
+
# @p uri the uri of the doctype, or nil. EG, "bar"
|
509
|
+
def doctype(name, pub_sys, long_name, uri)
|
510
|
+
xml_doctype = []
|
511
|
+
xml_doctype << '<!DOCTYPE'
|
512
|
+
xml_doctype << name
|
513
|
+
xml_doctype << pub_sys unless pub_sys.nil?
|
514
|
+
xml_doctype << %Q["#{long_name}"] unless long_name.nil?
|
515
|
+
xml_doctype << %Q["#{uri}"] unless uri.nil?
|
516
|
+
doctype_str = xml_doctype.join(' ')+'>'
|
517
|
+
@renderer.append_to_prolog( doctype_str )
|
518
|
+
end
|
519
|
+
|
520
|
+
def processing_instruction target, data
|
521
|
+
#todo
|
522
|
+
end
|
523
|
+
|
524
|
+
# If a doctype includes an ATTLIST declaration, it will cause this
|
525
|
+
# method to be called. The content is the declaration itself, unparsed.
|
526
|
+
# EG, <!ATTLIST el attr CDATA #REQUIRED> will come to this method as "el
|
527
|
+
# attr CDATA #REQUIRED". This is the same for all of the .*decl
|
528
|
+
# methods.
|
529
|
+
def attlistdecl(element, pairs, contents)
|
420
530
|
#todo
|
421
531
|
end
|
422
532
|
|
533
|
+
# <!ELEMENT ...>
|
534
|
+
def elementdecl content
|
535
|
+
#todo
|
536
|
+
end
|
537
|
+
|
538
|
+
# <!ENTITY ...>
|
539
|
+
# The argument passed to this method is an array of the entity
|
540
|
+
# declaration. It can be in a number of formats, but in general it
|
541
|
+
# returns (example, result):
|
542
|
+
# <!ENTITY % YN '"Yes"'>
|
543
|
+
# ["%", "YN", "'\"Yes\"'", "\""]
|
544
|
+
# <!ENTITY % YN 'Yes'>
|
545
|
+
# ["%", "YN", "'Yes'", "s"]
|
546
|
+
# <!ENTITY WhatHeSaid "He said %YN;">
|
547
|
+
# ["WhatHeSaid", "\"He said %YN;\"", "YN"]
|
548
|
+
# <!ENTITY open-hatch SYSTEM "http://www.textuality.com/boilerplate/OpenHatch.xml">
|
549
|
+
# ["open-hatch", "SYSTEM", "\"http://www.textuality.com/boilerplate/OpenHatch.xml\""]
|
550
|
+
# <!ENTITY open-hatch PUBLIC "-//Textuality//TEXT Standard open-hatch boilerplate//EN" "http://www.textuality.com/boilerplate/OpenHatch.xml">
|
551
|
+
# ["open-hatch", "PUBLIC", "\"-//Textuality//TEXT Standard open-hatch boilerplate//EN\"", "\"http://www.textuality.com/boilerplate/OpenHatch.xml\""]
|
552
|
+
# <!ENTITY hatch-pic SYSTEM "../grafix/OpenHatch.gif" NDATA gif>
|
553
|
+
# ["hatch-pic", "SYSTEM", "\"../grafix/OpenHatch.gif\"", "\n\t\t\t\t\t\t\tNDATA gif", "gif"]
|
554
|
+
def entitydecl content
|
555
|
+
#todo
|
556
|
+
end
|
557
|
+
|
558
|
+
# <!NOTATION ...>
|
559
|
+
def notationdecl content
|
560
|
+
puts 'in notationdecl'
|
561
|
+
end
|
562
|
+
|
423
563
|
def start_element(uri, localname, qname, attributes)
|
424
564
|
unescape_attributes!(attributes)
|
425
|
-
|
426
|
-
|
565
|
+
ci_attributes = CaseInsensitiveHash.new
|
566
|
+
ci_attributes.replace(attributes) #populate from original hash
|
567
|
+
push_levels(ci_attributes)
|
568
|
+
@renderer.push_tag(qname, ci_attributes)
|
427
569
|
end
|
428
570
|
|
429
571
|
def characters(text)
|
@@ -454,41 +596,41 @@ module MasterView
|
|
454
596
|
end
|
455
597
|
|
456
598
|
def generate_replace(value)
|
457
|
-
@renderer.append_raw
|
599
|
+
@renderer.append_raw ERB_EVAL_START+value+ERB_EVAL_END
|
458
600
|
end
|
459
601
|
|
460
602
|
# handle a mv:gen_partial attribute, which calls generate and outputs a token
|
461
603
|
# it takes an optional :dir => 'foo/bar' which is prepended to partial path,
|
462
|
-
# otherwise it just uses what is in partial.
|
604
|
+
# otherwise it just uses what is in partial.
|
463
605
|
# This creates a generate attribute value which will be used later.
|
464
606
|
# Parameters
|
465
607
|
# value = attribute value for gen_partial
|
466
608
|
# attributes = all remaining attributes hash
|
467
609
|
def handle_gen_partial(attributes)
|
468
|
-
value = @renderer.keyword_expander.resolveAttrAndDelete(attributes, @renderer.
|
469
|
-
if value
|
610
|
+
value = @renderer.keyword_expander.resolveAttrAndDelete(attributes, @renderer.mv_gen_partial_attr)
|
611
|
+
if value
|
470
612
|
prepend_dir = find_string_val_in_string_hash(value, :dir) #only used for masterview
|
471
|
-
partial = find_string_val_in_string_hash(value, :partial)
|
613
|
+
partial = find_string_val_in_string_hash(value, :partial)
|
472
614
|
return if partial.nil?
|
473
615
|
path = render_partial_name_to_file_name(partial, @renderer.default_extension)
|
474
616
|
path = File.join(prepend_dir, path) if prepend_dir
|
475
|
-
generate_attribute = attributes[@renderer.
|
617
|
+
generate_attribute = attributes[@renderer.mv_generate_attr] || '' # check if we need to add to existing generate
|
476
618
|
generate_attribute = path + (generate_attribute.blank? ? '' : ', '+generate_attribute)
|
477
|
-
attributes[@renderer.
|
478
|
-
@renderer.append_raw(
|
619
|
+
attributes[@renderer.mv_generate_attr] = generate_attribute
|
620
|
+
@renderer.append_raw( ERB_CONTENT_START+'render( '+value+' )'+ERB_CONTENT_END )
|
479
621
|
end
|
480
622
|
end
|
481
623
|
|
482
624
|
def push_levels(attributes)
|
483
625
|
handle_gen_partial(attributes)
|
484
|
-
|
485
|
-
gen_replace = @renderer.keyword_expander.resolveAttrAndDelete(attributes, @renderer.
|
626
|
+
|
627
|
+
gen_replace = @renderer.keyword_expander.resolveAttrAndDelete(attributes, @renderer.mv_gen_replace_attr) #get and delete from map
|
486
628
|
generate_replace( gen_replace ) unless gen_replace.nil?
|
487
629
|
|
488
|
-
gen = @renderer.keyword_expander.resolveAttrAndDelete(attributes, @renderer.
|
630
|
+
gen = @renderer.keyword_expander.resolveAttrAndDelete(attributes, @renderer.mv_generate_attr) #get and delete from map
|
489
631
|
if gen
|
490
632
|
omit_comment = @renderer.options[:omit_comment] || OmitGeneratedComments || File.extname(gen) != MasterView::IOMgr.erb.default_extension
|
491
|
-
attributes[@renderer.
|
633
|
+
attributes[@renderer.mv_insert_generated_comment_attr] = @renderer.template_full_pathname.to_s unless omit_comment #add the comment directive, so it will be written to each gen'd file
|
492
634
|
render_level = nil
|
493
635
|
gen_values = parse_eval_into_hash(gen, :normal)
|
494
636
|
|
@@ -499,7 +641,7 @@ module MasterView
|
|
499
641
|
arr_values = (value.is_a?(Enumerable)) ? value : [value] #if not enumerable add it to array
|
500
642
|
value.each do |path|
|
501
643
|
path.strip!
|
502
|
-
#Log.debug { ('pushing mode='+mode_type.to_s+' path='+path).indent(2*@renderer.render_levels.size) }
|
644
|
+
#Log.debug { ('pushing mode='+mode_type.to_s+' path='+path).indent(2*@renderer.render_levels.size) }
|
503
645
|
render_level ||= RenderLevel.new
|
504
646
|
render_level.push RenderMode.new(path, mode_type)
|
505
647
|
end
|
@@ -508,51 +650,79 @@ module MasterView
|
|
508
650
|
end
|
509
651
|
|
510
652
|
end
|
511
|
-
|
653
|
+
|
512
654
|
end
|
513
655
|
|
656
|
+
end
|
657
|
+
|
658
|
+
# The Parser processes a template document containing MasterView directives markup.
|
659
|
+
#
|
660
|
+
# Processing options can be specified to control pre-processing of the template
|
661
|
+
# (e.g., <code>:tidy</code> to run HTML Tidy) as well as the operation of
|
662
|
+
# the parse operation.
|
663
|
+
#
|
514
664
|
class Parser
|
515
|
-
|
516
|
-
|
665
|
+
|
666
|
+
# Parse a template document contained in an IO source.
|
667
|
+
def self.parse_mio( template_mio, output_mio_tree, options={} )
|
668
|
+
options = options.clone() # don't munge the client's copy of this
|
669
|
+
options[:template_pathname] = template_mio.pathname
|
517
670
|
options[:output_mio_tree] = output_mio_tree
|
518
671
|
template = template_mio.read
|
519
672
|
self.parse( template, options )
|
520
673
|
end
|
521
674
|
|
522
|
-
|
523
|
-
#
|
675
|
+
# Parse a MasterView template and render output.
|
676
|
+
#
|
524
677
|
# template param is actual template source passed in as string or array.
|
525
|
-
# options are the optional parameters which control the
|
526
|
-
|
527
|
-
|
528
|
-
|
678
|
+
# options are the optional parameters which control the template parsing (:tidy)
|
679
|
+
# and rendering output (:output_mio_tree, :namespace, :serializer)
|
680
|
+
#
|
681
|
+
def self.parse( template, options={} )
|
682
|
+
mv_parser = self.new
|
683
|
+
mv_parser.process(template, options)
|
684
|
+
end
|
685
|
+
|
686
|
+
# process the template
|
687
|
+
def process(template, options)
|
688
|
+
|
689
|
+
options = options.clone() # don't munge the client's copy of this
|
690
|
+
ensure_standard_options_configured(options)
|
529
691
|
|
530
692
|
begin
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
options[:listeners].each do |listener|
|
539
|
-
if listener.is_a? Class
|
540
|
-
parser.listen( listener.new(options) )
|
541
|
-
else
|
542
|
-
parser.listen(listener)
|
693
|
+
sax_parser = REXML::Parsers::SAX2ParserWithDoctypeFix.new( template )
|
694
|
+
options[:listeners].each do |listener|
|
695
|
+
if listener.is_a? Class
|
696
|
+
sax_parser.listen( listener.new(options) )
|
697
|
+
else
|
698
|
+
sax_parser.listen(listener)
|
699
|
+
end
|
543
700
|
end
|
544
|
-
|
545
|
-
|
546
|
-
rescue Exception => e
|
701
|
+
sax_parser.parse
|
702
|
+
rescue Exception => ex
|
547
703
|
if options[:rescue_exceptions]
|
548
|
-
Log.error { "Failure to parse template. Exception="+
|
549
|
-
Log.debug {
|
704
|
+
Log.error { "Failure to parse template. Exception="+ex }
|
705
|
+
Log.debug { ex.backtrace.join("\n") }
|
550
706
|
else
|
551
707
|
raise
|
552
708
|
end
|
553
709
|
end
|
554
710
|
end
|
555
711
|
|
712
|
+
# ensure that default policies are configured
|
713
|
+
def ensure_standard_options_configured(options) #:nodoc:
|
714
|
+
|
715
|
+
# install the standard processing mechanisms unless overridden by client
|
716
|
+
options[:directive_load_path] = DirectiveLoadPath.current unless options.include?(:directive_load_path)
|
717
|
+
options[:listeners] ||= [ TemplateProcessing::SAXParserListener ]
|
718
|
+
options[:rescue_exceptions] = RescueExceptions unless options.include?(:rescue_exceptions)
|
719
|
+
|
720
|
+
DefaultParserOptions.each_pair {|key, value|
|
721
|
+
options[key] = value unless options.include?(key)
|
722
|
+
}
|
723
|
+
|
724
|
+
end
|
725
|
+
|
556
726
|
end
|
557
727
|
|
558
728
|
end
|