dryml 1.1.0.pre0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +9 -0
- data/LICENSE.txt +22 -0
- data/README +36 -0
- data/Rakefile +47 -0
- data/TODO.txt +6 -0
- data/lib/dryml/dryml_builder.rb +140 -0
- data/lib/dryml/dryml_doc.rb +155 -0
- data/lib/dryml/dryml_generator.rb +271 -0
- data/lib/dryml/dryml_support_controller.rb +13 -0
- data/lib/dryml/helper.rb +69 -0
- data/lib/dryml/parser/attribute.rb +41 -0
- data/lib/dryml/parser/base_parser.rb +254 -0
- data/lib/dryml/parser/document.rb +57 -0
- data/lib/dryml/parser/element.rb +27 -0
- data/lib/dryml/parser/elements.rb +21 -0
- data/lib/dryml/parser/source.rb +58 -0
- data/lib/dryml/parser/text.rb +13 -0
- data/lib/dryml/parser/tree_parser.rb +67 -0
- data/lib/dryml/parser.rb +3 -0
- data/lib/dryml/part_context.rb +133 -0
- data/lib/dryml/scoped_variables.rb +42 -0
- data/lib/dryml/static_tags +98 -0
- data/lib/dryml/tag_parameters.rb +32 -0
- data/lib/dryml/taglib.rb +124 -0
- data/lib/dryml/template.rb +1021 -0
- data/lib/dryml/template_environment.rb +613 -0
- data/lib/dryml/template_handler.rb +187 -0
- data/lib/dryml.rb +291 -0
- data/taglibs/core.dryml +136 -0
- data/test/dryml.rdoctest +68 -0
- metadata +131 -0
data/lib/dryml/helper.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# An ActionView Helper
|
2
|
+
module Dryml::Helper
|
3
|
+
def context_map(enum = this)
|
4
|
+
# TODO: Calls to respond_to? in here can cause the full collection hiding behind a scoped collection to get loaded
|
5
|
+
res = []
|
6
|
+
empty = true
|
7
|
+
scope.new_scope(:repeat_collection => enum, :even_odd => 'odd', :repeat_item => nil) do
|
8
|
+
if enum.respond_to?(:each_pair)
|
9
|
+
enum.each_pair do |key, value|
|
10
|
+
scope.repeat_item = value
|
11
|
+
empty = false;
|
12
|
+
self.this_key = key;
|
13
|
+
new_object_context(value) { res << yield }
|
14
|
+
scope.even_odd = scope.even_odd == "even" ? "odd" : "even"
|
15
|
+
end
|
16
|
+
else
|
17
|
+
index = 0
|
18
|
+
enum.each do |e|
|
19
|
+
scope.repeat_item = e
|
20
|
+
empty = false;
|
21
|
+
if enum == this
|
22
|
+
new_field_context(index, e) { res << yield }
|
23
|
+
else
|
24
|
+
new_object_context(e) { res << yield }
|
25
|
+
end
|
26
|
+
scope.even_odd = scope.even_odd == "even" ? "odd" : "even"
|
27
|
+
index += 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
Dryml.last_if = !empty
|
31
|
+
end
|
32
|
+
res
|
33
|
+
end
|
34
|
+
|
35
|
+
def first_item?
|
36
|
+
if scope.repeat_collection.respond_to? :each_pair
|
37
|
+
this == scope.repeat_collection.first.try.last
|
38
|
+
else
|
39
|
+
this == scope.repeat_collection.first
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def last_item?
|
45
|
+
if scope.repeat_collection.respond_to? :each_pair
|
46
|
+
this == scope.repeat_collection.last.try.last
|
47
|
+
else
|
48
|
+
this == scope.repeat_collection.last
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def param_name_for(path)
|
53
|
+
field_path = field_path.to_s.split(".") if field_path.is_one_of?(String, Symbol)
|
54
|
+
attrs = path.rest.map{|part| "[#{part.to_s.sub /\?$/, ''}]"}.join
|
55
|
+
"#{path.first}#{attrs}"
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def param_name_for_this(foreign_key=false)
|
60
|
+
return "" unless form_this
|
61
|
+
name = if foreign_key && (refl = this_field_reflection) && refl.macro == :belongs_to
|
62
|
+
param_name_for(path_for_form_field[0..-2] + [refl.primary_key_name])
|
63
|
+
else
|
64
|
+
param_name_for(path_for_form_field)
|
65
|
+
end
|
66
|
+
register_form_field(name)
|
67
|
+
name
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Dryml::Parser
|
2
|
+
|
3
|
+
class Attribute < REXML::Attribute
|
4
|
+
|
5
|
+
def initialize(first, second=nil, parent=nil)
|
6
|
+
super
|
7
|
+
if first.is_a?(String) && second == true
|
8
|
+
@value = true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def value
|
13
|
+
if has_rhs?
|
14
|
+
super
|
15
|
+
else
|
16
|
+
element.document.default_attribute_value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_string
|
21
|
+
if has_rhs?
|
22
|
+
super
|
23
|
+
else
|
24
|
+
@expanded_name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def has_rhs?
|
29
|
+
@value != true
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# Override to supress Text.check call
|
34
|
+
def element=( element )
|
35
|
+
@element = element
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,254 @@
|
|
1
|
+
module Dryml::Parser
|
2
|
+
|
3
|
+
class BaseParser < REXML::Parsers::BaseParser
|
4
|
+
|
5
|
+
NEW_REX = defined?(REXML::VERSION) && REXML::VERSION =~ /3\.1\.(\d)(?:\.(\d))?/ && $1.to_i*1000 + $2.to_i >= 7002
|
6
|
+
|
7
|
+
DRYML_NAME_STR = "#{NCNAME_STR}(?::(?:#{NCNAME_STR})?)?"
|
8
|
+
DRYML_ATTRIBUTE_PATTERN = if NEW_REX
|
9
|
+
/\s*(#{NAME_STR})(?:\s*=\s*(["'])(.*?)\4)?/um
|
10
|
+
else
|
11
|
+
/\s*(#{NAME_STR})(?:\s*=\s*(["'])(.*?)\2)?/um
|
12
|
+
end
|
13
|
+
DRYML_TAG_MATCH = if NEW_REX
|
14
|
+
/^<((?>#{DRYML_NAME_STR}))\s*((?>\s+#{NAME_STR}(?:\s*=\s*(["']).*?\5)?)*)\s*(\/)?>/um
|
15
|
+
else
|
16
|
+
/^<((?>#{DRYML_NAME_STR}))\s*((?>\s+#{NAME_STR}(?:\s*=\s*(["']).*?\3)?)*)\s*(\/)?>/um
|
17
|
+
end
|
18
|
+
DRYML_CLOSE_MATCH = /^\s*<\/(#{DRYML_NAME_STR})\s*>/um
|
19
|
+
|
20
|
+
# For compatibility with REXML 3.1.7.3
|
21
|
+
IDENTITY = /^([!\*\w\-]+)(\s+#{NCNAME_STR})?(\s+["'](.*?)['"])?(\s+['"](.*?)["'])?/u
|
22
|
+
|
23
|
+
def pull
|
24
|
+
if @closed
|
25
|
+
x, @closed = @closed, nil
|
26
|
+
return [ :end_element, x, false ]
|
27
|
+
end
|
28
|
+
return [ :end_document ] if empty?
|
29
|
+
return @stack.shift if @stack.size > 0
|
30
|
+
#STDERR.puts @source.encoding
|
31
|
+
@source.read if @source.buffer.size<2
|
32
|
+
#STDERR.puts "BUFFER = #{@source.buffer.inspect}"
|
33
|
+
if @document_status == nil
|
34
|
+
#@source.consume( /^\s*/um )
|
35
|
+
word = @source.match( /^((?:\s+)|(?:<[^>]*>))/um ) #word = @source.match(/(<[^>]*)>/um)
|
36
|
+
word = word[1] unless word.nil?
|
37
|
+
#STDERR.puts "WORD = #{word.inspect}"
|
38
|
+
case word
|
39
|
+
when COMMENT_START
|
40
|
+
return [ :comment, @source.match( COMMENT_PATTERN, true )[1] ]
|
41
|
+
when XMLDECL_START
|
42
|
+
#STDERR.puts "XMLDECL"
|
43
|
+
results = @source.match( XMLDECL_PATTERN, true )[1]
|
44
|
+
version = VERSION.match( results )
|
45
|
+
version = version[1] unless version.nil?
|
46
|
+
encoding = ENCODING.match(results)
|
47
|
+
encoding = encoding[1] unless encoding.nil?
|
48
|
+
@source.encoding = encoding
|
49
|
+
standalone = STANDALONE.match(results)
|
50
|
+
standalone = standalone[1] unless standalone.nil?
|
51
|
+
return [ :xmldecl, version, encoding, standalone ]
|
52
|
+
when INSTRUCTION_START
|
53
|
+
return [ :processing_instruction, *@source.match(INSTRUCTION_PATTERN, true)[1,2] ]
|
54
|
+
when DOCTYPE_START
|
55
|
+
md = @source.match( DOCTYPE_PATTERN, true )
|
56
|
+
#@nsstack.unshift(curr_ns=Set.new)
|
57
|
+
identity = md[1]
|
58
|
+
close = md[2]
|
59
|
+
identity =~ IDENTITY
|
60
|
+
name = $1
|
61
|
+
raise REXML::ParseException.new("DOCTYPE is missing a name") if name.nil?
|
62
|
+
pub_sys = $2.nil? ? nil : $2.strip
|
63
|
+
long_name = $4.nil? ? nil : $4.strip
|
64
|
+
uri = $6.nil? ? nil : $6.strip
|
65
|
+
args = [ :start_doctype, name, pub_sys, long_name, uri ]
|
66
|
+
if close == ">"
|
67
|
+
@document_status = :after_doctype
|
68
|
+
@source.read if @source.buffer.size<2
|
69
|
+
md = @source.match(/^\s*/um, true)
|
70
|
+
@stack << [ :end_doctype ]
|
71
|
+
else
|
72
|
+
@document_status = :in_doctype
|
73
|
+
end
|
74
|
+
return args
|
75
|
+
when /^\s+/
|
76
|
+
else
|
77
|
+
@document_status = :after_doctype
|
78
|
+
@source.read if @source.buffer.size<2
|
79
|
+
md = @source.match(/\s*/um, true)
|
80
|
+
if @source.encoding == "UTF-8"
|
81
|
+
if @source.buffer.respond_to? :force_encoding
|
82
|
+
@source.buffer.force_encoding(Encoding::UTF_8)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
if @document_status == :in_doctype
|
88
|
+
md = @source.match(/\s*(.*?>)/um)
|
89
|
+
case md[1]
|
90
|
+
when SYSTEMENTITY
|
91
|
+
match = @source.match( SYSTEMENTITY, true )[1]
|
92
|
+
return [ :externalentity, match ]
|
93
|
+
|
94
|
+
when ELEMENTDECL_START
|
95
|
+
return [ :elementdecl, @source.match( ELEMENTDECL_PATTERN, true )[1] ]
|
96
|
+
|
97
|
+
when ENTITY_START
|
98
|
+
match = @source.match( ENTITYDECL, true ).to_a.compact
|
99
|
+
match[0] = :entitydecl
|
100
|
+
ref = false
|
101
|
+
if match[1] == '%'
|
102
|
+
ref = true
|
103
|
+
match.delete_at 1
|
104
|
+
end
|
105
|
+
# Now we have to sort out what kind of entity reference this is
|
106
|
+
if match[2] == 'SYSTEM'
|
107
|
+
# External reference
|
108
|
+
match[3] = match[3][1..-2] # PUBID
|
109
|
+
match.delete_at(4) if match.size > 4 # Chop out NDATA decl
|
110
|
+
# match is [ :entity, name, SYSTEM, pubid(, ndata)? ]
|
111
|
+
elsif match[2] == 'PUBLIC'
|
112
|
+
# External reference
|
113
|
+
match[3] = match[3][1..-2] # PUBID
|
114
|
+
match[4] = match[4][1..-2] # HREF
|
115
|
+
# match is [ :entity, name, PUBLIC, pubid, href ]
|
116
|
+
else
|
117
|
+
match[2] = match[2][1..-2]
|
118
|
+
match.pop if match.size == 4
|
119
|
+
# match is [ :entity, name, value ]
|
120
|
+
end
|
121
|
+
match << '%' if ref
|
122
|
+
return match
|
123
|
+
when ATTLISTDECL_START
|
124
|
+
md = @source.match( ATTLISTDECL_PATTERN, true )
|
125
|
+
raise REXML::ParseException.new( "Bad ATTLIST declaration!", @source ) if md.nil?
|
126
|
+
element = md[1]
|
127
|
+
contents = md[0]
|
128
|
+
|
129
|
+
pairs = {}
|
130
|
+
values = md[0].scan( ATTDEF_RE )
|
131
|
+
values.each do |attdef|
|
132
|
+
unless attdef[3] == "#IMPLIED"
|
133
|
+
attdef.compact!
|
134
|
+
val = attdef[3]
|
135
|
+
val = attdef[4] if val == "#FIXED "
|
136
|
+
pairs[attdef[0]] = val
|
137
|
+
if attdef[0] =~ /^xmlns:(.*)/
|
138
|
+
#@nsstack[0] << $1
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
return [ :attlistdecl, element, pairs, contents ]
|
143
|
+
when NOTATIONDECL_START
|
144
|
+
md = nil
|
145
|
+
if @source.match( PUBLIC )
|
146
|
+
md = @source.match( PUBLIC, true )
|
147
|
+
vals = [md[1],md[2],md[4],md[6]]
|
148
|
+
elsif @source.match( SYSTEM )
|
149
|
+
md = @source.match( SYSTEM, true )
|
150
|
+
vals = [md[1],md[2],nil,md[4]]
|
151
|
+
else
|
152
|
+
raise REXML::ParseException.new( "error parsing notation: no matching pattern", @source )
|
153
|
+
end
|
154
|
+
return [ :notationdecl, *vals ]
|
155
|
+
when CDATA_END
|
156
|
+
@document_status = :after_doctype
|
157
|
+
@source.match( CDATA_END, true )
|
158
|
+
return [ :end_doctype ]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
begin
|
162
|
+
if @source.buffer[0] == ?<
|
163
|
+
if @source.buffer[1] == ?/
|
164
|
+
#@nsstack.shift
|
165
|
+
last_tag, line_no = @tags.pop
|
166
|
+
#md = @source.match_to_consume( '>', CLOSE_MATCH)
|
167
|
+
md = @source.match(DRYML_CLOSE_MATCH, true)
|
168
|
+
|
169
|
+
valid_end_tag = last_tag =~ /^#{Regexp.escape(md[1])}(:.*)?$/
|
170
|
+
raise REXML::ParseException.new( "Missing end tag for "+
|
171
|
+
"'#{last_tag}' (line #{line_no}) (got \"#{md[1]}\")",
|
172
|
+
@source) unless valid_end_tag
|
173
|
+
return [ :end_element, last_tag, true ]
|
174
|
+
elsif @source.buffer[1] == ?!
|
175
|
+
md = @source.match(/\A(\s*[^>]*>)/um)
|
176
|
+
#STDERR.puts "SOURCE BUFFER = #{source.buffer}, #{source.buffer.size}"
|
177
|
+
raise REXML::ParseException.new("Malformed node", @source) unless md
|
178
|
+
if md[0][2] == ?-
|
179
|
+
md = @source.match( COMMENT_PATTERN, true )
|
180
|
+
|
181
|
+
case md[1]
|
182
|
+
when /--/, /-$/
|
183
|
+
raise REXML::ParseException.new("Malformed comment", @source)
|
184
|
+
end
|
185
|
+
|
186
|
+
return [ :comment, md[1] ] if md
|
187
|
+
else
|
188
|
+
md = @source.match( CDATA_PATTERN, true )
|
189
|
+
return [ :cdata, md[1] ] if md
|
190
|
+
end
|
191
|
+
raise REXML::ParseException.new( "Declarations can only occur "+
|
192
|
+
"in the doctype declaration.", @source)
|
193
|
+
elsif @source.buffer[1] == ??
|
194
|
+
md = @source.match( INSTRUCTION_PATTERN, true )
|
195
|
+
return [ :processing_instruction, md[1], md[2] ] if md
|
196
|
+
raise REXML::ParseException.new( "Bad instruction declaration",
|
197
|
+
@source)
|
198
|
+
else
|
199
|
+
# Get the next tag
|
200
|
+
md = @source.match(DRYML_TAG_MATCH, true)
|
201
|
+
unless md
|
202
|
+
# Check for missing attribute quotes
|
203
|
+
raise REXML::ParseException.new("missing attribute quote", @source) if
|
204
|
+
defined?(MISSING_ATTRIBUTE_QUOTES) && @source.match(MISSING_ATTRIBUTE_QUOTES)
|
205
|
+
raise REXML::ParseException.new("malformed XML: missing tag start", @source)
|
206
|
+
|
207
|
+
end
|
208
|
+
attributes = {}
|
209
|
+
#@nsstack.unshift(curr_ns=Set.new)
|
210
|
+
if md[2].size > 0
|
211
|
+
attrs = md[2].scan(DRYML_ATTRIBUTE_PATTERN)
|
212
|
+
raise REXML::ParseException.new( "error parsing attributes: [#{attrs.join ', '}], excess = \"#$'\"", @source) if $' and $'.strip.size > 0
|
213
|
+
attrs.each { |a,b,c,d,e|
|
214
|
+
val = NEW_REX ? e : c
|
215
|
+
if attributes.has_key? a
|
216
|
+
msg = "Duplicate attribute #{a.inspect}"
|
217
|
+
raise REXML::ParseException.new( msg, @source, self)
|
218
|
+
end
|
219
|
+
attributes[a] = val || true
|
220
|
+
}
|
221
|
+
end
|
222
|
+
|
223
|
+
if md[NEW_REX ? 6 : 4]
|
224
|
+
@closed = md[1]
|
225
|
+
#@nsstack.shift
|
226
|
+
else
|
227
|
+
cl = @source.current_line
|
228
|
+
@tags.push( [md[1], cl && cl[2]] )
|
229
|
+
end
|
230
|
+
return [ :start_element, md[1], attributes, md[0],
|
231
|
+
@source.respond_to?(:last_match_offset) && @source.last_match_offset ]
|
232
|
+
end
|
233
|
+
else
|
234
|
+
md = @source.match( TEXT_PATTERN, true )
|
235
|
+
if md[0].length == 0
|
236
|
+
@source.match( /(\s+)/, true )
|
237
|
+
end
|
238
|
+
#STDERR.puts "GOT #{md[1].inspect}" unless md[0].length == 0
|
239
|
+
#return [ :text, "" ] if md[0].length == 0
|
240
|
+
# unnormalized = Text::unnormalize( md[1], self )
|
241
|
+
# return PullEvent.new( :text, md[1], unnormalized )
|
242
|
+
return [ :text, md[1] ]
|
243
|
+
end
|
244
|
+
rescue REXML::ParseException
|
245
|
+
raise
|
246
|
+
rescue Exception, NameError => error
|
247
|
+
raise REXML::ParseException.new( "Exception parsing",
|
248
|
+
@source, self, (error ? error : $!) )
|
249
|
+
end
|
250
|
+
return [ :dummy ]
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Dryml
|
2
|
+
|
3
|
+
module Parser
|
4
|
+
|
5
|
+
class Document < REXML::Document
|
6
|
+
|
7
|
+
def initialize(source, path)
|
8
|
+
super(nil)
|
9
|
+
|
10
|
+
# Replace <%...%> scriptlets with xml-safe references into a hash of scriptlets
|
11
|
+
@scriptlets = {}
|
12
|
+
source = source.gsub(/<%(.*?)%>/m) do
|
13
|
+
_, scriptlet = *Regexp.last_match
|
14
|
+
id = @scriptlets.size + 1
|
15
|
+
@scriptlets[id] = scriptlet
|
16
|
+
newlines = "\n" * scriptlet.count("\n")
|
17
|
+
"[![DRYML-ERB#{id}#{newlines}]!]"
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
@reference_src = "<dryml_page>" + source + "</dryml_page>"
|
22
|
+
rex_src = Dryml::Parser::Source.new(@reference_src)
|
23
|
+
|
24
|
+
@elements = Dryml::Parser::Elements.new(self)
|
25
|
+
build(rex_src)
|
26
|
+
|
27
|
+
rescue REXML::ParseException => e
|
28
|
+
raise Dryml::DrymlSyntaxError, "File: #{path}\n#{e}"
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def element_line_num(el)
|
33
|
+
offset = el.source_offset
|
34
|
+
@reference_src[0..offset].count("\n") + 1
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def default_attribute_value
|
39
|
+
"&true"
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def restore_erb_scriptlets(src)
|
44
|
+
src.gsub(/\[!\[DRYML-ERB(\d+)\s*\]!\]/m) {|s| "<%#{@scriptlets[$1.to_i]}%>" }
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
private
|
49
|
+
def build( source )
|
50
|
+
Dryml::Parser::TreeParser.new( source, self ).parse
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Dryml::Parser
|
2
|
+
|
3
|
+
class Element < REXML::Element
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
super
|
7
|
+
@elements = Dryml::Parser::Elements.new(self)
|
8
|
+
end
|
9
|
+
|
10
|
+
def dryml_name
|
11
|
+
expanded_name.sub(/:.*/, "")
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :start_tag_source, :source_offset
|
15
|
+
|
16
|
+
attr_writer :has_end_tag
|
17
|
+
def has_end_tag?
|
18
|
+
@has_end_tag
|
19
|
+
end
|
20
|
+
|
21
|
+
def parameter_tag?
|
22
|
+
expanded_name =~ /:$/
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Dryml::Parser
|
2
|
+
|
3
|
+
class Elements < REXML::Elements
|
4
|
+
|
5
|
+
# Override to ensure DRYML elements are created
|
6
|
+
def add(element=nil)
|
7
|
+
rv = nil
|
8
|
+
if element.nil?
|
9
|
+
Dryml::Parser::Element.new("", self, @element.context)
|
10
|
+
elsif not element.kind_of?(Element)
|
11
|
+
Dryml::Parser::Element.new(element, self, @element.context)
|
12
|
+
else
|
13
|
+
@element << element
|
14
|
+
element.context = @element.context
|
15
|
+
element
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Dryml::Parser
|
2
|
+
|
3
|
+
# A REXML source that keeps track of where in the buffer it is
|
4
|
+
class Source < REXML::Source
|
5
|
+
|
6
|
+
def initialize(src)
|
7
|
+
super(src)
|
8
|
+
@buffer_offset = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :last_match_offset
|
12
|
+
|
13
|
+
def remember_match(m)
|
14
|
+
if m
|
15
|
+
@last_match = m
|
16
|
+
@last_match_offset = @buffer_offset + m.begin(0)
|
17
|
+
@orig[@last_match_offset..@last_match_offset+m[0].length] == @buffer[m.begin(0)..m.end(0)]
|
18
|
+
end
|
19
|
+
m
|
20
|
+
end
|
21
|
+
|
22
|
+
def advance_buffer(md)
|
23
|
+
@buffer = md.post_match
|
24
|
+
@buffer_offset += md.end(0)
|
25
|
+
end
|
26
|
+
|
27
|
+
def scan(pattern, cons=false)
|
28
|
+
raise '!'
|
29
|
+
return nil if @buffer.nil?
|
30
|
+
rv = @buffer.scan(pattern)
|
31
|
+
if cons and rv.size > 0
|
32
|
+
advance_buffer(Regexp.last_match)
|
33
|
+
end
|
34
|
+
rv
|
35
|
+
end
|
36
|
+
|
37
|
+
def consume(pattern)
|
38
|
+
md = remember_match(pattern.match(@buffer))
|
39
|
+
if md
|
40
|
+
advance_buffer(md)
|
41
|
+
@buffer
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def match(pattern, cons=false)
|
46
|
+
md = remember_match(pattern.match(@buffer))
|
47
|
+
advance_buffer(md) if cons and md
|
48
|
+
return md
|
49
|
+
end
|
50
|
+
|
51
|
+
def current_line
|
52
|
+
pos = last_match_offset || 0
|
53
|
+
[0, 0, @orig[0..pos].count("\n") + 1]
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Dryml::Parser
|
2
|
+
|
3
|
+
class Text < REXML::Text
|
4
|
+
|
5
|
+
def parent=(parent)
|
6
|
+
# Bypass immediate super
|
7
|
+
REXML::Child.instance_method(:parent=).bind(self).call(parent)
|
8
|
+
Text.check(@string, /</, nil) if @raw and @parent && Text.respond_to?(:check)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Dryml::Parser
|
2
|
+
|
3
|
+
class TreeParser < REXML::Parsers::TreeParser
|
4
|
+
def initialize( source, build_context = Document.new )
|
5
|
+
@build_context = build_context
|
6
|
+
@parser = Dryml::Parser::BaseParser.new(source)
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def parse
|
11
|
+
tag_stack = []
|
12
|
+
in_doctype = false
|
13
|
+
entities = nil
|
14
|
+
begin
|
15
|
+
while true
|
16
|
+
event = @parser.pull
|
17
|
+
#STDERR.puts "TREEPARSER GOT #{event.inspect}"
|
18
|
+
case event[0]
|
19
|
+
when :end_document
|
20
|
+
unless tag_stack.empty?
|
21
|
+
#raise ParseException.new("No close tag for #{tag_stack.inspect}")
|
22
|
+
raise ParseException.new("No close tag for #{@build_context.xpath}")
|
23
|
+
end
|
24
|
+
return
|
25
|
+
when :start_element
|
26
|
+
tag_stack.push(event[1])
|
27
|
+
el = @build_context = @build_context.add_element( event[1] )
|
28
|
+
event[2].each do |key, value|
|
29
|
+
el.attributes[key]=Dryml::Parser::Attribute.new(key,value,self)
|
30
|
+
end
|
31
|
+
@build_context.start_tag_source = event[3]
|
32
|
+
@build_context.source_offset = event[4]
|
33
|
+
when :end_element
|
34
|
+
tag_stack.pop
|
35
|
+
@build_context.has_end_tag = event[2]
|
36
|
+
@build_context = @build_context.parent
|
37
|
+
when :text
|
38
|
+
if not in_doctype
|
39
|
+
if @build_context[-1].instance_of? Text
|
40
|
+
@build_context[-1] << event[1]
|
41
|
+
else
|
42
|
+
@build_context.add(
|
43
|
+
Dryml::Parser::Text.new(event[1], @build_context.whitespace, nil, true)
|
44
|
+
) unless (
|
45
|
+
@build_context.ignore_whitespace_nodes and
|
46
|
+
event[1].strip.size==0
|
47
|
+
)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
when :comment
|
51
|
+
c = REXML::Comment.new( event[1] )
|
52
|
+
@build_context.add( c )
|
53
|
+
when :cdata
|
54
|
+
c = REXML::CData.new( event[1] )
|
55
|
+
@build_context.add( c )
|
56
|
+
when :processing_instruction
|
57
|
+
@build_context.add( Instruction.new( event[1], event[2] ) )
|
58
|
+
end
|
59
|
+
end
|
60
|
+
rescue REXML::Validation::ValidationException
|
61
|
+
raise
|
62
|
+
rescue
|
63
|
+
raise REXML::ParseException.new( $!.message, @parser.source, @parser, $! )
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/dryml/parser.rb
ADDED