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.
@@ -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