hobo 0.7.3 → 0.7.4

Sign up to get free protection for your applications and to get access to all the features.
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