domtempl 0.0.1

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.
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: []