under-os-ui 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +26 -0
- data/lib/assets/fontawesome-webfont.ttf +0 -0
- data/lib/assets/under-os.css +115 -0
- data/lib/core/kernel.rb +16 -0
- data/lib/under-os-ui.rb +6 -0
- data/lib/under_os/app.rb +26 -0
- data/lib/under_os/config.rb +25 -0
- data/lib/under_os/history.rb +53 -0
- data/lib/under_os/page.rb +178 -0
- data/lib/under_os/page/builder.rb +96 -0
- data/lib/under_os/page/layout.rb +43 -0
- data/lib/under_os/page/matcher.rb +128 -0
- data/lib/under_os/page/stylesheet.rb +67 -0
- data/lib/under_os/parser.rb +24 -0
- data/lib/under_os/parser/css.rb +37 -0
- data/lib/under_os/parser/html.rb +97 -0
- data/lib/under_os/ui.rb +3 -0
- data/lib/under_os/ui/alert.rb +52 -0
- data/lib/under_os/ui/button.rb +42 -0
- data/lib/under_os/ui/collection.rb +65 -0
- data/lib/under_os/ui/collection/cell.rb +21 -0
- data/lib/under_os/ui/collection/delegate.rb +70 -0
- data/lib/under_os/ui/collection/item.rb +32 -0
- data/lib/under_os/ui/collection/layout.rb +43 -0
- data/lib/under_os/ui/collection/styles.rb +15 -0
- data/lib/under_os/ui/div.rb +3 -0
- data/lib/under_os/ui/form.rb +60 -0
- data/lib/under_os/ui/icon.rb +61 -0
- data/lib/under_os/ui/icon/awesome.rb +376 -0
- data/lib/under_os/ui/icon/engine.rb +9 -0
- data/lib/under_os/ui/image.rb +31 -0
- data/lib/under_os/ui/input.rb +140 -0
- data/lib/under_os/ui/label.rb +21 -0
- data/lib/under_os/ui/locker.rb +42 -0
- data/lib/under_os/ui/navbar.rb +123 -0
- data/lib/under_os/ui/progress.rb +17 -0
- data/lib/under_os/ui/scroll.rb +102 -0
- data/lib/under_os/ui/select.rb +95 -0
- data/lib/under_os/ui/sidebar.rb +45 -0
- data/lib/under_os/ui/slider.rb +37 -0
- data/lib/under_os/ui/spinner.rb +23 -0
- data/lib/under_os/ui/style.rb +21 -0
- data/lib/under_os/ui/style/fonts.rb +56 -0
- data/lib/under_os/ui/style/margins.rb +164 -0
- data/lib/under_os/ui/style/outlining.rb +170 -0
- data/lib/under_os/ui/style/positioning.rb +183 -0
- data/lib/under_os/ui/switch.rb +26 -0
- data/lib/under_os/ui/textarea.rb +19 -0
- data/lib/under_os/ui/utils/animation.rb +101 -0
- data/lib/under_os/ui/utils/commons.rb +70 -0
- data/lib/under_os/ui/utils/dimensions.rb +37 -0
- data/lib/under_os/ui/utils/events.rb +210 -0
- data/lib/under_os/ui/utils/manipulation.rb +44 -0
- data/lib/under_os/ui/utils/position.rb +21 -0
- data/lib/under_os/ui/utils/size.rb +21 -0
- data/lib/under_os/ui/utils/styles.rb +89 -0
- data/lib/under_os/ui/utils/traversing.rb +44 -0
- data/lib/under_os/ui/utils/wrap.rb +77 -0
- data/lib/under_os/ui/view.rb +31 -0
- data/spec/assets/app.css +13 -0
- data/spec/assets/test.css +7 -0
- data/spec/assets/test.html +3 -0
- data/spec/assets/test.png +0 -0
- data/spec/assets/test_page.rb +2 -0
- data/spec/under_os/page/builder_spec.rb +128 -0
- data/spec/under_os/page/layout_spec.rb +18 -0
- data/spec/under_os/page/matcher_spec.rb +260 -0
- data/spec/under_os/page/stylesheet_spec.rb +83 -0
- data/spec/under_os/page_spec.rb +5 -0
- data/spec/under_os/parser/css_spec.rb +77 -0
- data/spec/under_os/parser/html_spec.rb +152 -0
- data/spec/under_os/parser_spec.rb +16 -0
- data/spec/under_os/ui/button_spec.rb +50 -0
- data/spec/under_os/ui/collection_spec.rb +19 -0
- data/spec/under_os/ui/div_spec.rb +24 -0
- data/spec/under_os/ui/form_spec.rb +156 -0
- data/spec/under_os/ui/icon_spec.rb +57 -0
- data/spec/under_os/ui/image_spec.rb +39 -0
- data/spec/under_os/ui/input_spec.rb +109 -0
- data/spec/under_os/ui/label_spec.rb +22 -0
- data/spec/under_os/ui/locker_spec.rb +31 -0
- data/spec/under_os/ui/progress_spec.rb +31 -0
- data/spec/under_os/ui/scroll_spec.rb +75 -0
- data/spec/under_os/ui/select_spec.rb +135 -0
- data/spec/under_os/ui/sidebar_spec.rb +35 -0
- data/spec/under_os/ui/slider_spec.rb +69 -0
- data/spec/under_os/ui/spinner_spec.rb +57 -0
- data/spec/under_os/ui/style/fonts_spec.rb +111 -0
- data/spec/under_os/ui/style/margins_spec.rb +106 -0
- data/spec/under_os/ui/style/outlining_spec.rb +101 -0
- data/spec/under_os/ui/style/positioning_spec.rb +69 -0
- data/spec/under_os/ui/style_spec.rb +19 -0
- data/spec/under_os/ui/switch_spec.rb +60 -0
- data/spec/under_os/ui/textarea_spec.rb +34 -0
- data/spec/under_os/ui/utils/commons_spec.rb +81 -0
- data/spec/under_os/ui/utils/events_spec.rb +87 -0
- data/spec/under_os/ui/utils/manipulation_spec.rb +130 -0
- data/spec/under_os/ui/utils/styles_spec.rb +140 -0
- data/spec/under_os/ui/utils/traversing_spec.rb +124 -0
- data/spec/under_os/ui/utils/wrap_spec.rb +69 -0
- data/spec/under_os/ui/view_spec.rb +39 -0
- data/under-os-ui.gemspec +23 -0
- metadata +216 -0
@@ -0,0 +1,96 @@
|
|
1
|
+
class UnderOs::Page::Builder
|
2
|
+
|
3
|
+
def self.views_from(source)
|
4
|
+
@inst ||= new
|
5
|
+
if source.is_a?(String)
|
6
|
+
@inst.from_html(source)
|
7
|
+
else
|
8
|
+
@inst.from_nodes(source)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def from_html(source)
|
13
|
+
@html ||= UnderOs::Parser::HTML.new
|
14
|
+
from_nodes @html.parse(source)
|
15
|
+
end
|
16
|
+
|
17
|
+
def from_nodes(list)
|
18
|
+
list.map do |node|
|
19
|
+
build_view_from(node)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def build_view_from(node)
|
24
|
+
klass = class_for(node)
|
25
|
+
options = options_from_attrs(node[:attrs] || {})
|
26
|
+
|
27
|
+
if klass.instance_methods.include?(:text) && node[:text]
|
28
|
+
options[:text] = node[:text]
|
29
|
+
end
|
30
|
+
|
31
|
+
klass.new(options).tap do |view|
|
32
|
+
if node[:children] && node[:children].any?
|
33
|
+
build_children_for(view, node[:children])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def class_for(node)
|
39
|
+
if node[:attrs] && node[:attrs][:wrapper].is_a?(String)
|
40
|
+
node[:attrs].delete(:wrapper).constantize
|
41
|
+
else
|
42
|
+
UnderOs::UI::Wrap::WRAPS_TAGS_MAP[node[:tag]] || UnderOs::UI::View
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def options_from_attrs(hash)
|
47
|
+
{}.tap do |options|
|
48
|
+
hash.each do |key, value|
|
49
|
+
options[key] = value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_children_for(view, nodes)
|
55
|
+
if view.is_a?(UnderOs::UI::Select)
|
56
|
+
view.optgroups = select_optgroups_from(nodes)
|
57
|
+
elsif view.is_a?(UnderOs::UI::Collection)
|
58
|
+
find_collection_items_for(view, nodes)
|
59
|
+
else
|
60
|
+
from_nodes(nodes).each do |child|
|
61
|
+
view.insert child
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def select_optgroups_from(nodes)
|
67
|
+
nodes = [{tag: 'optgroup', children: nodes}] if nodes[0][:tag] == 'option'
|
68
|
+
|
69
|
+
nodes.map do |group|
|
70
|
+
{}.tap do |options|
|
71
|
+
group[:children].each do |option|
|
72
|
+
if option[:tag] == 'option'
|
73
|
+
label = option[:text]
|
74
|
+
value = option[:attrs][:value] || label
|
75
|
+
|
76
|
+
options[value] = label if value && label
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def find_collection_items_for(collection, nodes)
|
84
|
+
nodes.each do |node|
|
85
|
+
klass = class_for(node)
|
86
|
+
|
87
|
+
if klass <= UnderOs::UI::Collection::Item
|
88
|
+
klass.build(collection) { from_nodes(node[:children]) }
|
89
|
+
end
|
90
|
+
|
91
|
+
# header
|
92
|
+
# footer
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class UnderOs::Page::Layout
|
2
|
+
|
3
|
+
def initialize(page)
|
4
|
+
@page = page
|
5
|
+
|
6
|
+
build_layout_from_xib || build_layout_from_html || build_layout_from_nothing
|
7
|
+
|
8
|
+
@page.view = UnderOs::UI::View.new(@page._.view)
|
9
|
+
@page.view.instance_variable_set('@_tag_name', 'PAGE')
|
10
|
+
end
|
11
|
+
|
12
|
+
def build_layout_from_xib
|
13
|
+
if filename = find_layout_with_type('xib')
|
14
|
+
@page._.view = NSBundle.mainBundle.loadNibNamed(filename.sub(/\.xib$/, ''), owner:self, options:nil)[0]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_layout_from_html
|
19
|
+
if filename = find_layout_with_type('html')
|
20
|
+
if layout = UnderOs::Parser.parse(filename)
|
21
|
+
if view = UnderOs::Page::Builder.views_from(layout)[0]
|
22
|
+
@page._.view = view._
|
23
|
+
@page.title = layout[0][:attrs][:title] || ''
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_layout_from_nothing
|
30
|
+
@page._.view = UIView.alloc.init
|
31
|
+
@page._.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_layout_with_type(ext)
|
35
|
+
filename = @page.class.layout || "#{@page.name}.#{ext}"
|
36
|
+
filename, type = filename.match(/^(.+?)\.([a-z]+)$/).to_a.slice(1,2)
|
37
|
+
|
38
|
+
if type == ext && NSBundle.mainBundle.pathForResource(filename, ofType:type)
|
39
|
+
"#{filename}.#{type}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
class UnderOs::Page::StylesMatcher
|
2
|
+
|
3
|
+
def self.new(css_rule)
|
4
|
+
@cache ||= {}
|
5
|
+
@cache[css_rule] ||= super
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(css_rule)
|
9
|
+
css_rules = css_rule.strip.split(/\s*,\s*/)
|
10
|
+
|
11
|
+
if css_rules.size == 1
|
12
|
+
rules = css_rules[0].scan(/([\S]+)/).map(&:first)
|
13
|
+
@rule = parse(rules.pop)
|
14
|
+
@parent = rules.size == 0 ? false : self.class.new(rules.join(' '))
|
15
|
+
else
|
16
|
+
@multiple_matchers = css_rules.map{ |rule| self.class.new(rule) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def match(view)
|
21
|
+
score_for(view) != 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def score_for(view)
|
25
|
+
return summary_score_for(view) if @multiple_matchers
|
26
|
+
|
27
|
+
id_score = id_score_for(view)
|
28
|
+
tag_score = tag_score_for(view)
|
29
|
+
class_score = class_score_for(view)
|
30
|
+
attrs_score = attrs_score_for(view)
|
31
|
+
total_score = id_score + tag_score + class_score + attrs_score
|
32
|
+
|
33
|
+
total_score = 0 if id_score == 0 && @rule[:id]
|
34
|
+
total_score = 0 if tag_score == 0 && @rule[:tag]
|
35
|
+
total_score = 0 if class_score == 0 && @rule[:classes].size != 0
|
36
|
+
total_score = 0 if attrs_score == 0 && @rule[:attrs].size != 0
|
37
|
+
|
38
|
+
if @parent && total_score != 0
|
39
|
+
parent_score = parent_score_for(view)
|
40
|
+
total_score = parent_score == 0 ? 0 : total_score + parent_score
|
41
|
+
end
|
42
|
+
|
43
|
+
total_score
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def summary_score_for(view)
|
49
|
+
@multiple_matchers.map{ |matcher| matcher.score_for(view) }.max
|
50
|
+
end
|
51
|
+
|
52
|
+
def id_score_for(view)
|
53
|
+
@rule[:id] == view.id ? 1 : 0
|
54
|
+
end
|
55
|
+
|
56
|
+
def tag_score_for(view)
|
57
|
+
@rule[:tag] == '*' || @rule[:tag] == view.tagName ? 1 : 0
|
58
|
+
end
|
59
|
+
|
60
|
+
def class_score_for(view)
|
61
|
+
return 0 if @rule[:classes].size == 0
|
62
|
+
match = @rule[:classes] & view.classNames
|
63
|
+
match.size == @rule[:classes].size ? match.size : 0
|
64
|
+
end
|
65
|
+
|
66
|
+
def attrs_score_for(view)
|
67
|
+
score = 0; return 0 if @rule[:attrs].size == 0
|
68
|
+
|
69
|
+
@rule[:attrs].each do |key, value, operand|
|
70
|
+
attr = view.respond_to?(key) && view.__send__(key)
|
71
|
+
attr = attr.to_s if attr.is_a?(Symbol) || attr.is_a?(Numeric)
|
72
|
+
|
73
|
+
if attr && attr.is_a?(String)
|
74
|
+
matches = case operand
|
75
|
+
when '=' then attr == value
|
76
|
+
when '*=' then attr.include?(value)
|
77
|
+
when '^=' then attr.starts_with?(value)
|
78
|
+
when '$=' then attr.ends_with?(value)
|
79
|
+
when '~=' then attr.split(' ').include?(value)
|
80
|
+
when '|=' then attr.split('-').include?(value)
|
81
|
+
end
|
82
|
+
|
83
|
+
score += 1 if matches
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
score
|
88
|
+
end
|
89
|
+
|
90
|
+
def parent_score_for(view)
|
91
|
+
parent = view; score = 0
|
92
|
+
|
93
|
+
while parent = parent.parent
|
94
|
+
score = @parent.score_for(parent)
|
95
|
+
break if score > 0
|
96
|
+
end
|
97
|
+
|
98
|
+
score
|
99
|
+
end
|
100
|
+
|
101
|
+
def parse(string)
|
102
|
+
{}.tap do |rule|
|
103
|
+
if m = string.match(/^([a-z\*]+)/)
|
104
|
+
rule[:tag] = m[1].upcase
|
105
|
+
else
|
106
|
+
rule[:tag] = false # so it wouldn't match nil
|
107
|
+
end
|
108
|
+
|
109
|
+
if m = string.match(/#([a-z_\-0-9]+)/)
|
110
|
+
rule[:id] = m[1]
|
111
|
+
else
|
112
|
+
rule[:id] = false # so it wouldn't match nil
|
113
|
+
end
|
114
|
+
|
115
|
+
if m = string.scan(/(\.)([a-z_\-0-9]+)/)
|
116
|
+
rule[:classes] = m.map{|c| c[1]}
|
117
|
+
else
|
118
|
+
rule[:classes] = []
|
119
|
+
end
|
120
|
+
|
121
|
+
if m = string.scan(/\[\s*([a-z]+)\s*([*^$~|]{0,1}=)\s*('|")?(.+?)(\3)?\s*\]/)
|
122
|
+
rule[:attrs] = m.map{|c| [c[0], c[3], c[1]] }
|
123
|
+
else
|
124
|
+
rule[:attrs] = []
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class UnderOs::Page::Stylesheet
|
2
|
+
attr_reader :rules
|
3
|
+
|
4
|
+
def initialize(styles={})
|
5
|
+
@rules = {}
|
6
|
+
|
7
|
+
styles.each do |rule, style|
|
8
|
+
self[rule] = style
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def styles_for(view)
|
13
|
+
{}.tap do |styles|
|
14
|
+
weighted_styles_for(view).each do |hash|
|
15
|
+
hash.each do |key, value|
|
16
|
+
styles[key] = value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](rule)
|
23
|
+
@rules[rule.to_s]
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(rule, values)
|
27
|
+
@rules[rule.to_s] ||= {}
|
28
|
+
@rules[rule.to_s].merge! values
|
29
|
+
end
|
30
|
+
|
31
|
+
def <<(another_stylesheet)
|
32
|
+
another_stylesheet.rules.each do |rule, styles|
|
33
|
+
self[rule] = styles
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def +(another_stylesheet)
|
38
|
+
self.class.new.tap do |combined_sheet|
|
39
|
+
combined_sheet << self
|
40
|
+
combined_sheet << another_stylesheet
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def load(filename)
|
45
|
+
UnderOs::Parser.parse(filename).each do |rule, styles|
|
46
|
+
self[rule] = styles
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def weighted_styles_for(view)
|
53
|
+
find_styles_for(view).
|
54
|
+
sort{|a,b| a[:score] <=> b[:score]}.
|
55
|
+
map{|e| e[:style]}
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_styles_for(view)
|
59
|
+
[].tap do |styles|
|
60
|
+
@rules.each do |css, rule|
|
61
|
+
score = UnderOs::Page::StylesMatcher.new(css).score_for(view)
|
62
|
+
styles << {score: score, style: rule} if score != 0
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#
|
2
|
+
# Generic templates/stylesheets parsing engine
|
3
|
+
#
|
4
|
+
class UnderOs::Parser
|
5
|
+
def self.parse(*args)
|
6
|
+
@inst ||= new
|
7
|
+
@inst.parse *args
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@css = CSS.new
|
12
|
+
@html = HTML.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse(filename)
|
16
|
+
filepath = NSBundle.mainBundle.pathForResource(filename, ofType:nil)
|
17
|
+
content = filepath ? UnderOs::File.read(filepath) : ''
|
18
|
+
|
19
|
+
case filename.split('.').pop
|
20
|
+
when 'css' then @css.parse(content)
|
21
|
+
when 'html' then @html.parse(content)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class UnderOs::Parser::CSS
|
2
|
+
def parse(style)
|
3
|
+
style = style.gsub(/\/\*[\s\S]+?\*\//, '').strip
|
4
|
+
|
5
|
+
{}.tap do |result|
|
6
|
+
style.scan(/(\A|\})([a-z0-9_\*\-\.\s#:,]+)\{([^}]+)/).map do |rule|
|
7
|
+
values = parse_styles(rule[2])
|
8
|
+
|
9
|
+
rule[1].split(',').each do |css_rule|
|
10
|
+
result[css_rule.gsub(/\s+/, ' ').strip] = values
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse_styles(styles)
|
17
|
+
{}.tap do |hash|
|
18
|
+
styles.scan(/([a-z\-]+)\s*:\s*([^;]+)\s*/).each do |param|
|
19
|
+
hash.merge! normalized_values(param[0], param[1])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def normalized_values(key, value)
|
25
|
+
key = key.camelize.to_sym
|
26
|
+
value = value.strip.gsub(/px$/, '')
|
27
|
+
value = value.gsub(/^('|")(.*?)\1$/, '\2')
|
28
|
+
value = value.to_f if value =~ /^[\-\d\.]+$/
|
29
|
+
|
30
|
+
if key == :background && value =~ /^[\S]+$/
|
31
|
+
key = :backgroundColor
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
{key => value}
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
class UnderOs::Parser::HTML
|
2
|
+
def parse(html)
|
3
|
+
html = html.strip.gsub(/<\!--[\s\S]*?-->/, '').gsub(/>\s+/, '>').gsub(/\s+</, '<')
|
4
|
+
|
5
|
+
[].tap do |top|
|
6
|
+
@top = top
|
7
|
+
@stack = []
|
8
|
+
@node = nil
|
9
|
+
i = 0
|
10
|
+
|
11
|
+
while i < html.size
|
12
|
+
@chunk = html.slice(i, html.size)
|
13
|
+
|
14
|
+
i += open_tag || close_tag || plain_text
|
15
|
+
end
|
16
|
+
|
17
|
+
# closing all the missing tags
|
18
|
+
while node = @stack.shift
|
19
|
+
node.delete(:children)
|
20
|
+
node.delete(:text)
|
21
|
+
@top << node if ! @top.include?(node)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def open_tag
|
27
|
+
if m = @chunk.match(/\A<([a-z]+)([^>]*)>/)
|
28
|
+
@node = {tag: m[1], attrs: parse_attrs_in(m[2])}
|
29
|
+
|
30
|
+
if parent = @stack.last
|
31
|
+
parent[:children] ||= []
|
32
|
+
parent[:children] << @node
|
33
|
+
parent.delete(:text) # it can have either text or children
|
34
|
+
else
|
35
|
+
@top << @node
|
36
|
+
end
|
37
|
+
|
38
|
+
@stack << @node
|
39
|
+
|
40
|
+
m[0].size
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def close_tag
|
45
|
+
if m = @chunk.match(/\A<\/([a-z]+)>/)
|
46
|
+
while node = @stack.pop
|
47
|
+
if node[:tag] != m[1]
|
48
|
+
if @stack.size > 0
|
49
|
+
@stack.last[:children] += node[:children] || []
|
50
|
+
node.delete(:children)
|
51
|
+
node.delete(:text)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
break
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
@node = @stack.last
|
59
|
+
|
60
|
+
m[0].size
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def plain_text
|
65
|
+
if m = @chunk.match(/\A([^<]+)/)
|
66
|
+
@stack.last[:text] = m[1] if @stack.last
|
67
|
+
|
68
|
+
m[0].size
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_attrs_in(string)
|
73
|
+
merge_data_attrs({}.tap do |hash|
|
74
|
+
string.scan(/([a-z][a-z_\-\d]+)=('|")(.+?)(\2)/).each do |match|
|
75
|
+
value = match[0] == match[2] ? true : match[2]
|
76
|
+
value = true if value == 'true'
|
77
|
+
value = false if value == 'false'
|
78
|
+
hash[match[0].to_sym] = value
|
79
|
+
end
|
80
|
+
end)
|
81
|
+
end
|
82
|
+
|
83
|
+
def merge_data_attrs(hash)
|
84
|
+
hash.keys.each do |key|
|
85
|
+
if key.to_s.starts_with?('data-')
|
86
|
+
hash[:data] ||= {}
|
87
|
+
|
88
|
+
value = hash.delete(key)
|
89
|
+
key = key.to_s.gsub(/^data\-/, '').camelize
|
90
|
+
|
91
|
+
hash[:data][key.to_sym] = value
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
hash
|
96
|
+
end
|
97
|
+
end
|