dryml 1.1.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,3 @@
1
+ module Dryml::Parser
2
+
3
+ end