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.
Files changed (155) hide show
  1. data/CHANGELOG +31 -1
  2. data/README +70 -69
  3. data/RELEASE_NOTES +70 -64
  4. data/Rakefile +26 -27
  5. data/TODO +13 -29
  6. data/doc/about.html +246 -0
  7. data/doc/configuration.html +49 -36
  8. data/doc/developer.html +423 -41
  9. data/doc/directives.html +139 -51
  10. data/doc/guide.html +19 -9
  11. data/doc/index.html +90 -224
  12. data/doc/installation.html +36 -28
  13. data/doc/media_list.html +30 -20
  14. data/doc/simple_diagram.html +3 -5
  15. data/doc/stylesheets/masterview.css +16 -1
  16. data/examples/rails_app_config/masterview/settings.rb +2 -1
  17. data/init.rb +1 -1
  18. data/lib/#ChangeLog# +6 -0
  19. data/lib/masterview/analyzer.rb +48 -34
  20. data/lib/masterview/attr_string_parser.rb +5 -1
  21. data/lib/masterview/case_insensitive_hash.rb +69 -0
  22. data/lib/masterview/{pathname_extensions.rb → core_ext/pathname.rb} +0 -0
  23. data/lib/masterview/{string_extensions.rb → core_ext/string.rb} +0 -0
  24. data/lib/masterview/deprecated/directive_base.rb +362 -0
  25. data/lib/masterview/directive_base.rb +201 -179
  26. data/lib/masterview/directive_dsl.rb +457 -0
  27. data/lib/masterview/directive_helpers.rb +28 -141
  28. data/lib/masterview/directive_load_path.rb +388 -0
  29. data/lib/masterview/directive_metadata.rb +377 -0
  30. data/lib/masterview/directive_registry.rb +259 -69
  31. data/lib/masterview/directives/.metadata +16 -0
  32. data/lib/masterview/directives/attr.rb +9 -8
  33. data/lib/masterview/directives/block.rb +11 -14
  34. data/lib/masterview/directives/check_box.rb +13 -18
  35. data/lib/masterview/directives/collection_select.rb +15 -29
  36. data/lib/masterview/directives/content.rb +9 -3
  37. data/lib/masterview/directives/else.rb +15 -13
  38. data/lib/masterview/directives/elsif.rb +14 -13
  39. data/lib/masterview/directives/eval.rb +20 -0
  40. data/lib/masterview/directives/form.rb +56 -9
  41. data/lib/masterview/directives/form_remote.rb +26 -0
  42. data/lib/masterview/directives/global_inline_erb.rb +10 -14
  43. data/lib/masterview/directives/hidden_field.rb +11 -20
  44. data/lib/masterview/directives/if.rb +13 -12
  45. data/lib/masterview/directives/image_tag.rb +20 -28
  46. data/lib/masterview/directives/import.rb +5 -12
  47. data/lib/masterview/directives/import_render.rb +7 -19
  48. data/lib/masterview/directives/insert_generated_comment.rb +8 -11
  49. data/lib/masterview/directives/javascript_include.rb +21 -12
  50. data/lib/masterview/directives/link_to.rb +14 -8
  51. data/lib/masterview/directives/link_to_function.rb +22 -0
  52. data/lib/masterview/directives/link_to_if.rb +15 -13
  53. data/lib/masterview/directives/link_to_remote.rb +13 -8
  54. data/lib/masterview/directives/omit_tag.rb +32 -16
  55. data/lib/masterview/directives/password_field.rb +10 -22
  56. data/lib/masterview/directives/radio_button.rb +11 -22
  57. data/lib/masterview/directives/replace.rb +7 -8
  58. data/lib/masterview/directives/select.rb +11 -24
  59. data/lib/masterview/directives/stylesheet_link.rb +20 -12
  60. data/lib/masterview/directives/submit.rb +11 -5
  61. data/lib/masterview/directives/text_area.rb +10 -23
  62. data/lib/masterview/directives/text_field.rb +10 -22
  63. data/lib/masterview/exceptions.rb +21 -0
  64. data/lib/masterview/extras/app/controllers/masterview_controller.rb +102 -75
  65. data/lib/masterview/extras/app/views/layouts/masterview_admin.rhtml +24 -23
  66. data/lib/masterview/extras/app/views/layouts/masterview_admin_config.rhtml +81 -0
  67. data/lib/masterview/extras/app/views/masterview/admin/configuration.rhtml +5 -1
  68. data/lib/masterview/extras/app/views/masterview/admin/create.rhtml +2 -2
  69. data/lib/masterview/extras/app/views/masterview/admin/directives.rhtml +5 -0
  70. data/lib/masterview/extras/app/views/masterview/admin/features.rhtml +5 -79
  71. data/lib/masterview/extras/app/views/masterview/admin/interact.rhtml +5 -0
  72. data/lib/masterview/extras/app/views/masterview/admin/list.rhtml +3 -71
  73. data/lib/masterview/extras/init_mv_admin_pages.rb +42 -23
  74. data/lib/masterview/filter_helpers.rb +26 -0
  75. data/lib/masterview/initializer.rb +99 -53
  76. data/lib/masterview/io.rb +19 -15
  77. data/lib/masterview/keyword_expander.rb +7 -2
  78. data/lib/masterview/masterview_info.rb +229 -23
  79. data/lib/masterview/masterview_version.rb +2 -2
  80. data/lib/masterview/parser.rb +275 -105
  81. data/lib/masterview/parser_helpers.rb +54 -0
  82. data/lib/masterview/rails_ext/action_controller_erb_direct.rb +29 -0
  83. data/lib/masterview/rails_ext/action_controller_reparse_checking.rb +27 -0
  84. data/lib/masterview/{extras/init_rails_erb_mv_direct.rb → rails_ext/action_view_erb_direct.rb} +12 -59
  85. data/lib/masterview/template_spec.rb +3 -2
  86. data/lib/masterview.rb +21 -12
  87. data/lib/rexml/parsers/baseparser_with_doctype_fix.rb +473 -0
  88. data/lib/rexml/parsers/sax2parser_with_doctype_fix.rb +243 -0
  89. data/test/directive_test_helper.rb +135 -0
  90. data/test/fixtures/directives/id_check.rb +18 -0
  91. data/test/fixtures/directives/test_directive_events.rb +70 -0
  92. data/test/test_helper.rb +18 -5
  93. data/test/tmp/views/layouts/product.rhtml +10 -10
  94. data/test/tmp/views/product/_form.rhtml +4 -4
  95. data/test/tmp/views/product/_product.rhtml +3 -3
  96. data/test/tmp/views/product/destroy.rhtml +5 -5
  97. data/test/tmp/views/product/edit.rhtml +4 -4
  98. data/test/tmp/views/product/list.rhtml +3 -3
  99. data/test/tmp/views/product/new.rhtml +4 -4
  100. data/test/tmp/views/product/show.rhtml +2 -2
  101. data/test/unit/attr_string_parser_test.rb +105 -0
  102. data/test/unit/case_insensitive_hash_mod_test.rb +104 -0
  103. data/test/unit/config_settings_test.rb +13 -1
  104. data/test/unit/default_generate_mio_filter_test.rb +3 -3
  105. data/test/unit/deprecated_directive_base_test.rb +30 -0
  106. data/test/unit/directive_attr_test.rb +111 -35
  107. data/test/unit/directive_base_test.rb +520 -1
  108. data/test/unit/directive_block_test.rb +30 -22
  109. data/test/unit/directive_content_test.rb +24 -11
  110. data/test/unit/directive_else_test.rb +18 -15
  111. data/test/unit/directive_elsif_test.rb +17 -15
  112. data/test/unit/directive_form_remote_test.rb +59 -0
  113. data/test/unit/directive_form_test.rb +31 -39
  114. data/test/unit/directive_global_inline_erb_test.rb +28 -17
  115. data/test/unit/directive_grid_test_notready.rb +38 -0
  116. data/test/unit/directive_helpers_test.rb +39 -0
  117. data/test/unit/directive_hidden_field_test.rb +44 -29
  118. data/test/unit/directive_if_test.rb +10 -7
  119. data/test/unit/directive_image_tag_test.rb +69 -61
  120. data/test/unit/directive_import_render_test.rb +28 -38
  121. data/test/unit/directive_import_test.rb +16 -14
  122. data/test/unit/directive_insert_generated_comment_test.rb +32 -0
  123. data/test/unit/directive_javascript_include_test.rb +40 -43
  124. data/test/unit/directive_link_to_function_test.rb +40 -0
  125. data/test/unit/directive_link_to_if_test.rb +52 -12
  126. data/test/unit/directive_link_to_remote_test.rb +58 -0
  127. data/test/unit/directive_link_to_test.rb +46 -31
  128. data/test/unit/directive_load_path_test.rb +257 -0
  129. data/test/unit/directive_metadata_test.rb +313 -0
  130. data/test/unit/directive_omit_tag_test.rb +73 -21
  131. data/test/unit/directive_password_field_test.rb +44 -38
  132. data/test/unit/directive_registry_test.rb +44 -0
  133. data/test/unit/directive_replace_test.rb +28 -12
  134. data/test/unit/directive_stylesheet_link_test.rb +43 -36
  135. data/test/unit/directive_submit_test.rb +29 -30
  136. data/test/unit/directive_text_area_test.rb +40 -36
  137. data/test/unit/directive_text_field_test.rb +44 -38
  138. data/test/unit/example_directive_child_events_test.rb +41 -0
  139. data/test/unit/example_test.rb +31 -4
  140. data/test/unit/file_mio_test.rb +18 -13
  141. data/test/unit/filter_helpers_test.rb +10 -8
  142. data/test/unit/find_directive_parent_test.rb +174 -0
  143. data/test/unit/keyword_expander_test.rb +4 -2
  144. data/test/unit/mio_test.rb +18 -11
  145. data/test/unit/mtime_string_hash_mio_tree_test.rb +5 -1
  146. data/test/unit/parser_test.rb +41 -29
  147. data/test/unit/pathname_extensions_test.rb +1 -1
  148. data/test/unit/run_parser_test.rb +2 -2
  149. data/test/unit/simplified_directive_base_test.rb +256 -0
  150. data/test/unit/string_hash_mio_test.rb +5 -1
  151. data/test/unit/template_file_watcher_test.rb +2 -2
  152. data/test/unit/template_test.rb +221 -46
  153. metadata +86 -45
  154. data/lib/masterview/directives/testfilter.rb +0 -55
  155. data/lib/masterview/extras/init_rails_reparse_checking.rb +0 -62
@@ -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
- class Tag
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
- def initialize(directives, tag_name, attributes, mode_type, parent)
195
+ attr_accessor :prolog, :renderer
196
+ def initialize(directives, tag_name, attributes, mode_type, parent, prolog, renderer)
158
197
  @tag_name = tag_name
159
- @attributes = attributes
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 :directive_load_paths, :mv_ns, :keyword_expander, :default_extension, :options
236
- attr_accessor :render_levels, :default_render_handler, :serializer, :template_pathname, :template_full_pathname
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
- def self.last_renderer; @@last_renderer; end
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
- self.mv_ns = options[:namespace] || NamespacePrefix
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
- #ISSUE: if :additional_directive_paths then do cleaning and validity checks here and now
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
- def mv_ns=(namespace_prefix)
264
- @mv_ns = namespace_prefix
265
- Log.debug { 'namespace_prefix set to '+namespace_prefix }
266
- end
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
- # Sets directive_load_paths, re-requiring all the new load paths, however any directives that were
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
- @directives_registry = MasterView::DirectivesRegistry
273
- #ISSUE: can we optimize this if there aren't :additional_directive_paths?
274
- # Do we even need/want the notion of per-invocation :additional_directive_paths
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
- # is this a good idea? no clear that we need/want to modify mv_ns after app initialization
280
- # If so, this could be simplifed. Discuss with jeffb.
281
- # [DJL 04-Jul-2006]
282
- if @mv_ns != NamespacePrefix
283
- @directives_registry = directives_registry.clone
284
- end
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 |x,y|
392
- xval = (x.respond_to?(:priority)) ? x.priority : DirectivePriorities::Medium
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
- end
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
- class MasterViewListener
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
- #todo
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
- def doctype(name, pub, sys, long_name, uri)
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
- push_levels(attributes)
426
- @renderer.push_tag(qname, attributes)
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 ERB_EVAL+value+ERB_END
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.mv_ns+'gen_partial')
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.mv_ns+'generate'] || '' # check if we need to add to existing generate
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.mv_ns+'generate'] = generate_attribute
478
- @renderer.append_raw( ERB_EVAL+'render( '+value+' )'+ERB_END )
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.mv_ns+'gen_replace') #get and delete from map
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.mv_ns+'generate') #get and delete from map
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.mv_ns+'insert_generated_comment'] = @renderer.template_full_pathname.to_s unless omit_comment #add the comment directive, so it will be written to each gen'd file
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
- def self.parse_mio( template_mio, output_mio_tree, options = {})
516
- options[:template_pathname]= template_mio.pathname
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
- # parse a MasterView template and render output.
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 output (:output_mio_tree, :namespace, :serializer)
526
- def self.parse( template, options = DefaultParserOptions.clone)
527
- options[:listeners] ||= [MasterViewListener]
528
- options[:rescue_exceptions] = RescueExceptions unless options.include?(:rescue_exceptions)
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
- if options[:tidy]
532
- template = TidyHelper.tidy(template)
533
- elsif options[:escape_erb]
534
- template = EscapeErbHelper.escape_erb(template)
535
- end
536
-
537
- parser = REXML::Parsers::SAX2Parser.new( template )
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
- end
545
- parser.parse
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="+e }
549
- Log.debug { e.backtrace.join("\n") }
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