domtempl 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/domtempl.rb +420 -0
  2. metadata +62 -0
data/lib/domtempl.rb ADDED
@@ -0,0 +1,420 @@
1
+ require 'nokogiri'
2
+ require 'open-uri'
3
+
4
+ class DOMtempl
5
+
6
+ FRAGMENT = 0x00000001;
7
+ PRETTIFY = 0x00000002;
8
+
9
+ attr_accessor :vars
10
+
11
+ def initialize(input, flags = 0)
12
+ @vars = { }
13
+ @var_iters = { }
14
+
15
+ if (flags & FRAGMENT)
16
+ @_document = Nokogiri::XML::DocumentFragment.parse(input)
17
+ else
18
+ @_document = Nokogiri::HTML(open(input))
19
+ end
20
+
21
+ parse()
22
+ end
23
+
24
+ def error( str)
25
+ puts "Error:" + str
26
+ end
27
+
28
+ def read_var(path)
29
+
30
+ if (path[0] == '/') then path = path[1..-1] end
31
+ walk = path.split(/(\.|\/)/, -1) #, -1, PREG_SPLIT_DELIM_CAPTURE);
32
+
33
+ #puts "let's r-walk [" + path + "] {"
34
+ #puts walk
35
+ #puts " } "
36
+ #puts @vars
37
+
38
+ cpath = '/'
39
+ ptr = @vars
40
+ last = walk[ walk.length - 1 ]
41
+
42
+ (0..walk.length-2).step(2).each do |i|
43
+
44
+ step = walk[i]
45
+ mod = walk[i+1]
46
+ cpath += step
47
+
48
+ if (ptr[step].nil?)
49
+ error('undefined array "' + step + '" of path ' + path)
50
+ return nil;
51
+ end
52
+
53
+ ptr = ptr[step];
54
+
55
+ if (mod == '/')
56
+ n = @var_iters[cpath];
57
+ #puts sprintf("Iterator of `%s` is %d", cpath, n)
58
+
59
+ if (ptr[n].nil?)
60
+ error(('cant iterate through "%d"' % n) + ' of path ' + path);
61
+ return nil;
62
+ end
63
+
64
+ ptr = ptr[n];
65
+
66
+ if (last == '*' and i == walk.length - 3) then return n; end # Hack -- iterator itself
67
+ if (last == '' and i == walk.length - 3) then break; end
68
+ end
69
+
70
+ cpath += mod;
71
+ end
72
+
73
+ if (last == '')
74
+ return ptr;
75
+ end
76
+ if (ptr[last].nil?)
77
+ error('undefined variable "'+last+'" of path "' + path + '"');
78
+ return nil;
79
+ end
80
+ return ptr[ last ];
81
+ end
82
+
83
+ def write_var(path, val, no_overwrite = false)
84
+
85
+ if (path[0] == '/') then path = path[1..path.length]; end
86
+ walk = path.split(/(\.|\/)/, -1); #, -1, PREG_SPLIT_DELIM_CAPTURE);
87
+
88
+ cpath = '/';
89
+ ptr = @vars;
90
+ last = walk[ walk.length - 1 ];
91
+
92
+ #puts "Lets w-walk [ " + path + "] { "
93
+ #puts walk
94
+ #puts "}"
95
+
96
+ (0..walk.length-2).step(2).each do |i|
97
+ step = walk[i]
98
+ mod = walk[i+1]
99
+ cpath += step
100
+ if (mod == '/')
101
+ n = 0;
102
+ if (ptr[step].nil? or ptr[step] == true)
103
+ ptr[step] = [ ]
104
+ @var_iters[cpath] = 0
105
+ else
106
+ n = @var_iters[cpath]
107
+ end
108
+ ptr = ptr[step]
109
+ if (last == '' and i == walk.length - 3) then break; end
110
+ if (ptr[n].nil?)
111
+ ptr[n] = { }
112
+ end
113
+ ptr = ptr[n]
114
+ end
115
+
116
+ if (mod == '.')
117
+ if (ptr[step].nil? or ptr[step] == true)
118
+ ptr[step] = { }
119
+ end
120
+ ptr = ptr[step]
121
+ end
122
+ cpath += mod
123
+ end
124
+
125
+ if (last == '')
126
+ #print ptr
127
+ #if isinstance(ptr, dict)
128
+ # ptr[ ptr.length ] = val;
129
+ #else
130
+ ptr.push( val );
131
+ #end
132
+ return;
133
+ end
134
+
135
+ if (no_overwrite and not(ptr[last].nil?)) then return; end
136
+ # puts "Setting [" + last + "]"
137
+ # puts "in"
138
+ # puts ptr
139
+ ptr[ last ] = val;
140
+ end
141
+
142
+ def expand_path(node, base, path = nil)
143
+ if (path == nil) then path = node.get_attribute(base) end
144
+
145
+ top = node.parent
146
+ while path[0] != '/'
147
+ top_path = '';
148
+ if (!top) then path = '/' + path; break; end
149
+ if (top.type != Nokogiri::XML::Node::ELEMENT_NODE)
150
+ top = top.parent
151
+ next;
152
+ end
153
+ if (top.has_attribute?('data-from'))
154
+ top_path = top.get_attribute('data-from') + '.';
155
+ elsif (top.has_attribute?('data-each'))
156
+ top_path = top.get_attribute('data-each') + '/';
157
+ elsif (top.has_attribute?('data-same'))
158
+ top_path = top.get_attribute('data-same') + '/';
159
+ elsif (top.has_attribute?('data-when'))
160
+ top_path = top.get_attribute('data-when') + '.';
161
+ end
162
+
163
+ path = top_path + path;
164
+ top = top.parent
165
+ end
166
+ return path
167
+ end
168
+
169
+ def parse_vars_node(root)
170
+ for node in root.children
171
+
172
+ if (node.type == Nokogiri::XML::Node::ELEMENT_NODE and node.attributes())
173
+
174
+ if node.has_attribute?('data-each')
175
+ @var_iters[ expand_path(node, 'data-each') ] = 0
176
+ end
177
+
178
+ if node.has_attribute?('data-same')
179
+ @var_iters[ expand_path(node, 'data-same') ] += 1
180
+ end
181
+
182
+ #for attr in node.get_attributes:
183
+ # print attr
184
+ for i in 0..node.attributes.length-1
185
+ attr = node.attribute_nodes[i]
186
+ if (attr.name.include?('data-attr-'))
187
+
188
+ key = attr.name['data-attr'.length..-1]
189
+
190
+ write_var(
191
+ expand_path(node, '', not(attr.value) ? key : attr.value),
192
+ node.get_attribute(key).to_s
193
+ )
194
+ end
195
+ end
196
+
197
+ if node.has_attribute?('data-var')
198
+ write_var(
199
+ expand_path(node, 'data-var'),
200
+ node.inner_text()
201
+ )
202
+ end
203
+
204
+ if node.has_attribute?('data-when')
205
+ write_var(
206
+ expand_path(node, 'data-when'),
207
+ true, 1
208
+ )
209
+ end
210
+ end
211
+ if node.children
212
+ parse_vars_node(node)
213
+ end
214
+ end
215
+ end
216
+
217
+ def replace_vars_node(node, clean)
218
+ stop_here = false; #hack, for speed
219
+
220
+ if (node.attributes)
221
+
222
+ if (node.type == Nokogiri::XML::Node::ELEMENT_NODE)
223
+
224
+ j = -1
225
+ #for (j = 0; j < node.get_attributes.length; j++):
226
+ while j < node.attributes.length - 1
227
+ j += 1
228
+ attr = node.attribute_nodes[j];
229
+ if (attr.name.include?('data-attr-'))
230
+ key = attr.name['data-attr-'.length..-1]
231
+ path = expand_path(node, '', (not(attr.value) ? key : attr.value));
232
+ val = read_var(path);
233
+ if (val != false)
234
+ node.set_attribute(key, val);
235
+ end
236
+ clean.push( attr.name );
237
+ end
238
+ end
239
+
240
+ if (node.has_attribute?('data-var'))
241
+ #print "\nReplacing data-var for `"
242
+ #print node
243
+ #puts "` using the path `" + expand_path(node, 'data-var') + "`"
244
+ clean.push( 'data-var' )
245
+ node.inner_html =
246
+ read_var(expand_path(node, 'data-var'))
247
+ ;
248
+
249
+ #print "Inner html is now:" + node.toxml()
250
+ #print "\n"
251
+ stop_here = true; # do not traverse children of inserted node
252
+ end
253
+ end
254
+ end
255
+
256
+ if (node.children and not(stop_here)) # stop here if 'data-var' was used
257
+ replace_vars(node);
258
+ end
259
+ for attr in clean
260
+ node.remove_attribute(attr);
261
+ end
262
+ end
263
+
264
+ def replace_vars(root)
265
+ for i in 0..root.children.length-1
266
+ if i >= root.children.length # because range/xrange doesn't change :((
267
+ break
268
+ end
269
+
270
+ node = root.children[i];
271
+ clean = []
272
+
273
+ if (node.type == Nokogiri::XML::Node::ELEMENT_NODE and node.attributes())
274
+ if node.has_attribute?('data-when')
275
+ if not( read_var(expand_path(node, 'data-when')) )
276
+ i -= safe_remove(node);
277
+ next;
278
+ end
279
+ clean.push( 'data-when' );
280
+ end
281
+
282
+ if (node.has_attribute?('data-same'))
283
+ clean.push( 'data-same' );
284
+ next;
285
+ end
286
+ if (node.has_attribute?('data-each'))
287
+ clean.push( 'data-each' );
288
+ path = expand_path(node, 'data-each');
289
+ arr = read_var(path);
290
+
291
+ # Kill marked siblings
292
+ kill = node.next_sibling;
293
+ while (kill)
294
+ _next = kill.next_sibling;
295
+ if (kill.type == Nokogiri::XML::Node::ELEMENT_NODE and kill.has_attribute('data-same'))
296
+ safe_remove(kill);
297
+ end
298
+ kill = _next;
299
+ end
300
+
301
+ #print "Cloning time with"
302
+ #print arr
303
+
304
+ if (arr.length)
305
+ # Clone new siblings
306
+ last = nil;
307
+ (1..arr.length-1).each do |j|
308
+ #puts sprintf("Doing clone #%d, setting var iter of /%s", j, path);
309
+ @var_iters[path] = j;
310
+ nod = safe_clone(node, last);
311
+ last = nod;
312
+ nod.remove_attribute('data-each');
313
+ nod.set_attribute('data-same', path);
314
+ replace_vars_node(nod, ['data-same']);
315
+ end
316
+ @var_iters[path] = 0;
317
+ end
318
+
319
+ #print "CLONE COMPLETE"
320
+ end
321
+ replace_vars_node( node, clean );
322
+ end
323
+ end
324
+ end
325
+
326
+ def reflow()
327
+ # Reset iteration counters
328
+ if (@var_iters)
329
+ for i in @var_iters.keys
330
+ @var_iters[i] = 0;
331
+ end
332
+ end
333
+
334
+ # Reflow all variables
335
+ replace_vars(@_document);
336
+ end
337
+
338
+ def parse()
339
+ parse_vars_node(@_document)
340
+ end
341
+
342
+ def assign(path, var)
343
+ write_var(path, var)
344
+ end
345
+
346
+ def dump()
347
+ reflow()
348
+ return @_document.to_html()
349
+ end
350
+
351
+ def out()
352
+ print dump()
353
+ end
354
+
355
+ def dumpXML()
356
+ reflow()
357
+ return @_document.toxml()
358
+ end
359
+
360
+ def outXML()
361
+ print dumpXML()
362
+ end
363
+
364
+ def safe_remove(node)
365
+ ident = node.previous_sibling;
366
+ #print "and ws is "
367
+ #print ident.isWhitespaceInElementContent
368
+ r = 0;
369
+ if (ident != nil \
370
+ and ident.type == Nokogiri::XML::Node::TEXT_NODE \
371
+ and ident.inner_text.strip() == ''
372
+ #and ident.isWhitespaceInElementContent
373
+ )
374
+ ident.remove();
375
+ r = 1;
376
+ end
377
+
378
+ node.remove()
379
+ return r
380
+ end
381
+
382
+ def safe_clone(elem, after = nil)
383
+ if after == nil then after = elem; end
384
+
385
+ #print "Must clone "
386
+ #puts elem
387
+ #print " after "
388
+ #puts after
389
+
390
+ #if (elem.cloneNode)
391
+
392
+ orig = elem.previous_sibling;
393
+ ident = nil;
394
+ if (orig != nil \
395
+ and orig.type == Nokogiri::XML::Node::TEXT_NODE \
396
+ and orig.inner_text.strip() == ''
397
+ #and orig.isWhitespaceInElementContent
398
+ )
399
+ ident = orig.clone();
400
+ #print "Found ident ("
401
+ #print ident.inner_text.length
402
+ #print ident
403
+ #print ")"
404
+ #puts ""
405
+ end
406
+
407
+ cln = elem.clone();
408
+
409
+ # Note: because Nokogiri has different DOM-append methods,
410
+ # we do not need appendChild/insertBefore wraps
411
+ after.after(cln);
412
+ if (ident) then cln.before(ident); end
413
+
414
+ return cln;
415
+ #end
416
+ #return nil;
417
+ end
418
+
419
+ end
420
+
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: domtempl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - driedfruit
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-08-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Much longer explanation of the example!
31
+ email: driedfruit@mindloop.net
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - lib/domtempl.rb
37
+ homepage: https://makesite.github.io/domtempl
38
+ licenses:
39
+ - BSD
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 1.8.23
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: This is an example!
62
+ test_files: []