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
@@ -0,0 +1,473 @@
1
+ require 'rexml/parseexception'
2
+ require 'rexml/source'
3
+
4
+ # REXML version 3.1.5 and lower have a bug which does not trigger the doctype event for sax2 listener
5
+ # We have submitted a fix to REXML ticket number 92 with some test cases and a patch for 3.1.5
6
+ # http://www.germane-software.com/projects/rexml/ticket/92
7
+ # Until that is deployed this patched version of the REXML baseparse has the applied fixes
8
+ # The fix also accompanies a sax2parser fix which utilizes this class
9
+
10
+ module REXML
11
+ module Parsers
12
+ # = Using the Pull Parser
13
+ # <em>This API is experimental, and subject to change.</em>
14
+ # parser = PullParser.new( "<a>text<b att='val'/>txet</a>" )
15
+ # while parser.has_next?
16
+ # res = parser.next
17
+ # puts res[1]['att'] if res.start_tag? and res[0] == 'b'
18
+ # end
19
+ # See the PullEvent class for information on the content of the results.
20
+ # The data is identical to the arguments passed for the various events to
21
+ # the StreamListener API.
22
+ #
23
+ # Notice that:
24
+ # parser = PullParser.new( "<a>BAD DOCUMENT" )
25
+ # while parser.has_next?
26
+ # res = parser.next
27
+ # raise res[1] if res.error?
28
+ # end
29
+ #
30
+ # Nat Price gave me some good ideas for the API.
31
+ class BaseParserWithDoctypeFix
32
+ NCNAME_STR= '[\w:][\-\w\d.]*'
33
+ NAME_STR= "(?:#{NCNAME_STR}:)?#{NCNAME_STR}"
34
+
35
+ NAMECHAR = '[\-\w\d\.:]'
36
+ NAME = "([\\w:]#{NAMECHAR}*)"
37
+ NMTOKEN = "(?:#{NAMECHAR})+"
38
+ NMTOKENS = "#{NMTOKEN}(\\s+#{NMTOKEN})*"
39
+ REFERENCE = "(?:&#{NAME};|&#\\d+;|&#x[0-9a-fA-F]+;)"
40
+ REFERENCE_RE = /#{REFERENCE}/
41
+
42
+ DOCTYPE_START = /\A\s*<!DOCTYPE\s/um
43
+ DOCTYPE_PATTERN = /\s*<!DOCTYPE\s+(.*?)(\[|>)/um
44
+ ATTRIBUTE_PATTERN = /\s*(#{NAME_STR})\s*=\s*(["'])(.*?)\2/um
45
+ COMMENT_START = /\A<!--/u
46
+ COMMENT_PATTERN = /<!--(.*?)-->/um
47
+ CDATA_START = /\A<!\[CDATA\[/u
48
+ CDATA_END = /^\s*\]\s*>/um
49
+ CDATA_PATTERN = /<!\[CDATA\[(.*?)\]\]>/um
50
+ XMLDECL_START = /\A<\?xml\s/u;
51
+ XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>/um
52
+ INSTRUCTION_START = /\A<\?/u
53
+ INSTRUCTION_PATTERN = /<\?(.*?)(\s+.*?)?\?>/um
54
+ TAG_MATCH = /^<((?>#{NAME_STR}))\s*((?>\s+#{NAME_STR}\s*=\s*(["']).*?\3)*)\s*(\/)?>/um
55
+ CLOSE_MATCH = /^\s*<\/(#{NAME_STR})\s*>/um
56
+
57
+ VERSION = /\bversion\s*=\s*["'](.*?)['"]/um
58
+ ENCODING = /\bencoding=["'](.*?)['"]/um
59
+ STANDALONE = /\bstandalone=["'](.*?)['"]/um
60
+
61
+ ENTITY_START = /^\s*<!ENTITY/
62
+ IDENTITY = /^([!\*\w\-]+)(\s+#{NCNAME_STR})?(\s+["'](.*?)['"])?(\s+['"](.*?)["'])?/u
63
+ ELEMENTDECL_START = /^\s*<!ELEMENT/um
64
+ ELEMENTDECL_PATTERN = /^\s*(<!ELEMENT.*?)>/um
65
+ SYSTEMENTITY = /^\s*(%.*?;)\s*$/um
66
+ ENUMERATION = "\\(\\s*#{NMTOKEN}(?:\\s*\\|\\s*#{NMTOKEN})*\\s*\\)"
67
+ NOTATIONTYPE = "NOTATION\\s+\\(\\s*#{NAME}(?:\\s*\\|\\s*#{NAME})*\\s*\\)"
68
+ ENUMERATEDTYPE = "(?:(?:#{NOTATIONTYPE})|(?:#{ENUMERATION}))"
69
+ ATTTYPE = "(CDATA|ID|IDREF|IDREFS|ENTITY|ENTITIES|NMTOKEN|NMTOKENS|#{ENUMERATEDTYPE})"
70
+ ATTVALUE = "(?:\"((?:[^<&\"]|#{REFERENCE})*)\")|(?:'((?:[^<&']|#{REFERENCE})*)')"
71
+ DEFAULTDECL = "(#REQUIRED|#IMPLIED|(?:(#FIXED\\s+)?#{ATTVALUE}))"
72
+ ATTDEF = "\\s+#{NAME}\\s+#{ATTTYPE}\\s+#{DEFAULTDECL}"
73
+ ATTDEF_RE = /#{ATTDEF}/
74
+ ATTLISTDECL_START = /^\s*<!ATTLIST/um
75
+ ATTLISTDECL_PATTERN = /^\s*<!ATTLIST\s+#{NAME}(?:#{ATTDEF})*\s*>/um
76
+ NOTATIONDECL_START = /^\s*<!NOTATION/um
77
+ PUBLIC = /^\s*<!NOTATION\s+(\w[\-\w]*)\s+(PUBLIC)\s+(["'])(.*?)\3(?:\s+(["'])(.*?)\5)?\s*>/um
78
+ SYSTEM = /^\s*<!NOTATION\s+(\w[\-\w]*)\s+(SYSTEM)\s+(["'])(.*?)\3\s*>/um
79
+
80
+ TEXT_PATTERN = /\A([^<]*)/um
81
+
82
+ # Entity constants
83
+ PUBIDCHAR = "\x20\x0D\x0Aa-zA-Z0-9\\-()+,./:=?;!*@$_%#"
84
+ SYSTEMLITERAL = %Q{((?:"[^"]*")|(?:'[^']*'))}
85
+ PUBIDLITERAL = %Q{("[#{PUBIDCHAR}']*"|'[#{PUBIDCHAR}]*')}
86
+ EXTERNALID = "(?:(?:(SYSTEM)\\s+#{SYSTEMLITERAL})|(?:(PUBLIC)\\s+#{PUBIDLITERAL}\\s+#{SYSTEMLITERAL}))"
87
+ NDATADECL = "\\s+NDATA\\s+#{NAME}"
88
+ PEREFERENCE = "%#{NAME};"
89
+ ENTITYVALUE = %Q{((?:"(?:[^%&"]|#{PEREFERENCE}|#{REFERENCE})*")|(?:'([^%&']|#{PEREFERENCE}|#{REFERENCE})*'))}
90
+ PEDEF = "(?:#{ENTITYVALUE}|#{EXTERNALID})"
91
+ ENTITYDEF = "(?:#{ENTITYVALUE}|(?:#{EXTERNALID}(#{NDATADECL})?))"
92
+ PEDECL = "<!ENTITY\\s+(%)\\s+#{NAME}\\s+#{PEDEF}\\s*>"
93
+ GEDECL = "<!ENTITY\\s+#{NAME}\\s+#{ENTITYDEF}\\s*>"
94
+ ENTITYDECL = /\s*(?:#{GEDECL})|(?:#{PEDECL})/um
95
+
96
+ EREFERENCE = /&(?!#{NAME};)/
97
+
98
+ DEFAULT_ENTITIES = {
99
+ 'gt' => [/&gt;/, '&gt;', '>', />/],
100
+ 'lt' => [/&lt;/, '&lt;', '<', /</],
101
+ 'quot' => [/&quot;/, '&quot;', '"', /"/],
102
+ "apos" => [/&apos;/, "&apos;", "'", /'/]
103
+ }
104
+
105
+
106
+ ######################################################################
107
+ # These are patterns to identify common markup errors, to make the
108
+ # error messages more informative.
109
+ ######################################################################
110
+ MISSING_ATTRIBUTE_QUOTES = /^<#{NAME_STR}\s+#{NAME_STR}\s*=\s*[^"']/um
111
+
112
+ def initialize( source )
113
+ self.stream = source
114
+ end
115
+
116
+ def add_listener( listener )
117
+ if !defined?(@listeners) or !@listeners
118
+ @listeners = []
119
+ instance_eval <<-EOL
120
+ alias :_old_pull :pull
121
+ def pull
122
+ event = _old_pull
123
+ @listeners.each do |listener|
124
+ listener.receive event
125
+ end
126
+ event
127
+ end
128
+ EOL
129
+ end
130
+ @listeners << listener
131
+ end
132
+
133
+ attr_reader :source
134
+
135
+ def stream=( source )
136
+ @source = SourceFactory.create_from( source )
137
+ @closed = nil
138
+ @document_status = nil
139
+ @tags = []
140
+ @stack = []
141
+ @entities = []
142
+ end
143
+
144
+ def position
145
+ if @source.respond_to? :position
146
+ @source.position
147
+ else
148
+ # FIXME
149
+ 0
150
+ end
151
+ end
152
+
153
+ # Returns true if there are no more events
154
+ def empty?
155
+ #STDERR.puts "@source.empty? = #{@source.empty?}"
156
+ #STDERR.puts "@stack.empty? = #{@stack.empty?}"
157
+ return (@source.empty? and @stack.empty?)
158
+ end
159
+
160
+ # Returns true if there are more events. Synonymous with !empty?
161
+ def has_next?
162
+ return !(@source.empty? and @stack.empty?)
163
+ end
164
+
165
+ # Push an event back on the head of the stream. This method
166
+ # has (theoretically) infinite depth.
167
+ def unshift token
168
+ @stack.unshift(token)
169
+ end
170
+
171
+ # Peek at the +depth+ event in the stack. The first element on the stack
172
+ # is at depth 0. If +depth+ is -1, will parse to the end of the input
173
+ # stream and return the last event, which is always :end_document.
174
+ # Be aware that this causes the stream to be parsed up to the +depth+
175
+ # event, so you can effectively pre-parse the entire document (pull the
176
+ # entire thing into memory) using this method.
177
+ def peek depth=0
178
+ raise %Q[Illegal argument "#{depth}"] if depth < -1
179
+ temp = []
180
+ if depth == -1
181
+ temp.push(pull()) until empty?
182
+ else
183
+ while @stack.size+temp.size < depth+1
184
+ temp.push(pull())
185
+ end
186
+ end
187
+ @stack += temp if temp.size > 0
188
+ @stack[depth]
189
+ end
190
+
191
+ # Returns the next event. This is a +PullEvent+ object.
192
+ def pull
193
+ if @closed
194
+ x, @closed = @closed, nil
195
+ return [ :end_element, x ]
196
+ end
197
+ return [ :end_document ] if empty?
198
+ return @stack.shift if @stack.size > 0
199
+ @source.read if @source.buffer.size<2
200
+ #STDERR.puts "BUFFER = #{@source.buffer.inspect}"
201
+ if @document_status == nil
202
+ #@source.consume( /^\s*/um )
203
+ word = @source.match( /^((?:\s+)|(?:<[^>]*>))/um )
204
+ word = word[1] unless word.nil?
205
+ #STDERR.puts "WORD = #{word.inspect}"
206
+ case word
207
+ when COMMENT_START
208
+ return [ :comment, @source.match( COMMENT_PATTERN, true )[1] ]
209
+ when XMLDECL_START
210
+ #STDERR.puts "XMLDECL"
211
+ results = @source.match( XMLDECL_PATTERN, true )[1]
212
+ version = VERSION.match( results )
213
+ version = version[1] unless version.nil?
214
+ encoding = ENCODING.match(results)
215
+ encoding = encoding[1] unless encoding.nil?
216
+ @source.encoding = encoding
217
+ standalone = STANDALONE.match(results)
218
+ standalone = standalone[1] unless standalone.nil?
219
+ return [ :xmldecl, version, encoding, standalone ]
220
+ when INSTRUCTION_START
221
+ return [ :processing_instruction, *@source.match(INSTRUCTION_PATTERN, true)[1,2] ]
222
+ when DOCTYPE_START
223
+ md = @source.match( DOCTYPE_PATTERN, true )
224
+ identity = md[1]
225
+ close = md[2]
226
+ identity =~ IDENTITY
227
+ name = $1
228
+ raise REXML::ParseException.new("DOCTYPE is missing a name") if name.nil?
229
+ pub_sys = $2.nil? ? nil : $2.strip
230
+ long_name = $4.nil? ? nil : $4.strip
231
+ uri = $6.nil? ? nil : $6.strip
232
+ args = [ :start_doctype, name, pub_sys, long_name, uri ]
233
+ if close == ">"
234
+ @document_status = :after_doctype
235
+ @source.read if @source.buffer.size<2
236
+ md = @source.match(/^\s*/um, true)
237
+ @stack << [ :end_doctype ]
238
+ else
239
+ @document_status = :in_doctype
240
+ end
241
+ return args
242
+ when /^\s+/
243
+ else
244
+ @document_status = :after_doctype
245
+ @source.read if @source.buffer.size<2
246
+ md = @source.match(/\s*/um, true)
247
+ end
248
+ end
249
+ if @document_status == :in_doctype
250
+ md = @source.match(/\s*(.*?>)/um)
251
+ case md[1]
252
+ when SYSTEMENTITY
253
+ match = @source.match( SYSTEMENTITY, true )[1]
254
+ return [ :externalentity, match ]
255
+
256
+ when ELEMENTDECL_START
257
+ return [ :elementdecl, @source.match( ELEMENTDECL_PATTERN, true )[1] ]
258
+
259
+ when ENTITY_START
260
+ match = @source.match( ENTITYDECL, true ).to_a.compact
261
+ match[0] = :entitydecl
262
+ ref = false
263
+ if match[1] == '%'
264
+ ref = true
265
+ match.delete_at 1
266
+ end
267
+ # Now we have to sort out what kind of entity reference this is
268
+ if match[2] == 'SYSTEM'
269
+ # External reference
270
+ match[3] = match[3][1..-2] # PUBID
271
+ match.delete_at(4) if match.size > 4 # Chop out NDATA decl
272
+ # match is [ :entity, name, SYSTEM, pubid(, ndata)? ]
273
+ elsif match[2] == 'PUBLIC'
274
+ # External reference
275
+ match[3] = match[3][1..-2] # PUBID
276
+ match[4] = match[4][1..-2] # HREF
277
+ # match is [ :entity, name, PUBLIC, pubid, href ]
278
+ else
279
+ match[2] = match[2][1..-2]
280
+ match.pop if match.size == 4
281
+ # match is [ :entity, name, value ]
282
+ end
283
+ match << '%' if ref
284
+ return match
285
+ when ATTLISTDECL_START
286
+ md = @source.match( ATTLISTDECL_PATTERN, true )
287
+ raise REXML::ParseException.new( "Bad ATTLIST declaration!", @source ) if md.nil?
288
+ element = md[1]
289
+ contents = md[0]
290
+
291
+ pairs = {}
292
+ values = md[0].scan( ATTDEF_RE )
293
+ values.each do |attdef|
294
+ unless attdef[3] == "#IMPLIED"
295
+ attdef.compact!
296
+ val = attdef[3]
297
+ val = attdef[4] if val == "#FIXED "
298
+ pairs[attdef[0]] = val
299
+ end
300
+ end
301
+ return [ :attlistdecl, element, pairs, contents ]
302
+ when NOTATIONDECL_START
303
+ md = nil
304
+ if @source.match( PUBLIC )
305
+ md = @source.match( PUBLIC, true )
306
+ vals = [md[1],md[2],md[4],md[6]]
307
+ elsif @source.match( SYSTEM )
308
+ md = @source.match( SYSTEM, true )
309
+ vals = [md[1],md[2],nil,md[4]]
310
+ else
311
+ raise REXML::ParseException.new( "error parsing notation: no matching pattern", @source )
312
+ end
313
+ return [ :notationdecl, *vals ]
314
+ when CDATA_END
315
+ @document_status = :after_doctype
316
+ @source.match( CDATA_END, true )
317
+ return [ :end_doctype ]
318
+ end
319
+ end
320
+ begin
321
+ if @source.buffer[0] == ?<
322
+ if @source.buffer[1] == ?/
323
+ last_tag = @tags.pop
324
+ #md = @source.match_to_consume( '>', CLOSE_MATCH)
325
+ md = @source.match( CLOSE_MATCH, true )
326
+ raise REXML::ParseException.new( "Missing end tag for "+
327
+ "'#{last_tag}' (got \"#{md[1]}\")",
328
+ @source) unless last_tag == md[1]
329
+ return [ :end_element, last_tag ]
330
+ elsif @source.buffer[1] == ?!
331
+ md = @source.match(/\A(\s*[^>]*>)/um)
332
+ #STDERR.puts "SOURCE BUFFER = #{source.buffer}, #{source.buffer.size}"
333
+ raise REXML::ParseException.new("Malformed node", @source) unless md
334
+ if md[0][2] == ?-
335
+ md = @source.match( COMMENT_PATTERN, true )
336
+ return [ :comment, md[1] ] if md
337
+ else
338
+ md = @source.match( CDATA_PATTERN, true )
339
+ return [ :cdata, md[1] ] if md
340
+ end
341
+ raise REXML::ParseException.new( "Declarations can only occur "+
342
+ "in the doctype declaration.", @source)
343
+ elsif @source.buffer[1] == ??
344
+ md = @source.match( INSTRUCTION_PATTERN, true )
345
+ return [ :processing_instruction, md[1], md[2] ] if md
346
+ raise REXML::ParseException.new( "Bad instruction declaration",
347
+ @source)
348
+ else
349
+ # Get the next tag
350
+ md = @source.match(TAG_MATCH, true)
351
+ unless md
352
+ # Check for missing attribute quotes
353
+ raise REXML::ParseException.new("missing attribute quote", @source) if @source.match(MISSING_ATTRIBUTE_QUOTES )
354
+ raise REXML::ParseException.new("malformed XML: missing tag start", @source)
355
+ end
356
+ attrs = []
357
+ if md[2].size > 0
358
+ attrs = md[2].scan( ATTRIBUTE_PATTERN )
359
+ raise REXML::ParseException.new( "error parsing attributes: [#{attrs.join ', '}], excess = \"#$'\"", @source) if $' and $'.strip.size > 0
360
+ end
361
+
362
+ if md[4]
363
+ @closed = md[1]
364
+ else
365
+ @tags.push( md[1] )
366
+ end
367
+ attributes = {}
368
+ attrs.each { |a,b,c| attributes[a] = c }
369
+ return [ :start_element, md[1], attributes ]
370
+ end
371
+ else
372
+ md = @source.match( TEXT_PATTERN, true )
373
+ if md[0].length == 0
374
+ puts "EMPTY = #{empty?}"
375
+ puts "BUFFER = \"#{@source.buffer}\""
376
+ @source.match( /(\s+)/, true )
377
+ end
378
+ #STDERR.puts "GOT #{md[1].inspect}" unless md[0].length == 0
379
+ #return [ :text, "" ] if md[0].length == 0
380
+ # unnormalized = Text::unnormalize( md[1], self )
381
+ # return PullEvent.new( :text, md[1], unnormalized )
382
+ return [ :text, md[1] ]
383
+ end
384
+ rescue REXML::ParseException
385
+ raise
386
+ rescue Exception, NameError => error
387
+ raise REXML::ParseException.new( "Exception parsing",
388
+ @source, self, (error ? error : $!) )
389
+ end
390
+ return [ :dummy ]
391
+ end
392
+
393
+ def entity( reference, entities )
394
+ value = nil
395
+ value = entities[ reference ] if entities
396
+ if not value
397
+ value = DEFAULT_ENTITIES[ reference ]
398
+ value = value[2] if value
399
+ end
400
+ unnormalize( value, entities ) if value
401
+ end
402
+
403
+ # Escapes all possible entities
404
+ def normalize( input, entities=nil, entity_filter=nil )
405
+ copy = input.clone
406
+ # Doing it like this rather than in a loop improves the speed
407
+ copy.gsub!( EREFERENCE, '&amp;' )
408
+ entities.each do |key, value|
409
+ copy.gsub!( value, "&#{key};" ) unless entity_filter and
410
+ entity_filter.include?(entity)
411
+ end if entities
412
+ copy.gsub!( EREFERENCE, '&amp;' )
413
+ DEFAULT_ENTITIES.each do |key, value|
414
+ copy.gsub!( value[3], value[1] )
415
+ end
416
+ copy
417
+ end
418
+
419
+ # Unescapes all possible entities
420
+ def unnormalize( string, entities=nil, filter=nil )
421
+ rv = string.clone
422
+ rv.gsub!( /\r\n?/, "\n" )
423
+ matches = rv.scan( REFERENCE_RE )
424
+ return rv if matches.size == 0
425
+ rv.gsub!( /&#0*((?:\d+)|(?:x[a-fA-F0-9]+));/ ) {|m|
426
+ m=$1
427
+ m = "0#{m}" if m[0] == ?x
428
+ [Integer(m)].pack('U*')
429
+ }
430
+ matches.collect!{|x|x[0]}.compact!
431
+ if matches.size > 0
432
+ matches.each do |entity_reference|
433
+ unless filter and filter.include?(entity_reference)
434
+ entity_value = entity( entity_reference, entities )
435
+ if entity_value
436
+ re = /&#{entity_reference};/
437
+ rv.gsub!( re, entity_value )
438
+ end
439
+ end
440
+ end
441
+ matches.each do |entity_reference|
442
+ unless filter and filter.include?(entity_reference)
443
+ er = DEFAULT_ENTITIES[entity_reference]
444
+ rv.gsub!( er[0], er[2] ) if er
445
+ end
446
+ end
447
+ rv.gsub!( /&amp;/, '&' )
448
+ end
449
+ rv
450
+ end
451
+ end
452
+ end
453
+ end
454
+
455
+ =begin
456
+ case event[0]
457
+ when :start_element
458
+ when :text
459
+ when :end_element
460
+ when :processing_instruction
461
+ when :cdata
462
+ when :comment
463
+ when :xmldecl
464
+ when :start_doctype
465
+ when :end_doctype
466
+ when :externalentity
467
+ when :elementdecl
468
+ when :entity
469
+ when :attlistdecl
470
+ when :notationdecl
471
+ when :end_doctype
472
+ end
473
+ =end