mullet 0.0.0 → 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/mullet/container.rb +8 -4
- data/lib/mullet/default_model.rb +9 -9
- data/lib/mullet/default_nested_model.rb +7 -6
- data/lib/mullet/html/attribute_command.rb +8 -4
- data/lib/mullet/html/attributes.rb +22 -0
- data/lib/mullet/html/command.rb +4 -3
- data/lib/mullet/html/command_element_renderer.rb +19 -0
- data/lib/mullet/html/element.rb +41 -0
- data/lib/mullet/html/element_renderer.rb +261 -0
- data/lib/mullet/html/filtered_element_handler.rb +87 -0
- data/lib/mullet/html/for_element_renderer.rb +47 -0
- data/lib/mullet/html/if_element_renderer.rb +46 -0
- data/lib/mullet/html/layout.rb +48 -0
- data/lib/mullet/html/message.rb +55 -0
- data/lib/mullet/html/message_attribute_command.rb +30 -0
- data/lib/mullet/html/model_attribute_command.rb +30 -0
- data/lib/mullet/html/page_builder.rb +152 -0
- data/lib/mullet/html/parser/attribute.rb +8 -0
- data/lib/mullet/html/parser/constants.rb +1061 -0
- data/lib/mullet/html/parser/default_handler.rb +27 -0
- data/lib/mullet/html/parser/input_stream.rb +711 -0
- data/lib/mullet/html/parser/open_element.rb +77 -0
- data/lib/mullet/html/parser/simple_parser.rb +128 -0
- data/lib/mullet/html/parser/tokenizer.rb +1085 -0
- data/lib/mullet/html/remove_mode.rb +30 -0
- data/lib/mullet/html/static_text_renderer.rb +20 -0
- data/lib/mullet/html/template.rb +44 -0
- data/lib/mullet/html/template_builder.rb +208 -63
- data/lib/mullet/html/template_loader.rb +77 -39
- data/lib/mullet/html/template_parser.rb +48 -0
- data/lib/mullet/html/unless_element_renderer.rb +24 -0
- data/lib/mullet/model.rb +2 -5
- data/lib/mullet/render_context.rb +24 -18
- data/lib/mullet/tilt.rb +37 -0
- data/lib/mullet/version.rb +2 -1
- data/lib/mullet.rb +1 -0
- metadata +58 -11
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'mullet/html/page_builder'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module Mullet; module HTML
|
5
|
+
|
6
|
+
# Extracts content from an HTML page and renders it in a layout. The layout
|
7
|
+
# is a template given these variables:
|
8
|
+
#
|
9
|
+
# `title`
|
10
|
+
# : content of the `title` element from the page
|
11
|
+
# `body`
|
12
|
+
# : content of the `body` element from the page
|
13
|
+
#
|
14
|
+
# The `body` variable typically contains HTML markup, so the layout must
|
15
|
+
# use the `data-escape-xml="false"` command to prevent markup characters
|
16
|
+
# being escaped when rendering the variable.
|
17
|
+
class Layout
|
18
|
+
|
19
|
+
# Constructor
|
20
|
+
#
|
21
|
+
# @param [Template] template
|
22
|
+
# layout using the template
|
23
|
+
def initialize(template)
|
24
|
+
@template = template
|
25
|
+
end
|
26
|
+
|
27
|
+
# Renders page data in a layout.
|
28
|
+
#
|
29
|
+
# @param [String] page_html
|
30
|
+
# content from this HTML page will be rendered in the layout
|
31
|
+
# @param [#<<] output
|
32
|
+
# where to write rendered output
|
33
|
+
def execute(page_html, output)
|
34
|
+
page = parse_page(page_html)
|
35
|
+
@template.execute(page, output)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def parse_page(page_html)
|
41
|
+
handler = PageBuilder.new()
|
42
|
+
parser = Nokogiri::HTML::SAX::Parser.new(handler)
|
43
|
+
parser.parse(page_html)
|
44
|
+
return handler.page
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end; end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
require 'mullet/html/attribute_command'
|
3
|
+
|
4
|
+
module Mullet; module HTML
|
5
|
+
|
6
|
+
# Holds the resource key and variable names that need to be resolved to
|
7
|
+
# format a localized message.
|
8
|
+
class Message
|
9
|
+
ARGUMENT_SEPARATOR = ','
|
10
|
+
|
11
|
+
# Constructor
|
12
|
+
#
|
13
|
+
# @param [String] message_arguments
|
14
|
+
# message key and arguments string
|
15
|
+
def initialize(message_arguments)
|
16
|
+
arguments = message_arguments.split(ARGUMENT_SEPARATOR)
|
17
|
+
if arguments.empty?()
|
18
|
+
raise TemplateException.new(
|
19
|
+
"incorrect syntax in message #{message_arguments}")
|
20
|
+
end
|
21
|
+
|
22
|
+
@message_key = arguments.shift().strip()
|
23
|
+
if @message_key.empty?()
|
24
|
+
raise TemplateException.new(
|
25
|
+
"empty message key in message #{message_arguments}")
|
26
|
+
end
|
27
|
+
|
28
|
+
@argument_keys = []
|
29
|
+
arguments.each do |argument_key|
|
30
|
+
argument_key = argument_key.strip()
|
31
|
+
if argument_key.empty?()
|
32
|
+
raise TemplateException.new(
|
33
|
+
"empty argument key in message #{message_arguments}")
|
34
|
+
end
|
35
|
+
@argument_keys << argument_key.to_sym()
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Formats localized message.
|
40
|
+
#
|
41
|
+
# @param [RenderContext] render_context
|
42
|
+
# render context
|
43
|
+
# @return localized message
|
44
|
+
def translate(render_context)
|
45
|
+
arguments = Hash.new()
|
46
|
+
@argument_keys.each do |argument_key|
|
47
|
+
arguments.store(
|
48
|
+
argument_key, render_context.get_display_value(argument_key))
|
49
|
+
end
|
50
|
+
|
51
|
+
return I18n.translate(@message_key, arguments)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end; end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'mullet/html/attribute_command'
|
2
|
+
|
3
|
+
module Mullet; module HTML
|
4
|
+
|
5
|
+
# Operation to set attribute value from translation.
|
6
|
+
class MessageAttributeCommand
|
7
|
+
include AttributeCommand
|
8
|
+
|
9
|
+
# Constructor
|
10
|
+
#
|
11
|
+
# @param [Symbol] attribute_name
|
12
|
+
# name of attribute this command sets
|
13
|
+
# @param [Message] message
|
14
|
+
# message to format to get attribute value
|
15
|
+
def initialize(attribute_name, message)
|
16
|
+
super(attribute_name)
|
17
|
+
@message = message
|
18
|
+
end
|
19
|
+
|
20
|
+
# Gets attribute value by formatting localized message.
|
21
|
+
#
|
22
|
+
# @param [RenderContext] render_context
|
23
|
+
# render context
|
24
|
+
# @return attribute value
|
25
|
+
def get_value(render_context)
|
26
|
+
return @message.translate(render_context)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end; end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'mullet/html/attribute_command'
|
2
|
+
|
3
|
+
module Mullet; module HTML
|
4
|
+
|
5
|
+
# Operation to set attribute value from model.
|
6
|
+
class ModelAttributeCommand
|
7
|
+
include AttributeCommand
|
8
|
+
|
9
|
+
# Constructor
|
10
|
+
#
|
11
|
+
# @param [Symbol] attribute_name
|
12
|
+
# name of attribute this command sets
|
13
|
+
# @param [Symbole] variable_name
|
14
|
+
# variable name to lookup to get value
|
15
|
+
def initialize(attribute_name, variable_name)
|
16
|
+
super(attribute_name)
|
17
|
+
@variable_name = variable_name
|
18
|
+
end
|
19
|
+
|
20
|
+
# Gets attribute value by looking up variable name.
|
21
|
+
#
|
22
|
+
# @param [RenderContext] render_context
|
23
|
+
# render context
|
24
|
+
# @return attribute value
|
25
|
+
def get_value(render_context)
|
26
|
+
return render_context.get_variable_value(@variable_name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end; end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'mullet/html/attributes'
|
2
|
+
require 'mullet/html/command'
|
3
|
+
require 'mullet/html/element'
|
4
|
+
require 'mullet/html/element_renderer'
|
5
|
+
require 'mullet/html/for_element_renderer'
|
6
|
+
require 'mullet/html/if_element_renderer'
|
7
|
+
require 'mullet/html/parser/default_handler'
|
8
|
+
require 'mullet/html/parser/simple_parser'
|
9
|
+
require 'mullet/html/remove_mode'
|
10
|
+
require 'mullet/html/static_text_renderer'
|
11
|
+
require 'mullet/html/template'
|
12
|
+
require 'mullet/html/unless_element_renderer'
|
13
|
+
require 'mullet/template_error'
|
14
|
+
require 'set'
|
15
|
+
|
16
|
+
module Mullet; module HTML
|
17
|
+
|
18
|
+
# Handles SAX events to extract content from an HTML page.
|
19
|
+
class PageBuilder < Parser::DefaultHandler
|
20
|
+
|
21
|
+
HEAD = 'head'
|
22
|
+
TITLE = 'title'
|
23
|
+
BODY = 'body'
|
24
|
+
START_CDATA = '<![CDATA['
|
25
|
+
END_CDATA = ']]>'
|
26
|
+
|
27
|
+
# TODO: Write an alternative implementation where we don't have to know
|
28
|
+
# which HTML elements have an empty content model.
|
29
|
+
EMPTY_CONTENT_ELEMENTS = Set[
|
30
|
+
'br',
|
31
|
+
'hr',
|
32
|
+
'img',
|
33
|
+
'input']
|
34
|
+
|
35
|
+
attr_reader :page
|
36
|
+
|
37
|
+
def start_document()
|
38
|
+
# number of open head elements
|
39
|
+
@head_count = 0
|
40
|
+
|
41
|
+
# Count of nested open elements where this handler is extracting their
|
42
|
+
# inner HTML.
|
43
|
+
@inner_depth = 0
|
44
|
+
|
45
|
+
@page = Hash.new()
|
46
|
+
end
|
47
|
+
|
48
|
+
def start_element(name, attributes)
|
49
|
+
if name == HEAD
|
50
|
+
@head_count += 1
|
51
|
+
end
|
52
|
+
|
53
|
+
if extracting_inner_html?()
|
54
|
+
@inner_depth += 1
|
55
|
+
render_start_tag(name, attributes)
|
56
|
+
return
|
57
|
+
end
|
58
|
+
|
59
|
+
if @head_count > 0 && name == TITLE
|
60
|
+
start_extracting_inner_html()
|
61
|
+
return
|
62
|
+
end
|
63
|
+
|
64
|
+
if name == BODY
|
65
|
+
start_extracting_inner_html()
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def end_element(name)
|
70
|
+
if name == HEAD
|
71
|
+
@head_count -= 1
|
72
|
+
end
|
73
|
+
|
74
|
+
if extracting_inner_html?()
|
75
|
+
@inner_depth -= 1
|
76
|
+
if extracting_inner_html?() && !EMPTY_CONTENT_ELEMENTS.include?(name)
|
77
|
+
render_end_tag(name)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
if @head_count > 0 && name == TITLE
|
83
|
+
@page.store(:title, @inner_html)
|
84
|
+
return
|
85
|
+
end
|
86
|
+
|
87
|
+
if name == BODY
|
88
|
+
@page.store(:body, @inner_html)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def characters(data)
|
93
|
+
if extracting_inner_html?()
|
94
|
+
append(data)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def cdata_block(data)
|
99
|
+
if extracting_inner_html?()
|
100
|
+
append(data)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def comment(data)
|
105
|
+
if extracting_inner_html?()
|
106
|
+
append("<!--#{data}-->")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def processing_instruction(data)
|
111
|
+
if extracting_inner_html?()
|
112
|
+
append("<?#{data}?>")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def extracting_inner_html?()
|
119
|
+
return @inner_depth > 0
|
120
|
+
end
|
121
|
+
|
122
|
+
def start_extracting_inner_html()
|
123
|
+
@inner_depth = 1
|
124
|
+
@inner_html = ''
|
125
|
+
end
|
126
|
+
|
127
|
+
def append(data)
|
128
|
+
@inner_html << data
|
129
|
+
end
|
130
|
+
|
131
|
+
def escape_quote(value)
|
132
|
+
return value.include?('"') ? value.gsub(/"/, '"') : value
|
133
|
+
end
|
134
|
+
|
135
|
+
def render_start_tag(tag_name, attributes)
|
136
|
+
append("<#{tag_name}")
|
137
|
+
|
138
|
+
attributes.each do |attribute|
|
139
|
+
qualified_name =
|
140
|
+
[attribute.prefix, attribute.localname].compact().join(':')
|
141
|
+
append(%( #{qualified_name}="#{escape_quote(attribute.value)}"))
|
142
|
+
end
|
143
|
+
|
144
|
+
append('>')
|
145
|
+
end
|
146
|
+
|
147
|
+
def render_end_tag(name)
|
148
|
+
append("</#{name}>")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end; end
|