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