hobo 0.7.3 → 0.7.4

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 (46) hide show
  1. data/bin/hobo +1 -1
  2. data/hobo_files/plugin/CHANGES.txt +302 -0
  3. data/hobo_files/plugin/generators/hobo_front_controller/templates/index.dryml +2 -9
  4. data/hobo_files/plugin/generators/hobo_model/templates/model.rb +1 -1
  5. data/hobo_files/plugin/generators/hobo_model_resource/hobo_model_resource_generator.rb +0 -2
  6. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo-rapid.js +76 -46
  7. data/hobo_files/plugin/generators/hobo_rapid/templates/lowpro.js +25 -18
  8. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/application.css +29 -11
  9. data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +2 -2
  10. data/hobo_files/plugin/init.rb +0 -1
  11. data/hobo_files/plugin/lib/active_record/has_many_association.rb +3 -0
  12. data/hobo_files/plugin/lib/hobo.rb +12 -8
  13. data/hobo_files/plugin/lib/hobo/bundle.rb +1 -1
  14. data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +1 -1
  15. data/hobo_files/plugin/lib/hobo/dryml/parser/attribute.rb +41 -0
  16. data/hobo_files/plugin/lib/hobo/dryml/parser/base_parser.rb +253 -0
  17. data/hobo_files/plugin/lib/hobo/dryml/parser/document.rb +26 -0
  18. data/hobo_files/plugin/lib/hobo/dryml/parser/element.rb +27 -0
  19. data/hobo_files/plugin/lib/hobo/dryml/parser/elements.rb +45 -0
  20. data/hobo_files/plugin/lib/hobo/dryml/parser/source.rb +58 -0
  21. data/hobo_files/plugin/lib/hobo/dryml/parser/text.rb +13 -0
  22. data/hobo_files/plugin/lib/hobo/dryml/parser/tree_parser.rb +67 -0
  23. data/hobo_files/plugin/lib/hobo/dryml/scoped_variables.rb +10 -5
  24. data/hobo_files/plugin/lib/hobo/dryml/template.rb +48 -27
  25. data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +28 -13
  26. data/hobo_files/plugin/lib/hobo/hobo_helper.rb +3 -1
  27. data/hobo_files/plugin/lib/hobo/model.rb +70 -10
  28. data/hobo_files/plugin/lib/hobo/model_controller.rb +49 -34
  29. data/hobo_files/plugin/lib/hobo/model_router.rb +10 -2
  30. data/hobo_files/plugin/lib/hobo/rapid_helper.rb +1 -0
  31. data/hobo_files/plugin/lib/hobo/scopes.rb +15 -0
  32. data/hobo_files/plugin/lib/hobo/scopes/apply_scopes.rb +23 -0
  33. data/hobo_files/plugin/lib/hobo/scopes/association_proxy_extensions.rb +4 -2
  34. data/hobo_files/plugin/lib/hobo/scopes/automatic_scopes.rb +34 -7
  35. data/hobo_files/plugin/lib/hobo/scopes/defined_scope_proxy_extender.rb +3 -1
  36. data/hobo_files/plugin/lib/hobo/scopes/scoped_proxy.rb +1 -5
  37. data/hobo_files/plugin/taglibs/rapid.dryml +33 -24
  38. data/hobo_files/plugin/taglibs/rapid_editing.dryml +6 -5
  39. data/hobo_files/plugin/taglibs/rapid_forms.dryml +37 -31
  40. data/hobo_files/plugin/taglibs/rapid_generics.dryml +68 -27
  41. data/hobo_files/plugin/taglibs/rapid_navigation.dryml +5 -8
  42. data/hobo_files/plugin/taglibs/rapid_pages.dryml +71 -47
  43. data/hobo_files/plugin/taglibs/rapid_plus.dryml +4 -5
  44. data/hobo_files/plugin/taglibs/rapid_support.dryml +11 -4
  45. metadata +23 -6
  46. data/hobo_files/plugin/lib/rexml.rb +0 -443
@@ -21,12 +21,15 @@
21
21
  </def>
22
22
 
23
23
 
24
+ <def tag="main-nav"><magic-nav class="main-nav"/></def>
25
+
26
+
24
27
  <!--- Account Navigation (log in / out / signup) -->
25
28
 
26
29
  <def tag="account-nav">
27
30
  <ul with="&current_user" class="account-nav" param>
28
31
  <if test="&logged_in?">
29
- <li class='nav-item' param="logged-in-as">Logged in as <view:login/></li>
32
+ <li class='nav-item' param="logged-in-as"><a to="&current_user">Logged in as <view:login/></a></li>
30
33
  <li class='nav-item' param="account"><a action="account">Account</a></li>
31
34
  <li class='nav-item' param="log-out"><a href="&logout_url">Log out</a></li>
32
35
  </if>
@@ -42,13 +45,7 @@
42
45
  <!--- Pagination Navigation -->
43
46
 
44
47
  <def tag="page-nav" attrs="params">
45
- <if test="&this.try.page_count._? > 1">
46
- <page-n-of-count/> -
47
- <first-page-link params="&params" param>|&lt;</first-page-link>
48
- <previous-page-link params="&params" param>Previous</previous-page-link>
49
- <next-page-link params="&params" param>Next</next-page-link>
50
- <last-page-link params="&params" param>&gt|</last-page-link>
51
- </if>
48
+ <%= will_paginate this, attributes.symbolize_keys.reverse_merge(:inner_window => 2, :prev_label => "&laquo; Prev") %>
52
49
  </def>
53
50
 
54
51
 
@@ -1,9 +1,9 @@
1
- <def tag="base-page" attrs="title, doctype">
2
- <% title ||= default_page_title %>
1
+ <def tag="base-page" attrs="title, full-title, doctype">
2
+ <% full_title ||= "#{title} : #{app_name}" %>
3
3
  <doctype param version="&doctype || 'HTML 4.01 STRICT'"/>
4
4
  <html>
5
5
  <head param>
6
- <title param><%= title.gsub(/<.*?>/, '') %></title>
6
+ <title param><%= full_title.gsub(/<.*?>/, '') %></title>
7
7
  <do param="stylesheets">
8
8
  <stylesheet name="reset"/>
9
9
  <stylesheet name="hobo-rapid"/>
@@ -17,7 +17,7 @@
17
17
  </head>
18
18
 
19
19
  <body onload="Hobo.applyEvents();" param/>
20
- </html>
20
+ </html>
21
21
  </def>
22
22
 
23
23
 
@@ -30,7 +30,7 @@
30
30
  <live-search param if="&defined_route? :site_search"/>
31
31
  <nav param>
32
32
  <account-nav if="&Hobo::User.default_user_model" param/>
33
- <do param="main-nav"><magic-nav class="main-nav"/></do>
33
+ <main-nav param/>
34
34
  </nav>
35
35
  </header>
36
36
  <section class="page-content" param="content">
@@ -41,6 +41,7 @@
41
41
  <footer class="content-footer" param="content-footer"/>
42
42
  </section>
43
43
  <footer class="page-footer" param/>
44
+ <part-contexts-javascripts/>
44
45
  </body:>
45
46
  </base-page>
46
47
  </def>
@@ -65,17 +66,17 @@
65
66
  <def tag="index-page">
66
67
  <set model="&this.try.member_class || self.model"/>
67
68
  <set model-name="&model.name.titleize"/>
68
- <page title="All #{type_name :with => model, :pluralize => true}" merge>
69
+ <page title="All #{type_name :with => model, :plural => true}" merge>
69
70
  <body: class="index-page #{type_id model}" param/>
70
71
  <content-header: param>
71
- <heading param><type-name with="&model"/></heading>
72
- <p class="note" if param>There <count part="item-count" prefix="are"/></p>
72
+ <heading param><type-name with="&model" plural/></heading>
73
+ <p class="note" if param>There <count prefix="are"/></p>
73
74
  </content-header:>
74
75
 
75
76
  <content-body: param>
76
77
  <nav param="top-pagination-nav"><page-nav/></nav>
77
78
 
78
- <collection param/>
79
+ <collection param><empty-message:></empty-message:></collection>
79
80
 
80
81
  <nav param="bottom-pagination-nav"><page-nav/></nav>
81
82
  </content-body>
@@ -86,7 +87,7 @@
86
87
  <if with="&model.user_new current_user">
87
88
  <section class="create-new">
88
89
  <h2>New <type-name/></h2>
89
- <form><field-list/><submit label="Create #{type_name}"/></form>
90
+ <form><field-list param="new-field-list"/><submit label="Create"/></form>
90
91
  </section>
91
92
  </if>
92
93
  </else>
@@ -97,7 +98,7 @@
97
98
 
98
99
  <def tag="new-page">
99
100
  <page title="New #{type_name}" merge>
100
- <body: class="new-page #{type_name.underscore}" param/>
101
+ <body: class="new-page #{type_name :dasherize => true}" param/>
101
102
  <content-header: param>
102
103
  <heading param>New <type-name title/></heading>
103
104
  </content-header>
@@ -106,7 +107,7 @@
106
107
  <error-messages param/>
107
108
 
108
109
  <form param>
109
- <field-list skip-associations="has_many" param/>
110
+ <field-list param/>
110
111
  <div class="actions" param="actions">
111
112
  <submit label="Create #{type_name}" param/><do param="back-link"> or <a>Cancel</a></do>
112
113
  </div>
@@ -117,61 +118,75 @@
117
118
 
118
119
 
119
120
  <def tag="show-page" attrs="primary-collection">
120
- <set primary-collection-name="&(primary_collection || self.primary_collection_name).to_s"/>
121
+ <set primary-collection-name="&(primary_collection || self.primary_collection_name).to_s" model="&this.class"/>
122
+ <set boolean-fields="&model.columns.select {|c| c.type == :boolean }.*.name"/>
123
+ <set skip-fields="&boolean_fields + [model.name_attribute, model.primary_content_attribute, model.creator_attribute, model.dependent_on.first].compact"/>
124
+ <set flags="&boolean_fields.map {|f| f.titleize if this.send(f)}.compact.join(', ')"/>
125
+
121
126
  <page merge title="#{name :no_wrapper => true}">
127
+
122
128
  <body: class="show-page #{type_name :dasherize => true}" param/>
129
+
123
130
  <content-header: param>
124
131
  <if with="&this.dependent_on.reject{|x| x.is_a?(Hobo::User)}.first">
125
- <div class="container"><a/></div>
132
+ <div class="container"><a>&laquo; <name/></a></div>
126
133
  </if>
127
134
 
128
- <heading param><%= this %></heading>
129
- <creation-details param/>
135
+ <heading param><this/></heading>
136
+
137
+ <div class="flags"><flags/></div>
130
138
 
139
+ <creation-details param/>
131
140
  <do field="&primary_collection_name" if="&primary_collection_name" param="primary-collection-count">
132
141
  <association-count class="primary-collection-count" part="primary-collection-count"/>
133
142
  </do>
134
143
 
135
- <a action="edit" class="edit" if="&can_edit?" param="edit-link">Edit <type-name/></a>
144
+ <a action="edit" class="edit" if="&linkable?(:edit) && can_edit?" param="edit-link">Edit <type-name/></a>
136
145
  </content-header>
137
146
 
138
147
  <content-body: param>
139
148
  <primary-content param/>
140
149
 
141
- <field-list skip="&[this.class.name_attribute, this.class.primary_content_attribute, this.class.creator_attribute, this.class.dependent_on.first].compact "
142
- skip-associations="has_many" param/>
143
-
144
- <section class="primary-collection" field="&primary_collection_name" if="&primary_collection_name">
145
- <h2 param="primary-collection-title"><%= primary_collection_name.titleize %></h2>
146
-
147
- <do param="primary-collection">
148
- <collection part="primary-collection"/>
149
- </do>
150
-
151
- <if test="can_create?">
152
- <nav class="new-link"><a action="new" if="&linkable?(:new) && can_create?"/></nav>
153
- <else>
154
- <section class="create-new" with="&new_for_current_user">
155
- <h2>Add <A-or-An word="&primary_collection_name.singularize.titleize"/></h2>
156
- <form update="primary-collection, primary-collection-count"
157
- message="Adding #{primary_collection_name.singularize.titleize}..." reset-form>
158
- <field-list skip="#{@this.class.reverse_reflection(@this.send(primary_collection_name).proxy_reflection.name).name}"
159
- skip-associations="has_many" param="primary-collection-field-list"/>
160
- <submit label="Create #{primary_collection_name.singularize.titleize}"/>
161
- </form>
162
- </section>
163
- </else>
164
- </if>
165
- </section>
166
-
150
+ <field-list skip="&skip_fields" skip-associations="has_many" param/>
151
+
152
+ <with-primary-collection name="&primary_collection_name" if="&primary_collection_name">
153
+ <section class="primary-collection">
154
+ <h2 param="primary-collection-title"><primary-collection-name.titleize/></h2>
155
+
156
+ <do param="primary-collection">
157
+ <collection part="primary-collection">
158
+ <card:><delete-button: update="primary-collection-count"></delete-button:></card:>
159
+ </collection>
160
+ </do>
161
+
162
+ <do with="&@this.send(primary_collection_name)">
163
+ <if test="&can_create?" param="primary-collection-add">
164
+ <nav class="new-link"><a action="new" if="&linkable?(:new) && can_create?"/></nav>
165
+ <else>
166
+ <section class="create-new" with="&new_for_current_user">
167
+ <h2>Add <A-or-An word="&primary_collection_name.singularize.titleize"/></h2>
168
+ <form update="primary-collection, primary-collection-count"
169
+ message="Adding #{primary_collection_name.singularize.titleize}..." reset-form>
170
+ <field-list skip="#{@this.class.reverse_reflection(@this.send(primary_collection_name).proxy_reflection.name).name}"
171
+ skip-associations="has_many" param="primary-collection-field-list"/>
172
+ <submit label="Add"/>
173
+ </form>
174
+ </section>
175
+ </else>
176
+ </if>
177
+ </do>
178
+ </section>
179
+ </with-primary-collection>
167
180
  </content-body:>
181
+
168
182
  <aside: param>
169
183
  <section class="preview-collections">
170
- <with-fields fields="&non_through_collections - [primary_collection_name._?.to_sym]">
184
+ <with-fields fields="&non_through_collections - [(primary_collection_name.to_sym unless primary_collection_name.blank?)]">
171
185
  <collection-preview class="#{this_field.dasherize}"/>
172
186
  </with-fields>
173
187
  </section>
174
188
  </aside:>
189
+
175
190
  </page>
176
191
  </def>
177
192
 
@@ -187,7 +202,7 @@
187
202
  <content-body: param>
188
203
  <error-messages param/>
189
204
  <form param>
190
- <field-list skip-associations="has_many" param/>
205
+ <field-list param/>
191
206
  <div class="actions" param="actions">
192
207
  <submit label="Save Changes" param/><do param="back-link"> or <a>Cancel</a></do>
193
208
  </div>
@@ -211,8 +226,7 @@
211
226
  <error-messages/>
212
227
 
213
228
  <form param>
214
- <field-list skip="#{@association.origin.class.reverse_reflection(association_name.to_sym).name}"
215
- skip-associations="has_many" param/>
229
+ <field-list skip="#{@association.origin.class.reverse_reflection(association_name.to_sym).name}" param/>
216
230
  <div class="actions" param="actions">
217
231
  <submit label="Create #{association_name.singularize.titleize}" param/>
218
232
  <do param="back-link"> or <a with="&@association.origin">Cancel</a></do>
@@ -329,3 +343,13 @@
329
343
 
330
344
  <def tag="default-page-title"><%= t = this.to_s; ; "#{t.blank? ? '' : t + ' - '}#{app_name}" %></def>
331
345
 
346
+
347
+ <def tag="with-primary-collection" attrs="name"><%
348
+ ivar = "@#{this.class.name.underscore}_#{name}"
349
+
350
+ if (collection = instance_variable_get(ivar))
351
+ %><do with="&collection" param="default"/><%
352
+ else
353
+ %><do field="&name" param="default"/><%
354
+ end
355
+ %></def>
@@ -1,5 +1,6 @@
1
1
  <def tag="table-plus" attrs="sort-field, sort-direction, sort-columns" >
2
2
  <% sort_field ||= @sort_field; sort_direction ||= @sort_direction; sort_columns ||= {} %>
3
+ <% sort_columns['this'] ||= this.member_class.name_attribute %>
3
4
  <div class="table-plus" merge-attrs="&attributes - attrs_for(:with_fields) - attrs_for(:table)">
4
5
  <div class="header" param="header">
5
6
  <div class="search">
@@ -12,7 +13,7 @@
12
13
  </div>
13
14
  </div>
14
15
 
15
- <table merge-attrs="&attributes & (attrs_for(:table) + attrs_for(:with_fields))" merge-params>
16
+ <table merge-attrs="&attributes & (attrs_for(:table) + attrs_for(:with_fields))" empty merge-params>
16
17
  <field-heading-row:>
17
18
  <with-field-names merge-attrs="&all_attributes & attrs_for(:with_fields)">
18
19
  <% col = sort_columns[scope.field_path] || scope.field_path
@@ -32,11 +33,9 @@
32
33
  <th if="&all_parameters[:controls]" class="controls"/>
33
34
  </field-heading-row>
34
35
  </table>
35
- <else>
36
- <do param="empty-message"/>
37
- </else>
36
+ <do param="empty-message" if="empty?"/>
38
37
 
39
- <nav class="page">
38
+ <nav class="page" if="&this.respond_to? :page_count">
40
39
  <page-nav param/>
41
40
  </nav>
42
41
  </div>
@@ -1,12 +1,13 @@
1
1
  <def tag="with-fields" attrs="fields, associations, skip, skip-associations, include-timestamps, force-all"><%
2
+ fields.nil? || associations.nil? or raise ArgumentError, "with-fields -- specify either fields or associations but not both"
3
+
2
4
  field_names = if associations == "has_many"
3
5
  this.class.reflections.values.select { |refl| refl.macro == :has_many }.map { |refl| refl.name.to_s }
4
6
 
5
7
  elsif fields.nil? || fields == "*" || fields.is_a?(Class)
6
8
  klass = fields.is_a?(Class) ? fields : this.class
7
9
  columns = klass.content_columns.*.name
8
- columns -= %w{created_at updated_at created_on updated_on} unless
9
- include_timestamps
10
+ columns -= %w{created_at updated_at created_on updated_on} unless include_timestamps
10
11
 
11
12
  if skip_associations == "has_many"
12
13
  assocs = this.class.reflections.values.reject {|r| r.macro == :has_many }.map &its.name.to_s
@@ -22,7 +23,13 @@
22
23
  end
23
24
  field_names -= comma_split(skip) if skip
24
25
  field_names = field_names.select {|f| can_view?(this, f)} unless force_all
25
- field_names.each do |field| %><with field="&field"><do param="default"/></with><% end
26
+ field_names.each do |field|
27
+ if field == "this"
28
+ %><do param="default"/><%
29
+ else
30
+ %><with field="&field"><do param="default"/></with><%
31
+ end
32
+ end
26
33
  %></def>
27
34
 
28
35
  <def tag="with-field-names" attrs="fields, skip, skip-associations, include-timestamps"><%=
@@ -47,7 +54,7 @@
47
54
  field_names -= comma_split(skip) if skip
48
55
  scope.new_scope do
49
56
  field_names.map do |n|
50
- scope.field_name = n.to_s.gsub("." , "_")
57
+ scope.field_name = n == "this" ? this.member_class.name : n.to_s.gsub("." , "_")
51
58
  scope.field_path = n
52
59
  parameters.default
53
60
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hobo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 0.7.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Locke
@@ -9,10 +9,18 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-03-14 00:00:00 +00:00
12
+ date: 2008-04-07 00:00:00 +01:00
13
13
  default_executable:
14
- dependencies: []
15
-
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hobosupport
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.7.4
23
+ version:
16
24
  description:
17
25
  email: tom@hobocentral.net
18
26
  executables:
@@ -151,6 +159,15 @@ files:
151
159
  - hobo_files/plugin/lib/hobo/dryml
152
160
  - hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb
153
161
  - hobo_files/plugin/lib/hobo/dryml/dryml_support_controller.rb
162
+ - hobo_files/plugin/lib/hobo/dryml/parser
163
+ - hobo_files/plugin/lib/hobo/dryml/parser/attribute.rb
164
+ - hobo_files/plugin/lib/hobo/dryml/parser/base_parser.rb
165
+ - hobo_files/plugin/lib/hobo/dryml/parser/document.rb
166
+ - hobo_files/plugin/lib/hobo/dryml/parser/element.rb
167
+ - hobo_files/plugin/lib/hobo/dryml/parser/elements.rb
168
+ - hobo_files/plugin/lib/hobo/dryml/parser/source.rb
169
+ - hobo_files/plugin/lib/hobo/dryml/parser/text.rb
170
+ - hobo_files/plugin/lib/hobo/dryml/parser/tree_parser.rb
154
171
  - hobo_files/plugin/lib/hobo/dryml/part_context.rb
155
172
  - hobo_files/plugin/lib/hobo/dryml/scoped_variables.rb
156
173
  - hobo_files/plugin/lib/hobo/dryml/tag_parameters.rb
@@ -168,6 +185,7 @@ files:
168
185
  - hobo_files/plugin/lib/hobo/model_support.rb
169
186
  - hobo_files/plugin/lib/hobo/rapid_helper.rb
170
187
  - hobo_files/plugin/lib/hobo/scopes
188
+ - hobo_files/plugin/lib/hobo/scopes/apply_scopes.rb
171
189
  - hobo_files/plugin/lib/hobo/scopes/association_proxy_extensions.rb
172
190
  - hobo_files/plugin/lib/hobo/scopes/automatic_scopes.rb
173
191
  - hobo_files/plugin/lib/hobo/scopes/defined_scope_proxy_extender.rb
@@ -180,7 +198,6 @@ files:
180
198
  - hobo_files/plugin/lib/hobo/user.rb
181
199
  - hobo_files/plugin/lib/hobo/user_controller.rb
182
200
  - hobo_files/plugin/lib/hobo.rb
183
- - hobo_files/plugin/lib/rexml.rb
184
201
  - hobo_files/plugin/LICENSE.txt
185
202
  - hobo_files/plugin/Rakefile
186
203
  - hobo_files/plugin/README
@@ -226,7 +243,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
226
243
  version:
227
244
  requirements: []
228
245
 
229
- rubyforge_project:
246
+ rubyforge_project: hobo
230
247
  rubygems_version: 1.0.1
231
248
  signing_key:
232
249
  specification_version: 2
@@ -1,443 +0,0 @@
1
- # Extensions to XML Parsing
2
- #
3
- # 1. Hobo needs to process XML as transparently as possibe. In the
4
- # case of tags that are not defined (i.e. html tags), they should pass
5
- # through just as they were in the dryml source. Recontructing the
6
- # tags from the DOM is not good enough. The extensions to REXML in
7
- # here allow Hobo to use the original start tag source in the output.
8
- #
9
- # 2. some fixes/extras to allow error messages with line numbers.
10
- #
11
- # 3. Attributes without a RHS are allowed. They are returned as having
12
- # a value of +true+ (the Ruby value, not the string 'true')
13
- #
14
- # 1 and 2 are achieved by adding two instance variables to Element
15
- # nodes : @start_tag_source and @source_offset
16
- #
17
- # So cool that Ruby allows us to redefine a method. Such a shame the method
18
- # we needed to change happened to be 200 lines long :-(
19
-
20
- require 'rexml/document'
21
-
22
-
23
-
24
- module REXML
25
- module Parsers
26
-
27
- class TreeParser
28
- def initialize( source, build_context = Document.new )
29
- @build_context = build_context
30
- @parser = Parsers::BaseParser.new(source)
31
- @parser.dryml_mode = build_context.context[:dryml_mode]
32
- end
33
- end
34
-
35
- class BaseParser
36
-
37
- DRYML_NAME_STR= "#{NCNAME_STR}(?::(?:#{NCNAME_STR})?)?"
38
- DRYML_ATTRIBUTE_PATTERN = /\s*(#{NAME_STR})(?:\s*=\s*(["'])(.*?)\2)?/um
39
- DRYML_TAG_MATCH = /^<((?>#{DRYML_NAME_STR}))\s*((?>\s+#{NAME_STR}(?:\s*=\s*(["']).*?\3)?)*)\s*(\/)?>/um
40
- DRYML_CLOSE_MATCH = /^\s*<\/(#{DRYML_NAME_STR})\s*>/um
41
-
42
- attr_writer :dryml_mode
43
- def dryml_mode?
44
- @dryml_mode
45
- end
46
-
47
-
48
- def pull
49
- if @closed
50
- x, @closed = @closed, nil
51
- return [ :end_element, x, false ]
52
- end
53
- return [ :end_document ] if empty?
54
- return @stack.shift if @stack.size > 0
55
- @source.read if @source.buffer.size<2
56
- if @document_status == nil
57
- @source.consume(/^\s*/um)
58
- word = @source.match(/(<[^>]*)>/um)
59
- word = word[1] unless word.nil?
60
- case word
61
- when COMMENT_START
62
- return [ :comment, @source.match(COMMENT_PATTERN, true)[1] ]
63
- when XMLDECL_START
64
- results = @source.match(XMLDECL_PATTERN, true)[1]
65
- version = VERSION.match(results)
66
- version = version[1] unless version.nil?
67
- encoding = ENCODING.match(results)
68
- encoding = encoding[1] unless encoding.nil?
69
- @source.encoding = encoding
70
- standalone = STANDALONE.match(results)
71
- standalone = standalone[1] unless standalone.nil?
72
- return [ :xmldecl, version, encoding, standalone]
73
- when INSTRUCTION_START
74
- return [ :processing_instruction, *@source.match(INSTRUCTION_PATTERN, true)[1,2] ]
75
- when DOCTYPE_START
76
- md = @source.match(DOCTYPE_PATTERN, true)
77
- identity = md[1]
78
- close = md[2]
79
- identity =~ IDENTITY
80
- name = $1
81
- raise REXML::ParseException("DOCTYPE is missing a name") if name.nil?
82
- pub_sys = $2.nil? ? nil : $2.strip
83
- long_name = $3.nil? ? nil : $3.strip
84
- uri = $4.nil? ? nil : $4.strip
85
- args = [ :start_doctype, name, pub_sys, long_name, uri ]
86
- if close == ">"
87
- @document_status = :after_doctype
88
- @source.read if @source.buffer.size<2
89
- md = @source.match(/^\s*/um, true)
90
- @stack << [ :end_doctype ]
91
- else
92
- @document_status = :in_doctype
93
- end
94
- return args
95
- else
96
- @document_status = :after_doctype
97
- @source.read if @source.buffer.size<2
98
- md = @source.match(/\s*/um, true)
99
- end
100
- end
101
- if @document_status == :in_doctype
102
- md = @source.match(/\s*(.*?>)/um)
103
- case md[1]
104
- when SYSTEMENTITY
105
- match = @source.match(SYSTEMENTITY, true)[1]
106
- return [ :externalentity, match ]
107
-
108
- when ELEMENTDECL_START
109
- return [ :elementdecl, @source.match(ELEMENTDECL_PATTERN, true)[1] ]
110
-
111
- when ENTITY_START
112
- match = @source.match(ENTITYDECL, true).to_a.compact
113
- match[0] = :entitydecl
114
- ref = false
115
- if match[1] == '%'
116
- ref = true
117
- match.delete_at 1
118
- end
119
- # Now we have to sort out what kind of entity reference this is
120
- if match[2] == 'SYSTEM'
121
- # External reference
122
- match[3] = match[3][1..-2] # PUBID
123
- match.delete_at(4) if match.size > 4 # Chop out NDATA decl
124
- # match is [ :entity, name, SYSTEM, pubid(, ndata)? ]
125
- elsif match[2] == 'PUBLIC'
126
- # External reference
127
- match[3] = match[3][1..-2] # PUBID
128
- match[4] = match[4][1..-2] # HREF
129
- # match is [ :entity, name, PUBLIC, pubid, href ]
130
- else
131
- match[2] = match[2][1..-2]
132
- match.pop if match.size == 4
133
- # match is [ :entity, name, value ]
134
- end
135
- match << '%' if ref
136
- return match
137
- when ATTLISTDECL_START
138
- md = @source.match(ATTLISTDECL_PATTERN, true)
139
- raise REXML::ParseException.new("Bad ATTLIST declaration!", @source) if md.nil?
140
- element = md[1]
141
- contents = md[0]
142
-
143
- pairs = {}
144
- values = md[0].scan(ATTDEF_RE)
145
- values.each do |attdef|
146
- unless attdef[3] == "#IMPLIED"
147
- attdef.compact!
148
- val = attdef[3]
149
- val = attdef[4] if val == "#FIXED "
150
- pairs[attdef[0]] = val
151
- end
152
- end
153
- return [ :attlistdecl, element, pairs, contents ]
154
- when NOTATIONDECL_START
155
- md = nil
156
- if @source.match(PUBLIC)
157
- md = @source.match(PUBLIC, true)
158
- elsif @source.match(SYSTEM)
159
- md = @source.match(SYSTEM, true)
160
- else
161
- raise REXML::ParseException.new("error parsing notation: no matching pattern", @source)
162
- end
163
- return [ :notationdecl, md[1], md[2], md[3] ]
164
- when CDATA_END
165
- @document_status = :after_doctype
166
- @source.match(CDATA_END, true)
167
- return [ :end_doctype ]
168
- end
169
- end
170
- begin
171
- if @source.buffer[0] == ?<
172
- if @source.buffer[1] == ?/
173
- last_tag, line_no = @tags.pop
174
- #md = @source.match_to_consume('>', CLOSE_MATCH)
175
- md = @source.match(dryml_mode? ? DRYML_CLOSE_MATCH : CLOSE_MATCH, true)
176
-
177
- valid_end_tag = if dryml_mode?
178
- last_tag =~ /^#{Regexp.escape(md[1])}(:.*)?/
179
- else
180
- last_tag == md[1]
181
- end
182
- raise REXML::ParseException.new("Missing end tag for "+
183
- "'#{last_tag}' (line #{line_no}) (got \"#{md[1]}\")",
184
- @source) unless valid_end_tag
185
- return [ :end_element, last_tag, true ]
186
- elsif @source.buffer[1] == ?!
187
- md = @source.match(/\A(\s*[^>]*>)/um)
188
- raise REXML::ParseException.new("Malformed node", @source) unless md
189
- if md[0][2] == ?-
190
- md = @source.match(COMMENT_PATTERN, true)
191
- return [ :comment, md[1] ] if md
192
- else
193
- md = @source.match(CDATA_PATTERN, true)
194
- return [ :cdata, md[1] ] if md
195
- end
196
- raise REXML::ParseException.new("Declarations can only occur "+
197
- "in the doctype declaration.", @source)
198
- elsif @source.buffer[1] == ??
199
- md = @source.match(INSTRUCTION_PATTERN, true)
200
- return [ :processing_instruction, md[1], md[2] ] if md
201
- raise REXML::ParseException.new("Bad instruction declaration",
202
- @source)
203
- else
204
- # Get the next tag
205
- md = @source.match(dryml_mode? ? DRYML_TAG_MATCH : TAG_MATCH, true)
206
- raise REXML::ParseException.new("malformed XML: missing tag start", @source) unless md
207
- attrs = []
208
- if md[2].size > 0
209
- attrs = md[2].scan(dryml_mode? ? DRYML_ATTRIBUTE_PATTERN : ATTRIBUTE_PATTERN)
210
- raise REXML::ParseException.new("error parsing attributes: [#{attrs.join ', '}], excess = \"#$'\"",
211
- @source) if $' and $'.strip.size > 0
212
- end
213
-
214
- if md[4]
215
- @closed = md[1]
216
- else
217
- cl = @source.current_line
218
- @tags.push([md[1], cl && cl[2]])
219
- end
220
- attributes = {}
221
- attrs.each { |a,b,c| attributes[a] = (c || true) }
222
- return [ :start_element, md[1], attributes, md[0],
223
- @source.respond_to?(:last_match_offset) && @source.last_match_offset ]
224
- end
225
- else
226
- md = @source.match(TEXT_PATTERN, true)
227
- if md[0].length == 0
228
- @source.match(/(\s+)/, true)
229
- end
230
- #return [ :text, "" ] if md[0].length == 0
231
- # unnormalized = Text::unnormalize(md[1], self)
232
- # return PullEvent.new(:text, md[1], unnormalized)
233
- return [ :text, md[1] ]
234
- end
235
- rescue REXML::ParseException
236
- raise
237
- rescue Exception, NameError => error
238
- raise REXML::ParseException.new("Exception parsing", @source, self, (error ? error : $!))
239
- end
240
- return [ :dummy ]
241
- end
242
- end
243
-
244
- class TreeParser
245
- def parse
246
- tag_stack = []
247
- in_doctype = false
248
- entities = nil
249
- begin
250
- while true
251
- event = @parser.pull
252
- case event[0]
253
- when :end_document
254
- return
255
- when :start_element
256
- tag_stack.push(event[1])
257
- # find the observers for namespaces
258
- @build_context = @build_context.add_element(event[1], event[2])
259
- @build_context.start_tag_source = event[3]
260
- @build_context.source_offset = event[4]
261
- when :end_element
262
- tag_stack.pop
263
- @build_context.has_end_tag = event[2]
264
- @build_context = @build_context.parent
265
- when :text
266
- if not in_doctype
267
- if @build_context[-1].instance_of? Text
268
- @build_context[-1] << event[1]
269
- else
270
- @build_context.add(
271
- Text.new(event[1], @build_context.whitespace, nil, true)
272
- ) unless (
273
- event[1].strip.size==0 and
274
- @build_context.ignore_whitespace_nodes
275
- )
276
- end
277
- end
278
- when :comment
279
- c = Comment.new(event[1])
280
- @build_context.add(c)
281
- when :cdata
282
- c = CData.new(event[1])
283
- @build_context.add(c)
284
- when :processing_instruction
285
- @build_context.add(Instruction.new(event[1], event[2]))
286
- when :end_doctype
287
- in_doctype = false
288
- entities.each { |k,v| entities[k] = @build_context.entities[k].value }
289
- @build_context = @build_context.parent
290
- when :start_doctype
291
- doctype = DocType.new(event[1..-1], @build_context)
292
- @build_context = doctype
293
- entities = {}
294
- in_doctype = true
295
- when :attlistdecl
296
- n = AttlistDecl.new(event[1..-1])
297
- @build_context.add(n)
298
- when :externalentity
299
- n = ExternalEntity.new(event[1])
300
- @build_context.add(n)
301
- when :elementdecl
302
- n = ElementDecl.new(event[1])
303
- @build_context.add(n)
304
- when :entitydecl
305
- entities[ event[1] ] = event[2] unless event[2] =~ /PUBLIC|SYSTEM/
306
- @build_context.add(Entity.new(event))
307
- when :notationdecl
308
- n = NotationDecl.new(*event[1..-1])
309
- @build_context.add(n)
310
- when :xmldecl
311
- x = XMLDecl.new(event[1], event[2], event[3])
312
- @build_context.add(x)
313
- end
314
- end
315
- rescue REXML::Validation::ValidationException
316
- raise
317
- rescue
318
- raise ParseException.new($!.message, @parser.source, @parser, $!)
319
- end
320
- end
321
- end
322
- end
323
-
324
- class Document
325
-
326
- attr_accessor :default_attribute_value
327
-
328
- end
329
-
330
- class Element
331
-
332
- def dryml_name
333
- expanded_name.sub(/:.*/, "")
334
- end
335
-
336
- attr_accessor :start_tag_source, :source_offset
337
-
338
- attr_writer :has_end_tag
339
- def has_end_tag?
340
- @has_end_tag
341
- end
342
-
343
- def parameter_tag?
344
- expanded_name =~ /:$/
345
- end
346
-
347
- end
348
-
349
- class Attribute
350
-
351
- def initialize_with_dryml(first, second=nil, parent=nil)
352
- initialize_without_dryml(first, second, parent)
353
- if first.is_a?(String) && second == true
354
- @value = true
355
- end
356
- end
357
- alias_method_chain :initialize, :dryml
358
-
359
- def value_with_dryml
360
- if has_rhs?
361
- value_without_dryml
362
- else
363
- element.document.default_attribute_value
364
- end
365
- end
366
- alias_method_chain :value, :dryml
367
-
368
- def to_string_with_dryml
369
- if has_rhs?
370
- to_string_without_dryml
371
- else
372
- @expanded_name
373
- end
374
- end
375
- alias_method_chain :to_string, :dryml
376
-
377
- def has_rhs?
378
- @value != true
379
- end
380
-
381
- end
382
-
383
- end
384
-
385
- module Hobo::Dryml
386
-
387
-
388
- # A REXML source that keeps track of where in the buffer it is
389
- class RexSource < REXML::Source
390
-
391
- def initialize(src)
392
- super(src)
393
- @buffer_offset = 0
394
- end
395
-
396
- attr_reader :last_match_offset
397
-
398
- def remember_match(m)
399
- if m
400
- @last_match = m
401
- @last_match_offset = @buffer_offset + m.begin(0)
402
- @orig[@last_match_offset..@last_match_offset+m[0].length] == @buffer[m.begin(0)..m.end(0)]
403
- end
404
- m
405
- end
406
-
407
- def advance_buffer(md)
408
- @buffer = md.post_match
409
- @buffer_offset += md.end(0)
410
- end
411
-
412
- def scan(pattern, cons=false)
413
- raise '!'
414
- return nil if @buffer.nil?
415
- rv = @buffer.scan(pattern)
416
- if cons and rv.size > 0
417
- advance_buffer(Regexp.last_match)
418
- end
419
- rv
420
- end
421
-
422
- def consume(pattern)
423
- md = remember_match(pattern.match(@buffer))
424
- if md
425
- advance_buffer(md)
426
- @buffer
427
- end
428
- end
429
-
430
- def match(pattern, cons=false)
431
- md = remember_match(pattern.match(@buffer))
432
- advance_buffer(md) if cons and md
433
- return md
434
- end
435
-
436
- def current_line
437
- pos = last_match_offset || 0
438
- [0, 0, @orig[0..pos].count("\n") + 1]
439
- end
440
-
441
- end
442
-
443
- end