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.
- data/lib/domtempl.rb +420 -0
- 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: []
|