dryml 1.1.0.pre0
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.
- 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