mjml-rb 0.5.1 → 0.5.2
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 +4 -4
- data/README.md +1 -1
- data/lib/mjml-rb/components/attributes.rb +5 -5
- data/lib/mjml-rb/components/base.rb +4 -0
- data/lib/mjml-rb/components/breakpoint.rb +1 -1
- data/lib/mjml-rb/components/head.rb +3 -3
- data/lib/mjml-rb/components/html_attributes.rb +3 -3
- data/lib/mjml-rb/components/social.rb +2 -2
- data/lib/mjml-rb/components/table.rb +1 -1
- data/lib/mjml-rb/node_compat.rb +15 -0
- data/lib/mjml-rb/parser.rb +21 -38
- data/lib/mjml-rb/renderer.rb +36 -26
- data/lib/mjml-rb/validator.rb +15 -4
- data/lib/mjml-rb/version.rb +1 -1
- data/lib/mjml-rb.rb +1 -1
- metadata +2 -2
- data/lib/mjml-rb/ast_node.rb +0 -37
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ffb73c167fce50a47b0513aa071f478625bd28de94a47904980746905f731e8e
|
|
4
|
+
data.tar.gz: 67977b5a7c57afb011f6e1f7f1b1de4d1872cca1847eb0f61c6413a60b93c919
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1eb9c9b43804484c8c0a5e89e04c83f85bcd0699ada32f6e77b566341795372386bac5fc41766bdc98d6f2844be69e6c456b461c9649971f42a4dbc97016226e
|
|
7
|
+
data.tar.gz: d49c215799a867e70ced384893081933c596ea65cc2e325782110c796f8f690b410f5a24cb1858e91c3699d635893a3ce273154cd12900575331899c108af54b
|
data/README.md
CHANGED
|
@@ -97,7 +97,7 @@ Supports Slim and Haml via `config.mjml_rb.rails_template_language = :slim`. See
|
|
|
97
97
|
MJML string → Parser → AST → Validator → Renderer → HTML
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
1. **Parser** — normalizes source, expands `mj-include`,
|
|
100
|
+
1. **Parser** — normalizes source, expands `mj-include`, produces a Nokogiri XML node tree
|
|
101
101
|
2. **Validator** — checks structure, hierarchy, and attribute types
|
|
102
102
|
3. **Renderer** — resolves head metadata, applies defaults, emits responsive HTML
|
|
103
103
|
|
|
@@ -23,16 +23,16 @@ module MjmlRb
|
|
|
23
23
|
attributes_node.element_children.each do |child|
|
|
24
24
|
case child.tag_name
|
|
25
25
|
when "mj-all"
|
|
26
|
-
context[:global_defaults].merge!(child
|
|
26
|
+
context[:global_defaults].merge!(node_string_attributes(child))
|
|
27
27
|
when "mj-class"
|
|
28
|
-
name = child
|
|
28
|
+
name = child["name"]
|
|
29
29
|
next unless name
|
|
30
30
|
|
|
31
31
|
context[:classes][name] ||= {}
|
|
32
|
-
context[:classes][name].merge!(child.
|
|
32
|
+
context[:classes][name].merge!(node_string_attributes(child).reject { |key, _| key == "name" })
|
|
33
33
|
|
|
34
34
|
defaults = child.element_children.each_with_object({}) do |class_child, memo|
|
|
35
|
-
memo[class_child.tag_name] = class_child
|
|
35
|
+
memo[class_child.tag_name] = node_string_attributes(class_child)
|
|
36
36
|
end
|
|
37
37
|
next if defaults.empty?
|
|
38
38
|
|
|
@@ -40,7 +40,7 @@ module MjmlRb
|
|
|
40
40
|
context[:classes_default][name].merge!(defaults)
|
|
41
41
|
else
|
|
42
42
|
context[:tag_defaults][child.tag_name] ||= {}
|
|
43
|
-
context[:tag_defaults][child.tag_name].merge!(child
|
|
43
|
+
context[:tag_defaults][child.tag_name].merge!(node_string_attributes(child))
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
end
|
|
@@ -42,14 +42,14 @@ module MjmlRb
|
|
|
42
42
|
context[:preview] = raw_inner(node).strip
|
|
43
43
|
when "mj-style"
|
|
44
44
|
css = raw_inner(node)
|
|
45
|
-
if node
|
|
45
|
+
if node["inline"] == "inline"
|
|
46
46
|
context[:inline_styles] << css
|
|
47
47
|
else
|
|
48
48
|
context[:user_styles] << css
|
|
49
49
|
end
|
|
50
50
|
when "mj-font"
|
|
51
|
-
name = node
|
|
52
|
-
href = node
|
|
51
|
+
name = node["name"]
|
|
52
|
+
href = node["href"]
|
|
53
53
|
context[:fonts][name] = href if name && href
|
|
54
54
|
end
|
|
55
55
|
end
|
|
@@ -32,16 +32,16 @@ module MjmlRb
|
|
|
32
32
|
node.element_children.each do |selector|
|
|
33
33
|
next unless selector.tag_name == "mj-selector"
|
|
34
34
|
|
|
35
|
-
path = selector
|
|
35
|
+
path = selector["path"].to_s.strip
|
|
36
36
|
next if path.empty?
|
|
37
37
|
|
|
38
38
|
custom_attrs = selector.element_children.each_with_object({}) do |child, memo|
|
|
39
39
|
next unless child.tag_name == "mj-html-attribute"
|
|
40
40
|
|
|
41
|
-
name = child
|
|
41
|
+
name = child["name"].to_s.strip
|
|
42
42
|
next if name.empty?
|
|
43
43
|
|
|
44
|
-
memo[name] = child.
|
|
44
|
+
memo[name] = child.content
|
|
45
45
|
end
|
|
46
46
|
next if custom_attrs.empty?
|
|
47
47
|
|
|
@@ -237,7 +237,7 @@ module MjmlRb
|
|
|
237
237
|
|
|
238
238
|
def render_social_element(node, attrs)
|
|
239
239
|
a = attrs # already merged with defaults by caller
|
|
240
|
-
net_name = node
|
|
240
|
+
net_name = node["name"]
|
|
241
241
|
network = SOCIAL_NETWORKS[net_name] || {}
|
|
242
242
|
|
|
243
243
|
# Resolve href: if network has a share-url, substitute [[URL]] with the raw href
|
|
@@ -325,7 +325,7 @@ module MjmlRb
|
|
|
325
325
|
HTML
|
|
326
326
|
|
|
327
327
|
# Content cell (text)
|
|
328
|
-
content = node.
|
|
328
|
+
content = node.content.strip
|
|
329
329
|
content_cell = if content.empty?
|
|
330
330
|
""
|
|
331
331
|
else
|
data/lib/mjml-rb/parser.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require "nokogiri"
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
3
|
+
require_relative "node_compat"
|
|
4
4
|
|
|
5
5
|
module MjmlRb
|
|
6
6
|
class Parser
|
|
@@ -57,7 +57,8 @@ module MjmlRb
|
|
|
57
57
|
xml = replace_html_entities(xml)
|
|
58
58
|
doc = Nokogiri::XML(xml) { |config| config.strict }
|
|
59
59
|
normalize_root_head_elements(doc)
|
|
60
|
-
|
|
60
|
+
prepare_node(doc.root, keep_comments: opts[:keep_comments])
|
|
61
|
+
doc.root
|
|
61
62
|
rescue Nokogiri::XML::SyntaxError => e
|
|
62
63
|
raise ParseError.new("XML parse error: #{e.message}")
|
|
63
64
|
end
|
|
@@ -402,55 +403,37 @@ module MjmlRb
|
|
|
402
403
|
raise Errno::ENOENT, include_path
|
|
403
404
|
end
|
|
404
405
|
|
|
405
|
-
def
|
|
406
|
+
def prepare_node(element, keep_comments:)
|
|
406
407
|
raise ParseError, "Missing XML root element" unless element
|
|
407
408
|
|
|
408
|
-
#
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
meta_line = element.line
|
|
412
|
-
meta_file = element["data-mjml-file"]
|
|
413
|
-
attrs = {}
|
|
414
|
-
element.attributes.each do |name, attr|
|
|
415
|
-
attrs[name] = attr.value unless name.start_with?("data-mjml-")
|
|
409
|
+
# Mark non-mj elements for raw HTML handling
|
|
410
|
+
unless element.name.start_with?("mj-") || element.name == "mjml"
|
|
411
|
+
element["data-mjml-raw"] = "true"
|
|
416
412
|
end
|
|
417
|
-
attrs["data-mjml-raw"] = "true" unless element.name.start_with?("mj-") || element.name == "mjml"
|
|
418
413
|
|
|
419
|
-
# For ending-tag elements whose content was wrapped in CDATA,
|
|
420
|
-
#
|
|
414
|
+
# For ending-tag elements whose content was wrapped in CDATA,
|
|
415
|
+
# mark them so raw_inner knows to extract content directly.
|
|
421
416
|
if ENDING_TAGS_FOR_CDATA.include?(element.name)
|
|
422
|
-
|
|
423
|
-
return
|
|
424
|
-
tag_name: element.name,
|
|
425
|
-
attributes: attrs,
|
|
426
|
-
children: [],
|
|
427
|
-
content: raw_content.empty? ? nil : raw_content,
|
|
428
|
-
line: meta_line,
|
|
429
|
-
file: meta_file
|
|
430
|
-
)
|
|
417
|
+
element["data-mjml-ending-tag"] = "true"
|
|
418
|
+
return
|
|
431
419
|
end
|
|
432
420
|
|
|
433
|
-
|
|
421
|
+
# Strip ignorable whitespace text nodes, unwanted comments,
|
|
422
|
+
# and recurse into element children.
|
|
423
|
+
element.children.to_a.each do |child|
|
|
434
424
|
if child.element?
|
|
435
|
-
|
|
425
|
+
prepare_node(child, keep_comments: keep_comments)
|
|
436
426
|
elsif child.text? || child.cdata?
|
|
437
427
|
text = child.content
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
memo << AstNode.new(tag_name: "#text", content: text)
|
|
428
|
+
if text.empty? || (text.strip.empty? && ignorable_whitespace_text?(text, parent_element_name: element.name))
|
|
429
|
+
child.remove
|
|
430
|
+
end
|
|
442
431
|
elsif child.comment?
|
|
443
|
-
|
|
432
|
+
child.remove unless keep_comments
|
|
433
|
+
else
|
|
434
|
+
child.remove
|
|
444
435
|
end
|
|
445
436
|
end
|
|
446
|
-
|
|
447
|
-
AstNode.new(
|
|
448
|
-
tag_name: element.name,
|
|
449
|
-
attributes: attrs,
|
|
450
|
-
children: children,
|
|
451
|
-
line: meta_line,
|
|
452
|
-
file: meta_file
|
|
453
|
-
)
|
|
454
437
|
end
|
|
455
438
|
|
|
456
439
|
# Lenient XML parse used during include expansion and intermediate steps.
|
data/lib/mjml-rb/renderer.rb
CHANGED
|
@@ -70,9 +70,9 @@ module MjmlRb
|
|
|
70
70
|
|
|
71
71
|
context = build_context(head, options)
|
|
72
72
|
context[:before_doctype] = root_file_start_raw(document)
|
|
73
|
-
context[:lang] = options[:lang] || document
|
|
74
|
-
context[:dir] = options[:dir] || document
|
|
75
|
-
context[:force_owa_desktop] = document
|
|
73
|
+
context[:lang] = options[:lang] || document["lang"] || "und"
|
|
74
|
+
context[:dir] = options[:dir] || document["dir"] || "auto"
|
|
75
|
+
context[:force_owa_desktop] = document["owa"] == "desktop"
|
|
76
76
|
context[:printer_support] = options[:printer_support] || options[:printerSupport]
|
|
77
77
|
context[:column_widths] = {}
|
|
78
78
|
append_component_head_styles(document, context)
|
|
@@ -225,7 +225,7 @@ module MjmlRb
|
|
|
225
225
|
return [100] if total == 0
|
|
226
226
|
|
|
227
227
|
widths = columns.map do |col|
|
|
228
|
-
w = col
|
|
228
|
+
w = col["width"]
|
|
229
229
|
if w && w.to_s =~ /(\d+(?:\.\d+)?)\s*%/
|
|
230
230
|
$1.to_f
|
|
231
231
|
elsif w && w.to_s =~ /(\d+(?:\.\d+)?)\s*px/
|
|
@@ -668,7 +668,7 @@ module MjmlRb
|
|
|
668
668
|
attrs.merge!(context[:global_defaults] || {})
|
|
669
669
|
attrs.merge!(context[:tag_defaults][node.tag_name] || {})
|
|
670
670
|
|
|
671
|
-
node_classes = node
|
|
671
|
+
node_classes = node["mj-class"].to_s.split(/\s+/).reject(&:empty?)
|
|
672
672
|
class_attrs = node_classes.each_with_object({}) do |klass, memo|
|
|
673
673
|
mj_class_attrs = (context[:classes] || {})[klass] || {}
|
|
674
674
|
if memo["css-class"] && mj_class_attrs["css-class"]
|
|
@@ -683,33 +683,29 @@ module MjmlRb
|
|
|
683
683
|
attrs.merge!(((context[:classes_default] || {})[klass] || {})[node.tag_name] || {})
|
|
684
684
|
end
|
|
685
685
|
|
|
686
|
-
attrs.merge!(node
|
|
686
|
+
attrs.merge!(node_string_attributes(node))
|
|
687
687
|
attrs
|
|
688
688
|
end
|
|
689
689
|
|
|
690
690
|
def html_inner(node)
|
|
691
|
-
|
|
692
|
-
node.inner_html
|
|
693
|
-
else
|
|
694
|
-
escape_html(node.text_content)
|
|
695
|
-
end
|
|
691
|
+
node.inner_html
|
|
696
692
|
end
|
|
697
693
|
|
|
698
694
|
def raw_inner(node)
|
|
699
|
-
# For ending-tag nodes whose content was preserved as raw HTML by the parser
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
if node.
|
|
703
|
-
node.children.
|
|
704
|
-
if child.text?
|
|
705
|
-
child.content.to_s
|
|
706
|
-
else
|
|
707
|
-
child.to_html
|
|
708
|
-
end
|
|
709
|
-
end.join
|
|
710
|
-
else
|
|
711
|
-
node.text_content
|
|
695
|
+
# For ending-tag nodes whose content was preserved as raw HTML by the parser.
|
|
696
|
+
# The parser marks these with data-mjml-ending-tag; their children are CDATA
|
|
697
|
+
# nodes containing the raw HTML content.
|
|
698
|
+
if node.element? && node["data-mjml-ending-tag"]
|
|
699
|
+
return node.children.select { |c| c.cdata? || c.text? }.map(&:content).join
|
|
712
700
|
end
|
|
701
|
+
|
|
702
|
+
node.children.map do |child|
|
|
703
|
+
if child.text?
|
|
704
|
+
child.content.to_s
|
|
705
|
+
else
|
|
706
|
+
child.to_html
|
|
707
|
+
end
|
|
708
|
+
end.join
|
|
713
709
|
end
|
|
714
710
|
|
|
715
711
|
def annotate_raw_html(content)
|
|
@@ -755,7 +751,7 @@ module MjmlRb
|
|
|
755
751
|
|
|
756
752
|
def with_inherited_mj_class(context, node)
|
|
757
753
|
previous = context[:inherited_mj_class]
|
|
758
|
-
current = node
|
|
754
|
+
current = node["mj-class"]
|
|
759
755
|
context[:inherited_mj_class] = (current && !current.empty?) ? current : previous
|
|
760
756
|
yield
|
|
761
757
|
ensure
|
|
@@ -765,7 +761,7 @@ module MjmlRb
|
|
|
765
761
|
def root_file_start_raw(document)
|
|
766
762
|
document.element_children.filter_map do |child|
|
|
767
763
|
next unless child.tag_name == "mj-raw"
|
|
768
|
-
next unless child
|
|
764
|
+
next unless child["position"] == "file-start"
|
|
769
765
|
|
|
770
766
|
raw_inner(child)
|
|
771
767
|
end.join("\n")
|
|
@@ -779,6 +775,20 @@ module MjmlRb
|
|
|
779
775
|
result
|
|
780
776
|
end
|
|
781
777
|
|
|
778
|
+
# Internal-only attributes set by the parser for metadata tracking.
|
|
779
|
+
# These are excluded from the public attributes hash; data-mjml-raw
|
|
780
|
+
# is intentionally kept because it is used by the rendering pipeline.
|
|
781
|
+
INTERNAL_ATTRIBUTES = %w[data-mjml-file data-mjml-ending-tag].freeze
|
|
782
|
+
|
|
783
|
+
def node_string_attributes(node)
|
|
784
|
+
result = {}
|
|
785
|
+
node.attributes.each do |name, attr|
|
|
786
|
+
next if INTERNAL_ATTRIBUTES.include?(name)
|
|
787
|
+
result[name] = attr.respond_to?(:value) ? attr.value : attr.to_s
|
|
788
|
+
end
|
|
789
|
+
result
|
|
790
|
+
end
|
|
791
|
+
|
|
782
792
|
def escape_html(value)
|
|
783
793
|
CGI.escapeHTML(value.to_s)
|
|
784
794
|
end
|
data/lib/mjml-rb/validator.rb
CHANGED
|
@@ -16,7 +16,7 @@ module MjmlRb
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def validate(mjml_or_ast, options = {})
|
|
19
|
-
root = mjml_or_ast.is_a?(
|
|
19
|
+
root = mjml_or_ast.is_a?(Nokogiri::XML::Node) ? mjml_or_ast : parse_ast(mjml_or_ast, options)
|
|
20
20
|
unless root&.tag_name == "mjml"
|
|
21
21
|
return { errors: [error("Root element must be <mjml>", tag_name: root&.tag_name)], warnings: [] }
|
|
22
22
|
end
|
|
@@ -97,7 +97,7 @@ module MjmlRb
|
|
|
97
97
|
def validate_required_attributes(node, errors)
|
|
98
98
|
required = REQUIRED_BY_TAG[node.tag_name] || []
|
|
99
99
|
required.each do |attr|
|
|
100
|
-
next if node.
|
|
100
|
+
next if node.has_attribute?(attr)
|
|
101
101
|
|
|
102
102
|
errors << error("Attribute `#{attr}` is required for <#{node.tag_name}>",
|
|
103
103
|
tag_name: node.tag_name, line: node.line, file: node.file)
|
|
@@ -108,7 +108,7 @@ module MjmlRb
|
|
|
108
108
|
allowed_attributes = allowed_attributes_for(node.tag_name)
|
|
109
109
|
return if allowed_attributes.empty?
|
|
110
110
|
|
|
111
|
-
node.
|
|
111
|
+
string_attrs(node).each_key do |attribute_name|
|
|
112
112
|
next if allowed_attributes.key?(attribute_name)
|
|
113
113
|
next if GLOBAL_ALLOWED_ATTRIBUTES.include?(attribute_name)
|
|
114
114
|
|
|
@@ -121,7 +121,7 @@ module MjmlRb
|
|
|
121
121
|
allowed_attributes = allowed_attributes_for(node.tag_name)
|
|
122
122
|
return if allowed_attributes.empty?
|
|
123
123
|
|
|
124
|
-
node.
|
|
124
|
+
string_attrs(node).each do |attribute_name, attribute_value|
|
|
125
125
|
next if GLOBAL_ALLOWED_ATTRIBUTES.include?(attribute_name)
|
|
126
126
|
|
|
127
127
|
expected_type = allowed_attributes[attribute_name]
|
|
@@ -218,6 +218,17 @@ module MjmlRb
|
|
|
218
218
|
end
|
|
219
219
|
end
|
|
220
220
|
|
|
221
|
+
INTERNAL_ATTRIBUTES = %w[data-mjml-file data-mjml-ending-tag data-mjml-raw].freeze
|
|
222
|
+
|
|
223
|
+
def string_attrs(node)
|
|
224
|
+
result = {}
|
|
225
|
+
node.attributes.each do |name, attr|
|
|
226
|
+
next if INTERNAL_ATTRIBUTES.include?(name)
|
|
227
|
+
result[name] = attr.respond_to?(:value) ? attr.value : attr.to_s
|
|
228
|
+
end
|
|
229
|
+
result
|
|
230
|
+
end
|
|
231
|
+
|
|
221
232
|
def error(message, line: nil, tag_name: nil, file: nil)
|
|
222
233
|
location = [
|
|
223
234
|
("line #{line}" if line),
|
data/lib/mjml-rb/version.rb
CHANGED
data/lib/mjml-rb.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require_relative "mjml-rb/version"
|
|
2
2
|
require_relative "mjml-rb/result"
|
|
3
|
-
require_relative "mjml-rb/
|
|
3
|
+
require_relative "mjml-rb/node_compat"
|
|
4
4
|
require_relative "mjml-rb/dependencies"
|
|
5
5
|
require_relative "mjml-rb/component_registry"
|
|
6
6
|
require_relative "mjml-rb/config_file"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mjml-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Andriichuk
|
|
@@ -51,7 +51,6 @@ files:
|
|
|
51
51
|
- Rakefile
|
|
52
52
|
- bin/mjml
|
|
53
53
|
- lib/mjml-rb.rb
|
|
54
|
-
- lib/mjml-rb/ast_node.rb
|
|
55
54
|
- lib/mjml-rb/cli.rb
|
|
56
55
|
- lib/mjml-rb/compiler.rb
|
|
57
56
|
- lib/mjml-rb/component_registry.rb
|
|
@@ -80,6 +79,7 @@ files:
|
|
|
80
79
|
- lib/mjml-rb/components/text.rb
|
|
81
80
|
- lib/mjml-rb/config_file.rb
|
|
82
81
|
- lib/mjml-rb/dependencies.rb
|
|
82
|
+
- lib/mjml-rb/node_compat.rb
|
|
83
83
|
- lib/mjml-rb/parser.rb
|
|
84
84
|
- lib/mjml-rb/railtie.rb
|
|
85
85
|
- lib/mjml-rb/renderer.rb
|
data/lib/mjml-rb/ast_node.rb
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
module MjmlRb
|
|
2
|
-
class AstNode
|
|
3
|
-
attr_reader :tag_name, :attributes, :children, :content, :line, :file
|
|
4
|
-
|
|
5
|
-
def initialize(tag_name:, attributes: {}, children: [], content: nil, line: nil, file: nil)
|
|
6
|
-
@tag_name = tag_name.to_s
|
|
7
|
-
@attributes = attributes.transform_keys(&:to_s)
|
|
8
|
-
@children = Array(children)
|
|
9
|
-
@content = content
|
|
10
|
-
@line = line
|
|
11
|
-
@file = file
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def text?
|
|
15
|
-
@tag_name == "#text"
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def comment?
|
|
19
|
-
@tag_name == "#comment"
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def element?
|
|
23
|
-
!text? && !comment?
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def text_content
|
|
27
|
-
return @content.to_s if text?
|
|
28
|
-
result = +""
|
|
29
|
-
@children.each { |child| result << child.text_content }
|
|
30
|
-
result
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def element_children
|
|
34
|
-
@element_children ||= @children.select(&:element?)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|