thymeleaf 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/thymeleaf/configuration.rb +45 -0
- data/lib/thymeleaf/context/context_holder.rb +34 -0
- data/lib/thymeleaf/context/context_struct.rb +50 -0
- data/lib/thymeleaf/dialects.rb +92 -0
- data/lib/thymeleaf/dialects/default/default_dialect.rb +66 -0
- data/lib/thymeleaf/dialects/default/parsers/dollar.rb +12 -0
- data/lib/thymeleaf/dialects/default/parsers/each.rb +12 -0
- data/lib/thymeleaf/dialects/default/parsers/eval.rb +15 -0
- data/lib/thymeleaf/dialects/default/parsers/fragment.rb +7 -0
- data/lib/thymeleaf/dialects/default/parsers/selection.rb +15 -0
- data/lib/thymeleaf/dialects/default/processors/block.rb +13 -0
- data/lib/thymeleaf/dialects/default/processors/case.rb +23 -0
- data/lib/thymeleaf/dialects/default/processors/default.rb +8 -0
- data/lib/thymeleaf/dialects/default/processors/each.rb +67 -0
- data/lib/thymeleaf/dialects/default/processors/fragment.rb +12 -0
- data/lib/thymeleaf/dialects/default/processors/if.rb +14 -0
- data/lib/thymeleaf/dialects/default/processors/insert.rb +65 -0
- data/lib/thymeleaf/dialects/default/processors/object.rb +21 -0
- data/lib/thymeleaf/dialects/default/processors/remove.rb +85 -0
- data/lib/thymeleaf/dialects/default/processors/replace.rb +33 -0
- data/lib/thymeleaf/dialects/default/processors/switch.rb +17 -0
- data/lib/thymeleaf/dialects/default/processors/text.rb +9 -0
- data/lib/thymeleaf/dialects/default/processors/unless.rb +13 -0
- data/lib/thymeleaf/dialects/default/processors/utext.rb +8 -0
- data/lib/thymeleaf/dialects/dialect.rb +18 -0
- data/lib/thymeleaf/parser.rb +12 -0
- data/lib/thymeleaf/parser/parse_options.rb +12 -0
- data/lib/thymeleaf/processor.rb +33 -0
- data/lib/thymeleaf/processor/context_evaluator.rb +13 -0
- data/lib/thymeleaf/template.rb +35 -0
- data/lib/thymeleaf/template/template_resolver.rb +15 -0
- data/lib/thymeleaf/template_engine.rb +56 -0
- data/lib/thymeleaf/utils/booleanize.rb +4 -0
- data/lib/thymeleaf/version.rb +3 -0
- metadata +192 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2ae44746cdafe6e28cb8f57da926e006a531e0db
|
4
|
+
data.tar.gz: 8b44f5ba0c9eb82025f21f3363fbc8ecd7f27895
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8663e50466b26f5e3f5de3b096ba355f1ff6649e9e9a7268cb66e2336cb158af1c2e1eed7d2e7d7a1130e3d94e28984e8fd0d7b67bfd5640d54ddf9211160a0f
|
7
|
+
data.tar.gz: 981a896eddf139b5050683f7a7703ad6d2e12db6adb0ddaf8f8c5f581689390cb847201501cd7ca07935e74aa091e7e440ac7e97729d3812d53b93a5d8f88498
|
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
require_relative 'dialects'
|
3
|
+
require_relative 'dialects/default/default_dialect'
|
4
|
+
require_relative 'template/template_resolver'
|
5
|
+
require_relative 'parser/parse_options'
|
6
|
+
|
7
|
+
module Thymeleaf
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_accessor :configuration
|
11
|
+
end
|
12
|
+
|
13
|
+
# TODO: Replace accessor with getter/setter?
|
14
|
+
|
15
|
+
def self.configure(&block)
|
16
|
+
self.configuration ||= Configuration.new
|
17
|
+
block.call configuration if block_given?
|
18
|
+
end
|
19
|
+
|
20
|
+
class Configuration
|
21
|
+
|
22
|
+
attr_accessor :dialects, :template, :parser
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
self.dialects = Dialects.new
|
26
|
+
self.template = TemplateResolver.new
|
27
|
+
self.parser = ParseOptions.new
|
28
|
+
add_dialect DefaultDialect
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_dialect(*args)
|
32
|
+
dialects.add_dialect(*args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def clear_dialects
|
36
|
+
dialects.clear_dialects
|
37
|
+
end
|
38
|
+
|
39
|
+
def template_uri(name)
|
40
|
+
template.get_template(name)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
require_relative 'context_struct'
|
3
|
+
|
4
|
+
class ContextHolder < Struct.new(:context, :parent_context)
|
5
|
+
|
6
|
+
def initialize(context, parent_context = nil)
|
7
|
+
if context.is_a? Hash
|
8
|
+
super(ContextStruct.new(context), parent_context)
|
9
|
+
else
|
10
|
+
super(context, parent_context)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def evaluate(expr)
|
15
|
+
instance_eval(expr.to_s)
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(m, *args)
|
19
|
+
if context.respond_to? m
|
20
|
+
context.send(m, *args)
|
21
|
+
elsif !parent_context.nil?
|
22
|
+
parent_context.send(m, *args)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def root
|
27
|
+
if parent_context.nil?
|
28
|
+
context
|
29
|
+
else
|
30
|
+
parent_context.root
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
class ContextStruct < OpenStruct
|
5
|
+
|
6
|
+
def initialize(hash=nil)
|
7
|
+
@table = {}
|
8
|
+
@hash_table = {}
|
9
|
+
|
10
|
+
if hash && (hash.is_a? Hash)
|
11
|
+
hash.each do |k,v|
|
12
|
+
|
13
|
+
if v.is_a?(Array)
|
14
|
+
other = Array.new
|
15
|
+
v.each do |entry|
|
16
|
+
other.push((entry.is_a?(Hash) ? self.class.new(entry) : entry))
|
17
|
+
end
|
18
|
+
v = other
|
19
|
+
end
|
20
|
+
|
21
|
+
@table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v)
|
22
|
+
@hash_table[k.to_sym] = v
|
23
|
+
new_ostruct_member(k)
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_private(private_var, value)
|
30
|
+
value = ContextStruct.new(value) if value.is_a? Hash
|
31
|
+
send(:"\##{private_var}=", value)
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_private(private_var)
|
35
|
+
send(:"\##{private_var}")
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_private(private_var)
|
39
|
+
begin
|
40
|
+
!(get_private private_var).nil?
|
41
|
+
rescue
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_h
|
47
|
+
@hash_table
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
|
2
|
+
module Thymeleaf
|
3
|
+
|
4
|
+
class Dialects
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
clear_dialects
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_dialect(*args)
|
11
|
+
key, dialect_class = * expand_key_dialect(*args)
|
12
|
+
|
13
|
+
dialect = dialect_class.new
|
14
|
+
|
15
|
+
registered_dialects[key] = dialect
|
16
|
+
registered_attr_processors[key] = dialect_processors(dialect)
|
17
|
+
registered_tag_processors[key] = dialect_tag_processors(dialect)
|
18
|
+
end
|
19
|
+
|
20
|
+
def clear_dialects
|
21
|
+
self.registered_dialects = {}
|
22
|
+
self.registered_attr_processors = {}
|
23
|
+
self.registered_tag_processors = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_attr_processor(key)
|
27
|
+
find_processor key, dialect_attr_matchers, registered_attr_processors
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_tag_processor(key)
|
31
|
+
find_processor key, dialect_tag_matchers, registered_tag_processors
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_accessor :registered_dialects, :registered_attr_processors, :registered_tag_processors
|
37
|
+
|
38
|
+
def dialect_attr_matchers
|
39
|
+
/^data-(#{registered_dialects.keys.join("|")})-(.*)$/
|
40
|
+
end
|
41
|
+
|
42
|
+
def dialect_tag_matchers
|
43
|
+
/^(#{registered_dialects.keys.join("|")})-(.*)$/
|
44
|
+
end
|
45
|
+
|
46
|
+
def null_processor
|
47
|
+
@null_prccesor ||= NullProcessor.new
|
48
|
+
end
|
49
|
+
|
50
|
+
def expand_key_dialect(*args)
|
51
|
+
if args.length == 1
|
52
|
+
[ args[0].default_key, args[0] ]
|
53
|
+
elsif args.length == 2
|
54
|
+
args
|
55
|
+
else
|
56
|
+
raise ArgumentError
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def dialect_processors(dialect)
|
61
|
+
dialect.processors.reduce({}) do |processors, (processor_key, processor)|
|
62
|
+
processors[processor_key.to_s] = processor.new
|
63
|
+
processors
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def dialect_tag_processors(dialect)
|
68
|
+
dialect.tag_processors.reduce({}) do |processors, (processor_key, processor)|
|
69
|
+
processors[processor_key.to_s] = processor.new
|
70
|
+
processors
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def find_processor(key, dialect_matchers, processor_list)
|
75
|
+
match = dialect_matchers.match(key)
|
76
|
+
|
77
|
+
# TODO: check performance null object vs null check
|
78
|
+
return [key, null_processor] if match.nil?
|
79
|
+
|
80
|
+
dialect_key, processor_key = *match[1..2]
|
81
|
+
|
82
|
+
dialect_processors = processor_list[dialect_key]
|
83
|
+
raise ArgumentError, "No dialect found for key #{key}" if dialect_processors.nil?
|
84
|
+
|
85
|
+
processor = dialect_processors[processor_key] || dialect_processors['default']
|
86
|
+
raise ArgumentError, "No processor found for key #{key}" if processor.nil?
|
87
|
+
|
88
|
+
[processor_key, processor]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
|
2
|
+
require_relative '../../processor'
|
3
|
+
require_relative '../../template_engine'
|
4
|
+
|
5
|
+
require_relative '../dialect'
|
6
|
+
|
7
|
+
class DefaultDialect < Dialect
|
8
|
+
|
9
|
+
CONTEXT_SWITCH_VAR = 'switch_var'
|
10
|
+
CONTEXT_FRAGMENT_VAR = 'fragment_var'
|
11
|
+
CONTEXT_OBJECT_VAR = 'context_obj'
|
12
|
+
|
13
|
+
def self.default_key
|
14
|
+
'th'
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.context_fragment_var(var_name)
|
18
|
+
"#{CONTEXT_FRAGMENT_VAR}_#{var_name}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def tag_processors
|
22
|
+
{
|
23
|
+
block: BlockProcessor
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
# Precedence based on order for the time being
|
28
|
+
def processors
|
29
|
+
{
|
30
|
+
insert: InsertProcessor,
|
31
|
+
replace: ReplaceProcessor,
|
32
|
+
fragment: FragmentProcessor,
|
33
|
+
each: EachProcessor,
|
34
|
+
if: IfProcessor,
|
35
|
+
unless: UnlessProcessor,
|
36
|
+
switch: SwitchProcessor,
|
37
|
+
case: CaseProcessor,
|
38
|
+
object: ObjectProcessor,
|
39
|
+
text: TextProcessor,
|
40
|
+
utext: UTextProcessor,
|
41
|
+
remove: RemoveProcessor,
|
42
|
+
default: DefaultProcessor
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
require_relative 'parsers/eval'
|
47
|
+
require_relative 'parsers/selection'
|
48
|
+
require_relative 'parsers/each'
|
49
|
+
require_relative 'parsers/fragment'
|
50
|
+
|
51
|
+
require_relative 'processors/default'
|
52
|
+
require_relative 'processors/object'
|
53
|
+
require_relative 'processors/text'
|
54
|
+
require_relative 'processors/utext'
|
55
|
+
require_relative 'processors/if'
|
56
|
+
require_relative 'processors/unless'
|
57
|
+
require_relative 'processors/switch'
|
58
|
+
require_relative 'processors/case'
|
59
|
+
require_relative 'processors/each'
|
60
|
+
require_relative 'processors/remove'
|
61
|
+
require_relative 'processors/insert'
|
62
|
+
require_relative 'processors/replace'
|
63
|
+
require_relative 'processors/block'
|
64
|
+
require_relative 'processors/fragment'
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Matches:
|
2
|
+
# "item, stat : ${iterator}",
|
3
|
+
# "item : ${iterator}" or
|
4
|
+
# "${iterator}"
|
5
|
+
class EachExpression
|
6
|
+
|
7
|
+
def self.parse(context, expr, **args)
|
8
|
+
md = expr.match(/\s*(?:([^\n,]+?)\s*(?:,\s*([^\n,]*?))?\s*:\s*)?\${(.+?)}/)
|
9
|
+
raise ArgumentError, "Not a valid each expression" if md.nil?
|
10
|
+
md[1..3]
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
require_relative 'selection'
|
3
|
+
require_relative 'dollar'
|
4
|
+
|
5
|
+
class EvalExpression
|
6
|
+
|
7
|
+
def self.parse(context, expr, mode = nil, **_)
|
8
|
+
text = SelectionExpression.parse(context, expr, context.get_private(DefaultDialect::CONTEXT_OBJECT_VAR))
|
9
|
+
DollarExpression.parse(context, text, mode)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.parse_single_expression(context, expr, **_)
|
13
|
+
self.parse(context, expr, :single_expression)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
class SelectionExpression
|
3
|
+
|
4
|
+
# Parse asterisk *{...} syntax (object selection)
|
5
|
+
def self.parse(context, expr, obj_var, **args)
|
6
|
+
expr.gsub(/(\*{.+?})/) do |match|
|
7
|
+
if obj_var.nil?
|
8
|
+
"${#{match[2..-2]}}"
|
9
|
+
else
|
10
|
+
ContextEvaluator.new(ContextHolder.new obj_var).evaluate(match[2..-2])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
class CaseProcessor
|
3
|
+
include Thymeleaf::Processor
|
4
|
+
|
5
|
+
def call(node:nil, attribute:nil, context:nil, **_)
|
6
|
+
attribute.unlink
|
7
|
+
|
8
|
+
var_cmp = EvalExpression.parse(context, attribute.value)
|
9
|
+
|
10
|
+
unless case_equals? context, var_cmp
|
11
|
+
node.children.each { |child| child.unlink }
|
12
|
+
node.unlink
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
def case_equals?(context, var_comparation)
|
18
|
+
(context.has_private DefaultDialect::CONTEXT_SWITCH_VAR) &&
|
19
|
+
(context.get_private DefaultDialect::CONTEXT_SWITCH_VAR).eql?(var_comparation)
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class EachProcessor
|
2
|
+
|
3
|
+
include Thymeleaf::Processor
|
4
|
+
require_relative '../parsers/each'
|
5
|
+
|
6
|
+
def call(node:nil, attribute:nil, context:nil, **_)
|
7
|
+
variable, stat, enumerable = EachExpression.parse(context, attribute.value)
|
8
|
+
|
9
|
+
elements = evaluate_in_context(context, enumerable)
|
10
|
+
|
11
|
+
stat_var = init_stat_var(stat, elements)
|
12
|
+
|
13
|
+
|
14
|
+
attribute.unlink
|
15
|
+
|
16
|
+
elements.each do |element|
|
17
|
+
subcontext_vars = {}
|
18
|
+
subcontext_vars[variable] = element unless variable.nil?
|
19
|
+
|
20
|
+
unless stat.nil?
|
21
|
+
stat_var[:index] += 1
|
22
|
+
stat_var[:count] += 1
|
23
|
+
stat_var[:current] = element
|
24
|
+
stat_var[:even] = stat_var[:count].even?
|
25
|
+
stat_var[:odd] = stat_var[:count].odd?
|
26
|
+
stat_var[:first] = (stat_var[:index].eql? 0)
|
27
|
+
stat_var[:last] = (stat_var[:count].eql? stat_var[:size])
|
28
|
+
|
29
|
+
subcontext_vars[stat] = stat_var
|
30
|
+
end
|
31
|
+
|
32
|
+
subcontext = ContextHolder.new(subcontext_vars, context)
|
33
|
+
new_node = node.dup
|
34
|
+
subprocess_node(subcontext, new_node)
|
35
|
+
node.add_previous_sibling(new_node)
|
36
|
+
end
|
37
|
+
|
38
|
+
node.children.each {|child| child.unlink }
|
39
|
+
node.unlink
|
40
|
+
|
41
|
+
context # TODO: Remove
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_subcontext?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def init_stat_var(stat, elements)
|
51
|
+
if stat.nil?
|
52
|
+
nil
|
53
|
+
else
|
54
|
+
{
|
55
|
+
:index => -1,
|
56
|
+
:count => 0,
|
57
|
+
:size => elements.length,
|
58
|
+
:current => nil,
|
59
|
+
:even => false,
|
60
|
+
:odd => true,
|
61
|
+
:first => true,
|
62
|
+
:last => false
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class FragmentProcessor
|
2
|
+
include Thymeleaf::Processor
|
3
|
+
|
4
|
+
def call(node:nil, attribute:nil, context:nil, **_)
|
5
|
+
fragment_name = EvalExpression.parse(context, attribute.value)
|
6
|
+
|
7
|
+
context.root.set_private DefaultDialect::context_fragment_var(fragment_name), node
|
8
|
+
|
9
|
+
attribute.unlink
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative '../../../utils/booleanize'
|
2
|
+
|
3
|
+
class IfProcessor
|
4
|
+
|
5
|
+
include Thymeleaf::Processor
|
6
|
+
|
7
|
+
def call(node:nil, attribute:nil, context:nil, **_)
|
8
|
+
attribute.unlink
|
9
|
+
unless booleanize EvalExpression.parse(context, attribute.value)
|
10
|
+
node.children.each {|child| child.unlink }
|
11
|
+
node.unlink
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
|
2
|
+
class InsertProcessor
|
3
|
+
|
4
|
+
include Thymeleaf::Processor
|
5
|
+
|
6
|
+
require_relative '../parsers/fragment'
|
7
|
+
|
8
|
+
def call(node:nil, attribute:nil, context:nil, **_)
|
9
|
+
attribute.unlink
|
10
|
+
|
11
|
+
template, fragment = FragmentExpression.parse(context, attribute.value)
|
12
|
+
|
13
|
+
node_subcontent = get_node_template(template, node, context)
|
14
|
+
|
15
|
+
node.children.each {|child| child.unlink }
|
16
|
+
|
17
|
+
if fragment.nil?
|
18
|
+
# Avoid infinite loop when template is "this" and fragment is nil
|
19
|
+
return nil if is_self_template? template
|
20
|
+
else
|
21
|
+
node_subcontent = get_fragment_node(fragment, context, node_subcontent)
|
22
|
+
end
|
23
|
+
|
24
|
+
unless node_subcontent.nil?
|
25
|
+
node_subcontent.dup.parent = node
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def get_node_template(template, node, context)
|
33
|
+
if is_self_template? template
|
34
|
+
root_node node
|
35
|
+
else
|
36
|
+
subtemplate = EvalExpression.parse(context, template)
|
37
|
+
|
38
|
+
load_template subtemplate do |template_file|
|
39
|
+
Thymeleaf::Parser.new(template_file).call
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def root_node(node)
|
45
|
+
new_node = node
|
46
|
+
until new_node.parent.nil?
|
47
|
+
new_node = new_node.parent
|
48
|
+
end
|
49
|
+
new_node
|
50
|
+
end
|
51
|
+
|
52
|
+
def is_self_template?(template)
|
53
|
+
template.nil? || (template.eql? 'this')
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_fragment_node(fragment_name, context, node)
|
57
|
+
root_context = context.root
|
58
|
+
|
59
|
+
if root_context.has_private DefaultDialect::context_fragment_var(fragment_name)
|
60
|
+
root_context.get_private DefaultDialect::context_fragment_var(fragment_name)
|
61
|
+
else
|
62
|
+
node.at_css fragment_name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative '../../../utils/booleanize'
|
2
|
+
|
3
|
+
class ObjectProcessor
|
4
|
+
|
5
|
+
include Thymeleaf::Processor
|
6
|
+
|
7
|
+
def call(attribute:nil, context:nil, **_)
|
8
|
+
attribute.unlink
|
9
|
+
|
10
|
+
obj_var = EvalExpression.parse_single_expression(context, attribute.value)
|
11
|
+
new_context = ContextHolder.new({}, context)
|
12
|
+
new_context.set_private(DefaultDialect::CONTEXT_OBJECT_VAR, obj_var)
|
13
|
+
|
14
|
+
attribute.unlink
|
15
|
+
new_context
|
16
|
+
end
|
17
|
+
|
18
|
+
def has_subcontext?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require_relative '../../../utils/booleanize'
|
2
|
+
|
3
|
+
class RemoveProcessor
|
4
|
+
|
5
|
+
include Thymeleaf::Processor
|
6
|
+
|
7
|
+
REMOVE_ALL = 'all'
|
8
|
+
REMOVE_BODY = 'body'
|
9
|
+
REMOVE_TAG = 'tag'
|
10
|
+
REMOVE_ALL_BUT_FIRST = 'all-but-first'
|
11
|
+
REMOVE_NONE = 'none'
|
12
|
+
|
13
|
+
def call(node:nil, attribute:nil, context:nil, **_)
|
14
|
+
attribute.unlink
|
15
|
+
|
16
|
+
expr = EvalExpression.parse(context, attribute.value)
|
17
|
+
|
18
|
+
method = case expr
|
19
|
+
when REMOVE_ALL
|
20
|
+
:remove_all
|
21
|
+
when REMOVE_BODY
|
22
|
+
:remove_body
|
23
|
+
when REMOVE_TAG
|
24
|
+
:remove_tag
|
25
|
+
when REMOVE_ALL_BUT_FIRST
|
26
|
+
:remove_allbutfirst
|
27
|
+
when REMOVE_NONE
|
28
|
+
:remove_none
|
29
|
+
else
|
30
|
+
if booleanize expr
|
31
|
+
:remove_all
|
32
|
+
else
|
33
|
+
:remove_none
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
send(method, node, context)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def remove_all(node, _)
|
42
|
+
node.children.each do |child|
|
43
|
+
child.unlink
|
44
|
+
end
|
45
|
+
node.unlink
|
46
|
+
end
|
47
|
+
|
48
|
+
def remove_body(node, _)
|
49
|
+
node.children.each do |child|
|
50
|
+
child.unlink
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def remove_tag(node, context)
|
55
|
+
node.children.reverse.each do |child|
|
56
|
+
subprocess_node(context, child)
|
57
|
+
node.add_next_sibling child
|
58
|
+
end
|
59
|
+
node.unlink
|
60
|
+
end
|
61
|
+
|
62
|
+
def remove_allbutfirst(node, _)
|
63
|
+
skip_first(node.children) do |child|
|
64
|
+
child.unlink
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def remove_none(_, _)
|
69
|
+
end
|
70
|
+
|
71
|
+
def empty_node?(node)
|
72
|
+
node.to_s.strip.empty?
|
73
|
+
end
|
74
|
+
|
75
|
+
def skip_first(node_set)
|
76
|
+
i = 0
|
77
|
+
node_set.each do |child|
|
78
|
+
if i > 0
|
79
|
+
yield child
|
80
|
+
else
|
81
|
+
i += 1 unless empty_node? child
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
require_relative 'insert'
|
3
|
+
|
4
|
+
class ReplaceProcessor < InsertProcessor
|
5
|
+
|
6
|
+
include Thymeleaf::Processor
|
7
|
+
|
8
|
+
def call(node:nil, attribute:nil, context:nil, **_)
|
9
|
+
|
10
|
+
attribute.unlink
|
11
|
+
|
12
|
+
template, fragment = FragmentExpression.parse(context, attribute.value)
|
13
|
+
|
14
|
+
node_subcontent = get_node_template(template, node, context)
|
15
|
+
|
16
|
+
node.children.each {|child| child.unlink }
|
17
|
+
|
18
|
+
if fragment.nil?
|
19
|
+
# Avoid infinite loop when template is "this" and fragment is nil
|
20
|
+
return nil if is_self_template? template
|
21
|
+
else
|
22
|
+
node_subcontent = get_fragment_node(fragment, context, node_subcontent)
|
23
|
+
end
|
24
|
+
|
25
|
+
unless node_subcontent.nil?
|
26
|
+
node_subcontent = node_subcontent.dup
|
27
|
+
subprocess_node(context, node_subcontent)
|
28
|
+
|
29
|
+
node.replace node_subcontent
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class SwitchProcessor
|
2
|
+
include Thymeleaf::Processor
|
3
|
+
|
4
|
+
def call(attribute:nil, context:nil, **_)
|
5
|
+
condition = EvalExpression.parse(context, attribute.value)
|
6
|
+
new_context = ContextHolder.new({}, context)
|
7
|
+
new_context.set_private DefaultDialect::CONTEXT_SWITCH_VAR, condition
|
8
|
+
|
9
|
+
attribute.unlink
|
10
|
+
new_context
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_subcontext?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative '../../../utils/booleanize'
|
2
|
+
|
3
|
+
class UnlessProcessor
|
4
|
+
include Thymeleaf::Processor
|
5
|
+
|
6
|
+
def call(node:nil, attribute:nil, context:nil, **_)
|
7
|
+
attribute.unlink
|
8
|
+
if booleanize EvalExpression.parse(context, attribute.value)
|
9
|
+
node.children.each {|child| child.unlink }
|
10
|
+
node.unlink
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
module Thymeleaf
|
3
|
+
|
4
|
+
module Processor
|
5
|
+
|
6
|
+
require_relative 'processor/context_evaluator'
|
7
|
+
|
8
|
+
def evaluate_in_context(context, expr)
|
9
|
+
ContextEvaluator.new(context).evaluate(expr)
|
10
|
+
end
|
11
|
+
|
12
|
+
def subprocess_node(context, node)
|
13
|
+
processor = Thymeleaf::TemplateEngine.new
|
14
|
+
processor.send(:process_node, context, node)
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_template(template_name)
|
18
|
+
template_uri = Thymeleaf.configuration.template_uri(template_name)
|
19
|
+
|
20
|
+
File.open template_uri do |template_file|
|
21
|
+
template_file.rewind
|
22
|
+
yield template_file.read
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
class NullProcessor
|
29
|
+
def call(**_)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
require_relative 'processor'
|
3
|
+
|
4
|
+
module Thymeleaf
|
5
|
+
|
6
|
+
require_relative 'context/context_holder'
|
7
|
+
|
8
|
+
class Template < Struct.new(:template_markup, :context)
|
9
|
+
def render
|
10
|
+
do_render template_markup
|
11
|
+
end
|
12
|
+
|
13
|
+
def render_file
|
14
|
+
template_markup_uri = Thymeleaf.configuration.template_uri(template_markup)
|
15
|
+
|
16
|
+
template_file_open template_markup_uri do |template|
|
17
|
+
do_render template
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def do_render(template)
|
23
|
+
parsed_template = Parser.new(template).call
|
24
|
+
context_holder = ContextHolder.new(context)
|
25
|
+
TemplateEngine.new.call(parsed_template, context_holder).to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def template_file_open(template_file)
|
29
|
+
File.open template_file do |template|
|
30
|
+
template.rewind
|
31
|
+
yield template.read
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
module Thymeleaf
|
3
|
+
|
4
|
+
class TemplateEngine
|
5
|
+
def call(parsed_template, context_holder)
|
6
|
+
process_node(context_holder, parsed_template)
|
7
|
+
parsed_template
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def process_node(context_holder, node)
|
12
|
+
attr_context = process_attributes(context_holder, node)
|
13
|
+
children_context = process_tag(attr_context, node)
|
14
|
+
|
15
|
+
# TODO: Processing all nodes. Maybe filtering by only-thymeleaf nodes can give better performance?
|
16
|
+
node.children.each {|child| process_node(children_context, child)}
|
17
|
+
end
|
18
|
+
|
19
|
+
def process_attributes(context_holder, node)
|
20
|
+
attr_context = context_holder
|
21
|
+
node.attributes.each do |attribute_key, attribute|
|
22
|
+
attr_context = process_attribute(attr_context, node, attribute_key, attribute)
|
23
|
+
end
|
24
|
+
attr_context
|
25
|
+
end
|
26
|
+
|
27
|
+
def process_attribute(context_holder, node, attribute_key, attribute)
|
28
|
+
# TODO: Find all proccessors. Apply in precedence order!
|
29
|
+
dialects = Thymeleaf.configuration.dialects
|
30
|
+
key, processor = * dialects.find_attr_processor(attribute_key)
|
31
|
+
|
32
|
+
process_element(context_holder, node, attribute, key, processor)
|
33
|
+
end
|
34
|
+
|
35
|
+
def process_tag(context_holder, node)
|
36
|
+
dialects = Thymeleaf.configuration.dialects
|
37
|
+
key, processor = * dialects.find_tag_processor(node.name)
|
38
|
+
|
39
|
+
process_element(context_holder, node, nil, key, processor)
|
40
|
+
end
|
41
|
+
|
42
|
+
def processor_has_subcontext?(processor)
|
43
|
+
processor.respond_to?(:has_subcontext?) && processor.has_subcontext?
|
44
|
+
end
|
45
|
+
|
46
|
+
def process_element(context_holder, node, attribute, key, processor)
|
47
|
+
subcontext = processor.call(key: key, node: node, attribute: attribute, context: context_holder)
|
48
|
+
|
49
|
+
if processor_has_subcontext?(processor)
|
50
|
+
subcontext
|
51
|
+
else
|
52
|
+
context_holder
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thymeleaf
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Barral Precedo
|
8
|
+
- Daniel Vazquez Brañas
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-09-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: nokogiri
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: bundler
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '1.10'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '1.10'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rake
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '10.0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '10.0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: minitest
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: awesome_print
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: benchmark-ips
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '2.6'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '2.6'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: webrick
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: json
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
description: Thymeleaf template engine for Ruby
|
127
|
+
email:
|
128
|
+
- contact@trabesoluciones.com
|
129
|
+
- david.barral@trabesoluciones.com
|
130
|
+
executables: []
|
131
|
+
extensions: []
|
132
|
+
extra_rdoc_files: []
|
133
|
+
files:
|
134
|
+
- lib/thymeleaf/configuration.rb
|
135
|
+
- lib/thymeleaf/context/context_holder.rb
|
136
|
+
- lib/thymeleaf/context/context_struct.rb
|
137
|
+
- lib/thymeleaf/dialects.rb
|
138
|
+
- lib/thymeleaf/dialects/default/default_dialect.rb
|
139
|
+
- lib/thymeleaf/dialects/default/parsers/dollar.rb
|
140
|
+
- lib/thymeleaf/dialects/default/parsers/each.rb
|
141
|
+
- lib/thymeleaf/dialects/default/parsers/eval.rb
|
142
|
+
- lib/thymeleaf/dialects/default/parsers/fragment.rb
|
143
|
+
- lib/thymeleaf/dialects/default/parsers/selection.rb
|
144
|
+
- lib/thymeleaf/dialects/default/processors/block.rb
|
145
|
+
- lib/thymeleaf/dialects/default/processors/case.rb
|
146
|
+
- lib/thymeleaf/dialects/default/processors/default.rb
|
147
|
+
- lib/thymeleaf/dialects/default/processors/each.rb
|
148
|
+
- lib/thymeleaf/dialects/default/processors/fragment.rb
|
149
|
+
- lib/thymeleaf/dialects/default/processors/if.rb
|
150
|
+
- lib/thymeleaf/dialects/default/processors/insert.rb
|
151
|
+
- lib/thymeleaf/dialects/default/processors/object.rb
|
152
|
+
- lib/thymeleaf/dialects/default/processors/remove.rb
|
153
|
+
- lib/thymeleaf/dialects/default/processors/replace.rb
|
154
|
+
- lib/thymeleaf/dialects/default/processors/switch.rb
|
155
|
+
- lib/thymeleaf/dialects/default/processors/text.rb
|
156
|
+
- lib/thymeleaf/dialects/default/processors/unless.rb
|
157
|
+
- lib/thymeleaf/dialects/default/processors/utext.rb
|
158
|
+
- lib/thymeleaf/dialects/dialect.rb
|
159
|
+
- lib/thymeleaf/parser.rb
|
160
|
+
- lib/thymeleaf/parser/parse_options.rb
|
161
|
+
- lib/thymeleaf/processor.rb
|
162
|
+
- lib/thymeleaf/processor/context_evaluator.rb
|
163
|
+
- lib/thymeleaf/template.rb
|
164
|
+
- lib/thymeleaf/template/template_resolver.rb
|
165
|
+
- lib/thymeleaf/template_engine.rb
|
166
|
+
- lib/thymeleaf/utils/booleanize.rb
|
167
|
+
- lib/thymeleaf/version.rb
|
168
|
+
homepage: https://trabe.github.io/thymeleaf-rb
|
169
|
+
licenses:
|
170
|
+
- MIT
|
171
|
+
metadata: {}
|
172
|
+
post_install_message:
|
173
|
+
rdoc_options: []
|
174
|
+
require_paths:
|
175
|
+
- lib
|
176
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 2.3.0
|
181
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
182
|
+
requirements:
|
183
|
+
- - ">="
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: '0'
|
186
|
+
requirements: []
|
187
|
+
rubyforge_project:
|
188
|
+
rubygems_version: 2.4.5.1
|
189
|
+
signing_key:
|
190
|
+
specification_version: 4
|
191
|
+
summary: Thymeleaf for Ruby
|
192
|
+
test_files: []
|