herbgobbler 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.
- data/bin/gobble +43 -0
- data/grammer/erb_grammer.treetop +256 -0
- data/lib/core/base_node.rb +29 -0
- data/lib/core/base_text_extractor.rb +19 -0
- data/lib/core/base_translation_store.rb +49 -0
- data/lib/core/core.rb +17 -0
- data/lib/core/double_quoted_text_node.rb +89 -0
- data/lib/core/erb_file.rb +159 -0
- data/lib/core/erb_string_non_text_content.rb +8 -0
- data/lib/core/i18n_key.rb +74 -0
- data/lib/core/ignorable_tag_node.rb +10 -0
- data/lib/core/method_call_node.rb +44 -0
- data/lib/core/node_processing.rb +26 -0
- data/lib/core/non_extracting_non_text_node.rb +8 -0
- data/lib/core/non_text_node.rb +32 -0
- data/lib/core/rails_text_extractor.rb +88 -0
- data/lib/core/rails_translation_store.rb +42 -0
- data/lib/core/text_extractor.rb +31 -0
- data/lib/core/text_node.rb +73 -0
- data/lib/herbgobbler.rb +7 -0
- data/lib/nodes/combindable_herb_non_text_node.rb +5 -0
- data/lib/nodes/herb_combined_node.rb +47 -0
- data/lib/nodes/herb_erb_text_call_node.rb +52 -0
- data/lib/nodes/herb_node_retaining_node.rb +37 -0
- data/lib/nodes/herb_node_retaining_non_text_node.rb +30 -0
- data/lib/nodes/herb_node_retaining_text_node.rb +107 -0
- data/lib/nodes/herb_non_text_node.rb +21 -0
- data/lib/nodes/herb_string_variable.rb +9 -0
- data/lib/nodes/herb_text_node.rb +23 -0
- data/lib/nodes/herb_white_space_text_node.rb +14 -0
- data/lib/nodes/nodes.rb +13 -0
- data/lib/nodes/rails_text_variable_node.rb +19 -0
- data/scripts/display_syntax_tree.rb +18 -0
- data/scripts/extract_text_from_erb.rb +12 -0
- data/scripts/extract_yml_from_erb.rb +12 -0
- data/scripts/rails_gobble_file.rb +37 -0
- data/scripts/save_result.rb +11 -0
- metadata +154 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
class I18nKey
|
2
|
+
|
3
|
+
DEFAULT_KEY_NAME = "key"
|
4
|
+
|
5
|
+
def initialize( text, key_store = [] )
|
6
|
+
@text = text
|
7
|
+
@key_store = key_store
|
8
|
+
@key_value = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def key_value
|
12
|
+
if( @key_value.nil? )
|
13
|
+
to_return = remove_html_tags( @text )
|
14
|
+
to_return = to_return.gsub( /[%{].*?[}]/, '' )
|
15
|
+
to_return = to_return.gsub( /[^a-zA-Z0-9]/, ' ' )
|
16
|
+
to_return = to_return.gsub( /[ ]+/, ' ' )
|
17
|
+
to_return = to_return.strip.downcase.chomp
|
18
|
+
to_return = to_return.gsub( / /, '_' )
|
19
|
+
to_return = cut_down_to_size( to_return )
|
20
|
+
to_return = ensure_no_duplicates( to_return )
|
21
|
+
self.key_value = to_return
|
22
|
+
end
|
23
|
+
@key_value
|
24
|
+
end
|
25
|
+
|
26
|
+
def key_value=(value)
|
27
|
+
@key_value = value
|
28
|
+
@key_store << value
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
key_value
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def cut_down_to_size( string_to_remove )
|
38
|
+
if( string_to_remove.length < 15 )
|
39
|
+
string_to_remove
|
40
|
+
else
|
41
|
+
if( string_to_remove.rindex( '_' ) )
|
42
|
+
cut_down_to_size( string_to_remove[0..(string_to_remove.rindex('_')-1) ] )
|
43
|
+
else
|
44
|
+
string_to_remove
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def exists_in_keystore?( value )
|
50
|
+
!(@key_store.index( value ).nil?)
|
51
|
+
end
|
52
|
+
|
53
|
+
def ensure_no_duplicates( key_value, incrementor = 1 )
|
54
|
+
if( key_value.strip.empty? )
|
55
|
+
ensure_no_duplicates( DEFAULT_KEY_NAME )
|
56
|
+
elsif( exists_in_keystore?( key_value ) )
|
57
|
+
incremented_value = "#{key_value}_#{incrementor}"
|
58
|
+
if( exists_in_keystore?( incremented_value ) )
|
59
|
+
ensure_no_duplicates( key_value, incrementor + 1 )
|
60
|
+
else
|
61
|
+
incremented_value
|
62
|
+
end
|
63
|
+
else
|
64
|
+
key_value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def remove_html_tags( string_to_remove )
|
69
|
+
string_to_remove.gsub( /<[\/]?[a-zA-Z]+[^<]*>/, '_')
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module MethodCallNode
|
2
|
+
include TextNode
|
3
|
+
|
4
|
+
def extract_text( text_extractor, node_tree, surrounding_nodes )
|
5
|
+
|
6
|
+
text_string = ''
|
7
|
+
self.elements.each do |node|
|
8
|
+
if( node.is_a?( TextNode ) )
|
9
|
+
translated_node = text_extractor.translate_text( node.text_value )
|
10
|
+
text_string << "(#{translated_node.text_value})"
|
11
|
+
else
|
12
|
+
text_string << node.text_value
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
if( surrounded_by_text? (surrounding_nodes) )
|
17
|
+
text_extractor.add_variable( generate_i18n_key( text_extractor, node_tree ).to_s, text_string.strip )
|
18
|
+
else
|
19
|
+
node_tree << HerbNonTextNode.new( "<%= #{text_string.strip} %>" )
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def surrounded_by_text?( nodes )
|
27
|
+
# if the surrounding nodes only contain myself and <%= and %> then this
|
28
|
+
# is not surrounded by text
|
29
|
+
count_non_whitespace_nodes( nodes ) > 3
|
30
|
+
end
|
31
|
+
|
32
|
+
def count_non_whitespace_nodes( nodes )
|
33
|
+
node_count = 0
|
34
|
+
|
35
|
+
nodes.each do |node|
|
36
|
+
node_count += 1 unless node.text_value.strip.empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
node_count
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module NodeProcessing
|
2
|
+
|
3
|
+
|
4
|
+
def flatten(node, leaves = [])
|
5
|
+
# This finds all of the leaves, where a leaf is defined as an
|
6
|
+
# element with no sub elements. This also treats combindable nodes
|
7
|
+
# and text nodes as leaves because we want to keep them intact for
|
8
|
+
# extraction and combination
|
9
|
+
if( !node.respond_to?( :elements) ||
|
10
|
+
node.elements.nil? ||
|
11
|
+
node.elements.empty? ||
|
12
|
+
(node.is_a?( TextNode ) && !node.has_variables?)||
|
13
|
+
node.is_a?( HerbStringVariable ) ||
|
14
|
+
( node.is_a?(NonTextNode) && node.can_be_combined? ) )
|
15
|
+
leaves << node if( !node.text_value.empty? )
|
16
|
+
leaves
|
17
|
+
else
|
18
|
+
node.elements.each do |sub_node|
|
19
|
+
flatten( sub_node, leaves )
|
20
|
+
end
|
21
|
+
leaves
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module NonTextNode
|
2
|
+
include BaseNode
|
3
|
+
def can_be_combined?
|
4
|
+
false
|
5
|
+
end
|
6
|
+
|
7
|
+
def extract_text( text_extractor, node_tree )
|
8
|
+
if( self.white_space? )
|
9
|
+
text_extractor.white_space( self )
|
10
|
+
else
|
11
|
+
text_extractor.add_non_text( self ) unless self.contains_only_whitespace?
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def contains_only_whitespace?
|
17
|
+
self.text_value.strip.length == 0
|
18
|
+
end
|
19
|
+
|
20
|
+
def text?
|
21
|
+
false
|
22
|
+
end
|
23
|
+
|
24
|
+
def top_level?
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def white_space?
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
class RailsTextExtractor < BaseTextExtractor
|
2
|
+
attr_reader :translation_store
|
3
|
+
|
4
|
+
def initialize( translation_store = RailsTranslationStore.new )
|
5
|
+
@key_store = []
|
6
|
+
@translation_store = translation_store
|
7
|
+
@current_text = []
|
8
|
+
@current_nodes = []
|
9
|
+
@current_variables = []
|
10
|
+
end
|
11
|
+
|
12
|
+
# This is called when text extraction has begun
|
13
|
+
def starting_text_extraction
|
14
|
+
end
|
15
|
+
|
16
|
+
def start_html_text
|
17
|
+
@current_text = []
|
18
|
+
@current_nodes = []
|
19
|
+
@current_variables = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def end_html_text
|
23
|
+
whitespace, @current_text = strip_ending_whitespace_nodes( @current_text )
|
24
|
+
|
25
|
+
unless( @current_text.empty? && @current_variables.empty? )
|
26
|
+
total_text = @current_text.inject("") { |all_text, node| all_text + node.text_value }
|
27
|
+
@current_nodes << HerbErbTextCallNode.new( [total_text], @key_store, "t '.", "'", @current_variables )
|
28
|
+
@translation_store.add_translation( @current_nodes.last.key_value, @current_nodes.last.original_text )
|
29
|
+
end
|
30
|
+
|
31
|
+
# Just reset everything here to be cautious
|
32
|
+
to_return = @current_nodes
|
33
|
+
@current_nodes = []
|
34
|
+
@current_text = []
|
35
|
+
|
36
|
+
return to_return + whitespace
|
37
|
+
end
|
38
|
+
|
39
|
+
def white_space( node )
|
40
|
+
if( @current_text.empty? )
|
41
|
+
@current_nodes << node
|
42
|
+
else
|
43
|
+
@current_text << node
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# This is used to add a new variable into the current html element
|
48
|
+
def add_variable( variable_name, variable_value)
|
49
|
+
variable_node = RailsTextVariableNode.new( variable_name, variable_value )
|
50
|
+
@current_text << variable_node
|
51
|
+
@current_variables << variable_node
|
52
|
+
end
|
53
|
+
|
54
|
+
# This is called to just produce a translated text node without erb
|
55
|
+
# braces around it.
|
56
|
+
def translate_text( text_to_translate )
|
57
|
+
call_node = HerbErbTextCallNode.new( [text_to_translate], @key_store, "t '.", "'", [], false )
|
58
|
+
@translation_store.add_translation( call_node.key_value, call_node.original_text )
|
59
|
+
call_node
|
60
|
+
end
|
61
|
+
|
62
|
+
# This takes in a text node and returns one or more nodes that will
|
63
|
+
# then be output. The nodes that are output should implement
|
64
|
+
# node_name and text_value
|
65
|
+
def add_html_text( text_node )
|
66
|
+
@current_text << text_node
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def add_non_text( non_text_node )
|
71
|
+
@current_text << non_text_node
|
72
|
+
end
|
73
|
+
|
74
|
+
# This is called when text extraction has finished
|
75
|
+
def completed_text_extraction
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
|
80
|
+
def strip_ending_whitespace_nodes( node_list )
|
81
|
+
whitespace = []
|
82
|
+
while( !node_list.empty? && node_list.last.white_space? )
|
83
|
+
whitespace << node_list.pop
|
84
|
+
end
|
85
|
+
return whitespace, node_list
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class RailsTranslationStore < BaseTranslationStore
|
2
|
+
|
3
|
+
def initialize( language = "en" )
|
4
|
+
super()
|
5
|
+
@language = language
|
6
|
+
end
|
7
|
+
|
8
|
+
# This goes out of it's way to keep things in exactly the correct
|
9
|
+
# order. This makes it easier for translators when dealing with
|
10
|
+
# files. This is the reason to not just throw things into a hash
|
11
|
+
# and serialize the hash
|
12
|
+
def serialize
|
13
|
+
to_return = "#{@language}:\n"
|
14
|
+
last_context = nil
|
15
|
+
self.each do |context, key, value|
|
16
|
+
whitespace_depth = 2
|
17
|
+
context_array = context.split('/')
|
18
|
+
context_array.each do |split_context|
|
19
|
+
to_return << "#{build_whitespace( whitespace_depth )}#{split_context}:\n"
|
20
|
+
whitespace_depth += 2
|
21
|
+
end unless context == last_context
|
22
|
+
to_return << "#{build_whitespace( 2 + 2 * context_array.length )}#{key}: \"#{escape(value)}\"\n"
|
23
|
+
last_context = context
|
24
|
+
end
|
25
|
+
|
26
|
+
to_return.chomp
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def build_whitespace( amount )
|
32
|
+
" " * amount
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
|
38
|
+
def escape( key )
|
39
|
+
key.gsub( /"/, '\"' ).gsub( "\n", "\\n" )
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# This is only meant to be a base class, please extend and implement
|
2
|
+
# the base methods if you want to do anything interesting
|
3
|
+
class TextExtractor
|
4
|
+
|
5
|
+
# This is called when text extraction has begun
|
6
|
+
def starting_text_extraction
|
7
|
+
raise "Please implement me (start_text_extraction)"
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_html_text( text_node )
|
11
|
+
raise "Please implement me (add_html_text)"
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_variable( variable_name, variable_value )
|
15
|
+
raise "Please implement me (add_variable)"
|
16
|
+
end
|
17
|
+
|
18
|
+
def translate_text( text_node_to_translate )
|
19
|
+
raise "Please impelement me (translate_text)"
|
20
|
+
end
|
21
|
+
|
22
|
+
def end_html_text
|
23
|
+
raise "Please implement me (end_html_text)"
|
24
|
+
end
|
25
|
+
|
26
|
+
def start_html_text
|
27
|
+
raise "Please implement me (start_html_text)"
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module TextNode
|
2
|
+
include BaseNode
|
3
|
+
include NodeProcessing
|
4
|
+
|
5
|
+
def extract_text( text_extractor, node_tree )
|
6
|
+
if( self.strip_whitespace? && self.has_leading_or_trailing_whitespace? )
|
7
|
+
self.remove_leading_and_trailing_whitespace.each do |node|
|
8
|
+
node.extract_text( text_extractor, node_tree )
|
9
|
+
end
|
10
|
+
else
|
11
|
+
text_extractor.add_html_text( self )
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def has_variables?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def html?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def strip_whitespace?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def text?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def top_level?
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def white_space?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def amount_of_ending_whitespace
|
41
|
+
self.text_value.length - self.text_value.rstrip.length
|
42
|
+
end
|
43
|
+
|
44
|
+
def amount_of_starting_whitespace
|
45
|
+
self.text_value.length - self.text_value.lstrip.length
|
46
|
+
end
|
47
|
+
|
48
|
+
def has_leading_or_trailing_whitespace?
|
49
|
+
amount_of_ending_whitespace > 0 || amount_of_starting_whitespace > 0
|
50
|
+
end
|
51
|
+
|
52
|
+
# This probably won't work, because I want it to retain all of the
|
53
|
+
# nodes. Maybe we should run this after the text extraction
|
54
|
+
# happens? Nope, that won't work....
|
55
|
+
def remove_leading_and_trailing_whitespace
|
56
|
+
to_return = []
|
57
|
+
if( self.text_value.strip.empty? ) # is this only whitespace
|
58
|
+
to_return << HerbWhiteSpaceTextNode.new( self.text_value )
|
59
|
+
else # otherwise it has some text
|
60
|
+
if( amount_of_starting_whitespace > 0 )
|
61
|
+
to_return << HerbWhiteSpaceTextNode.new( self.text_value[0..(amount_of_starting_whitespace - 1 )] )
|
62
|
+
end
|
63
|
+
to_return << HerbTextNode.new( self.text_value.strip )
|
64
|
+
if( amount_of_ending_whitespace > 0 )
|
65
|
+
to_return << HerbWhiteSpaceTextNode.new( self.text_value[(self.text_value.length - amount_of_ending_whitespace)..self.text_value.length ])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
to_return
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
end
|
data/lib/herbgobbler.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
class HerbCombinedNode
|
2
|
+
|
3
|
+
def initialize( node_a, node_b )
|
4
|
+
@combined_nodes = [node_a, node_b]
|
5
|
+
end
|
6
|
+
|
7
|
+
def node_name
|
8
|
+
"herb_combined_nodes"
|
9
|
+
end
|
10
|
+
|
11
|
+
def can_be_combined?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def should_be_unrolled?
|
16
|
+
@combined_nodes.last.white_space?
|
17
|
+
end
|
18
|
+
|
19
|
+
def html?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def text?
|
24
|
+
@combined_nodes.first.text? || @combined_nodes.last.text?
|
25
|
+
end
|
26
|
+
|
27
|
+
def text_value
|
28
|
+
to_return = "";
|
29
|
+
@combined_nodes.each do |node|
|
30
|
+
to_return << node.text_value
|
31
|
+
end
|
32
|
+
to_return
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
self.text_value
|
37
|
+
end
|
38
|
+
|
39
|
+
def whitespace?
|
40
|
+
@combined_nodes.first.whitespace? || @combined_nodes.last.whitespace?
|
41
|
+
end
|
42
|
+
|
43
|
+
def unroll
|
44
|
+
@combined_nodes
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class HerbErbTextCallNode
|
2
|
+
|
3
|
+
def initialize( text_values = [], key_store = [], prepend = nil, postpend = nil, variables = [], include_markup = true )
|
4
|
+
@text_values = text_values
|
5
|
+
@prepend = prepend
|
6
|
+
@postpend = postpend
|
7
|
+
@key_store = key_store
|
8
|
+
@key_value = nil
|
9
|
+
@include_markup = include_markup
|
10
|
+
@variables = variables
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_text( text )
|
14
|
+
@text_values << text
|
15
|
+
end
|
16
|
+
|
17
|
+
def can_be_combined?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def key_value
|
22
|
+
if( @key_value.nil? )
|
23
|
+
@key_value = I18nKey.new( original_text, @key_store ).key_value
|
24
|
+
end
|
25
|
+
|
26
|
+
@key_value
|
27
|
+
end
|
28
|
+
|
29
|
+
def node_name
|
30
|
+
"herb_erb_text_call_node"
|
31
|
+
end
|
32
|
+
|
33
|
+
def original_text
|
34
|
+
@text_values.join( '' )
|
35
|
+
end
|
36
|
+
|
37
|
+
def text_value
|
38
|
+
string_to_return = "#{@prepend}#{key_value}#{@postpend}"
|
39
|
+
string_to_return += ", #{@variables.join( ', ' )}" unless @variables.empty?
|
40
|
+
if( @include_markup )
|
41
|
+
"<%= #{string_to_return} %>"
|
42
|
+
else
|
43
|
+
string_to_return
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
text_value
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class HerbNodeRetainingNode
|
2
|
+
|
3
|
+
def initialize( first_node = nil )
|
4
|
+
@sub_nodes = []
|
5
|
+
@sub_nodes << first_node unless first_node.nil?
|
6
|
+
end
|
7
|
+
|
8
|
+
def nodes
|
9
|
+
to_return = []
|
10
|
+
@sub_nodes.each do|node|
|
11
|
+
if( node.is_a?( HerbNodeRetainingNode ) )
|
12
|
+
to_return += node.nodes
|
13
|
+
else
|
14
|
+
to_return << node
|
15
|
+
end
|
16
|
+
end
|
17
|
+
to_return
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(node)
|
21
|
+
if( node.is_a?(Array) )
|
22
|
+
@sub_nodes+= node
|
23
|
+
else
|
24
|
+
@sub_nodes << node
|
25
|
+
end
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def text_value
|
30
|
+
to_return = ""
|
31
|
+
@sub_nodes.each do |node|
|
32
|
+
to_return += node.text_value
|
33
|
+
end
|
34
|
+
to_return
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class HerbNodeRetainingNonTextNode < HerbNodeRetainingNode
|
2
|
+
include NonTextNode
|
3
|
+
|
4
|
+
def initialize( first_node = nil )
|
5
|
+
super( first_node )
|
6
|
+
@can_be_combined = false
|
7
|
+
end
|
8
|
+
|
9
|
+
def <<(node)
|
10
|
+
super(node)
|
11
|
+
@can_be_combined ||= ( node.respond_to?( :can_be_combined? ) && node.can_be_combined? )
|
12
|
+
end
|
13
|
+
|
14
|
+
def can_be_combined?
|
15
|
+
@can_be_combined
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.create_from_nodes( nodes )
|
19
|
+
to_return = HerbNodeRetainingNonTextNode.new
|
20
|
+
nodes.each do |node|
|
21
|
+
to_return << node
|
22
|
+
end
|
23
|
+
to_return
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
end
|