sablon 0.0.21 → 0.0.22
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/.travis.yml +4 -3
- data/Gemfile.lock +9 -9
- data/README.md +120 -11
- data/lib/sablon.rb +7 -1
- data/lib/sablon/configuration/configuration.rb +165 -0
- data/lib/sablon/configuration/html_tag.rb +99 -0
- data/lib/sablon/content.rb +12 -9
- data/lib/sablon/context.rb +27 -20
- data/lib/sablon/environment.rb +31 -0
- data/lib/sablon/html/ast.rb +290 -75
- data/lib/sablon/html/ast_builder.rb +90 -0
- data/lib/sablon/html/converter.rb +3 -123
- data/lib/sablon/numbering.rb +0 -5
- data/lib/sablon/operations.rb +11 -11
- data/lib/sablon/parser/mail_merge.rb +7 -6
- data/lib/sablon/processor/document.rb +9 -9
- data/lib/sablon/processor/numbering.rb +4 -4
- data/lib/sablon/template.rb +5 -4
- data/lib/sablon/version.rb +1 -1
- data/sablon.gemspec +3 -3
- data/test/configuration_test.rb +122 -0
- data/test/content_test.rb +7 -6
- data/test/context_test.rb +11 -11
- data/test/environment_test.rb +27 -0
- data/test/expression_test.rb +2 -2
- data/test/fixtures/html/html_test_content.html +174 -0
- data/test/fixtures/html_sample.docx +0 -0
- data/test/fixtures/xml/comment_block_and_comment_as_key.xml +31 -0
- data/test/html/ast_builder_test.rb +65 -0
- data/test/html/ast_test.rb +117 -0
- data/test/html/converter_test.rb +386 -87
- data/test/html/node_properties_test.rb +113 -0
- data/test/html_test.rb +10 -10
- data/test/mail_merge_parser_test.rb +3 -2
- data/test/processor/document_test.rb +20 -2
- data/test/section_properties_test.rb +1 -1
- data/test/support/html_snippets.rb +9 -0
- data/test/test_helper.rb +0 -1
- metadata +27 -7
Binary file
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<w:r><w:t xml:space="preserve">Before </w:t></w:r>
|
2
|
+
<w:p>
|
3
|
+
<w:fldSimple w:instr=" MERGEFIELD comment \* MERGEFORMAT ">
|
4
|
+
<w:r>
|
5
|
+
<w:rPr><w:noProof/></w:rPr>
|
6
|
+
<w:t>«comment»</w:t>
|
7
|
+
</w:r>
|
8
|
+
</w:fldSimple>
|
9
|
+
</w:p>
|
10
|
+
<w:p>
|
11
|
+
<w:r>
|
12
|
+
<w:t>Inside Comment! </w:t>
|
13
|
+
</w:r>
|
14
|
+
</w:p>
|
15
|
+
<w:p>
|
16
|
+
<w:fldSimple w:instr=" MERGEFIELD endComment \* MERGEFORMAT ">
|
17
|
+
<w:r>
|
18
|
+
<w:rPr><w:noProof/></w:rPr>
|
19
|
+
<w:t>«endComment»</w:t>
|
20
|
+
</w:r>
|
21
|
+
</w:fldSimple>
|
22
|
+
</w:p>
|
23
|
+
<w:p>
|
24
|
+
<w:fldSimple w:instr=" MERGEFIELD =comment \* MERGEFORMAT ">
|
25
|
+
<w:r w:rsidR="004B49F0">
|
26
|
+
<w:rPr><w:noProof/></w:rPr>
|
27
|
+
<w:t>«=comment»</w:t>
|
28
|
+
</w:r>
|
29
|
+
</w:fldSimple>
|
30
|
+
</w:p>
|
31
|
+
<w:r><w:t xml:space="preserve">After </w:t></w:r>
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "test_helper"
|
3
|
+
|
4
|
+
# Tests some low level private methods in the ASTBuilder class. #process_nodes
|
5
|
+
# and self.html_to_ast are covered extensively in converter_test.rb
|
6
|
+
class HTMLConverterASTBuilderTest < Sablon::TestCase
|
7
|
+
def setup
|
8
|
+
super
|
9
|
+
@env = Sablon::Environment.new(nil)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_fetch_tag
|
13
|
+
@bulider = new_builder
|
14
|
+
tag = Sablon::Configuration.instance.permitted_html_tags[:span]
|
15
|
+
assert_equal @bulider.send(:fetch_tag, :span), tag
|
16
|
+
# check that strings are converted into symbols
|
17
|
+
assert_equal @bulider.send(:fetch_tag, 'span'), tag
|
18
|
+
# test uknown tag raises error
|
19
|
+
e = assert_raises ArgumentError do
|
20
|
+
@bulider.send(:fetch_tag, :unknown_tag)
|
21
|
+
end
|
22
|
+
assert_equal "Don't know how to handle HTML tag: unknown_tag", e.message
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_validate_structure
|
26
|
+
@bulider = new_builder
|
27
|
+
root = Sablon::Configuration.instance.permitted_html_tags['#document-fragment'.to_sym]
|
28
|
+
div = Sablon::Configuration.instance.permitted_html_tags[:div]
|
29
|
+
span = Sablon::Configuration.instance.permitted_html_tags[:span]
|
30
|
+
# test valid relationship
|
31
|
+
assert_nil @bulider.send(:validate_structure, div, span)
|
32
|
+
# test inverted relationship
|
33
|
+
e = assert_raises ArgumentError do
|
34
|
+
@bulider.send(:validate_structure, span, div)
|
35
|
+
end
|
36
|
+
assert_equal "Invalid HTML structure: div is not a valid child element of span.", e.message
|
37
|
+
# test inline tag with no parent
|
38
|
+
e = assert_raises ArgumentError do
|
39
|
+
@bulider.send(:validate_structure, root, span)
|
40
|
+
end
|
41
|
+
assert_equal "Invalid HTML structure: span needs to be wrapped in a block level tag.", e.message
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_merge_properties
|
45
|
+
@builder = new_builder
|
46
|
+
node = Nokogiri::HTML.fragment('<span style="color: #F00; text-decoration: underline wavy">Test</span>').children[0]
|
47
|
+
tag = Struct.new(:properties).new(rStyle: 'Normal')
|
48
|
+
# test that properties are merged across all three arguments
|
49
|
+
props = @builder.send(:merge_node_properties, node, tag, 'background-color' => '#00F')
|
50
|
+
assert_equal({ 'background-color' => '#00F', rStyle: 'Normal', 'color' => '#F00', 'text-decoration' => 'underline wavy' }, props)
|
51
|
+
# test that parent properties are overriden by tag properties
|
52
|
+
props = @builder.send(:merge_node_properties, node, tag, rStyle: 'Citation', 'background-color' => '#00F')
|
53
|
+
assert_equal({ 'background-color' => '#00F', rStyle: 'Normal', 'color' => '#F00', 'text-decoration' => 'underline wavy' }, props)
|
54
|
+
# test that inline properties override parent styles
|
55
|
+
node = Nokogiri::HTML.fragment('<span style="color: #F00">Test</span>').children[0]
|
56
|
+
props = @builder.send(:merge_node_properties, node, tag, 'color' => '#00F')
|
57
|
+
assert_equal({ rStyle: 'Normal', 'color' => '#F00' }, props)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def new_builder(nodes = [], properties = {})
|
63
|
+
Sablon::HTMLConverter::ASTBuilder.new(@env, nodes, properties)
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "test_helper"
|
3
|
+
|
4
|
+
class HTMLConverterASTTest < Sablon::TestCase
|
5
|
+
def setup
|
6
|
+
super
|
7
|
+
@converter = Sablon::HTMLConverter.new
|
8
|
+
@converter.instance_variable_set(:@env, Sablon::Environment.new(nil))
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_div
|
12
|
+
input = '<div>Lorem ipsum dolor sit amet</div>'
|
13
|
+
ast = @converter.processed_ast(input)
|
14
|
+
assert_equal '<Root: [<Paragraph{Normal}: [<Run{}: Lorem ipsum dolor sit amet>]>]>', ast.inspect
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_p
|
18
|
+
input = '<p>Lorem ipsum dolor sit amet</p>'
|
19
|
+
ast = @converter.processed_ast(input)
|
20
|
+
assert_equal '<Root: [<Paragraph{Paragraph}: [<Run{}: Lorem ipsum dolor sit amet>]>]>', ast.inspect
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_b
|
24
|
+
input = '<p>Lorem <b>ipsum dolor sit amet</b></p>'
|
25
|
+
ast = @converter.processed_ast(input)
|
26
|
+
assert_equal '<Root: [<Paragraph{Paragraph}: [<Run{}: Lorem >, <Run{b}: ipsum dolor sit amet>]>]>', ast.inspect
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_i
|
30
|
+
input = '<p>Lorem <i>ipsum dolor sit amet</i></p>'
|
31
|
+
ast = @converter.processed_ast(input)
|
32
|
+
assert_equal '<Root: [<Paragraph{Paragraph}: [<Run{}: Lorem >, <Run{i}: ipsum dolor sit amet>]>]>', ast.inspect
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_br_in_strong
|
36
|
+
input = '<div><strong>Lorem<br />ipsum<br />dolor</strong></div>'
|
37
|
+
par = @converter.processed_ast(input).grep(Sablon::HTMLConverter::Paragraph).first
|
38
|
+
assert_equal "[<Run{b}: Lorem>, <Newline>, <Run{b}: ipsum>, <Newline>, <Run{b}: dolor>]", par.runs.inspect
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_br_in_em
|
42
|
+
input = '<div><em>Lorem<br />ipsum<br />dolor</em></div>'
|
43
|
+
par = @converter.processed_ast(input).grep(Sablon::HTMLConverter::Paragraph).first
|
44
|
+
assert_equal "[<Run{i}: Lorem>, <Newline>, <Run{i}: ipsum>, <Newline>, <Run{i}: dolor>]", par.runs.inspect
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_nested_strong_and_em
|
48
|
+
input = '<div><strong>Lorem <em>ipsum</em> dolor</strong></div>'
|
49
|
+
par = @converter.processed_ast(input).grep(Sablon::HTMLConverter::Paragraph).first
|
50
|
+
assert_equal "[<Run{b}: Lorem >, <Run{b;i}: ipsum>, <Run{b}: dolor>]", par.runs.inspect
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_ignore_last_br_in_div
|
54
|
+
input = '<div>Lorem ipsum dolor sit amet<br /></div>'
|
55
|
+
par = @converter.processed_ast(input).grep(Sablon::HTMLConverter::Paragraph).first
|
56
|
+
assert_equal "[<Run{}: Lorem ipsum dolor sit amet>]", par.runs.inspect
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_ignore_br_in_blank_div
|
60
|
+
input = '<div><br /></div>'
|
61
|
+
par = @converter.processed_ast(input).grep(Sablon::HTMLConverter::Paragraph).first
|
62
|
+
assert_equal "[]", par.runs.inspect
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_headings
|
66
|
+
input = '<h1>First</h1><h2>Second</h2><h3>Third</h3>'
|
67
|
+
ast = @converter.processed_ast(input)
|
68
|
+
assert_equal "<Root: [<Paragraph{Heading1}: [<Run{}: First>]>, <Paragraph{Heading2}: [<Run{}: Second>]>, <Paragraph{Heading3}: [<Run{}: Third>]>]>", ast.inspect
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_h_with_formatting
|
72
|
+
input = '<h1><strong>Lorem</strong> ipsum dolor <em>sit <u>amet</u></em></h1>'
|
73
|
+
ast = @converter.processed_ast(input)
|
74
|
+
assert_equal "<Root: [<Paragraph{Heading1}: [<Run{b}: Lorem>, <Run{}: ipsum dolor >, <Run{i}: sit >, <Run{i;u=single}: amet>]>]>", ast.inspect
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_ul
|
78
|
+
input = '<ul><li>Lorem</li><li>ipsum</li></ul>'
|
79
|
+
ast = @converter.processed_ast(input)
|
80
|
+
assert_equal "<Root: [<List: [<Paragraph{ListBullet}: [<Run{}: Lorem>]>, <Paragraph{ListBullet}: [<Run{}: ipsum>]>]>]>", ast.inspect
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_ol
|
84
|
+
input = '<ol><li>Lorem</li><li>ipsum</li></ol>'
|
85
|
+
ast = @converter.processed_ast(input)
|
86
|
+
assert_equal "<Root: [<List: [<Paragraph{ListNumber}: [<Run{}: Lorem>]>, <Paragraph{ListNumber}: [<Run{}: ipsum>]>]>]>", ast.inspect
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_num_id
|
90
|
+
ast = @converter.processed_ast('<ol><li>Some</li><li>Lorem</li></ol><ul><li>ipsum</li></ul><ol><li>dolor</li><li>sit</li></ol>')
|
91
|
+
assert_equal %w[1001 1001 1002 1003 1003], get_numpr_prop_from_ast(ast, :numId)
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_nested_lists_have_the_same_numid
|
95
|
+
ast = @converter.processed_ast('<ul><li>Lorem<ul><li>ipsum<ul><li>dolor</li></ul></li></ul></li></ul>')
|
96
|
+
assert_equal %w[1001 1001 1001], get_numpr_prop_from_ast(ast, :numId)
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_keep_nested_list_order
|
100
|
+
input = '<ul><li>1<ul><li>1.1<ul><li>1.1.1</li></ul></li><li>1.2</li></ul></li><li>2<ul><li>1.3<ul><li>1.3.1</li></ul></li></ul></li></ul>'
|
101
|
+
ast = @converter.processed_ast(input)
|
102
|
+
assert_equal %w[1001], get_numpr_prop_from_ast(ast, :numId).uniq
|
103
|
+
assert_equal %w[0 1 2 1 0 1 2], get_numpr_prop_from_ast(ast, :ilvl)
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# returns the numid attribute from paragraphs
|
109
|
+
def get_numpr_prop_from_ast(ast, key)
|
110
|
+
values = []
|
111
|
+
ast.grep(Sablon::HTMLConverter::ListParagraph).each do |para|
|
112
|
+
numpr = para.instance_variable_get('@properties')[:numPr]
|
113
|
+
numpr.each { |val| values.push(val[key]) if val[key] }
|
114
|
+
end
|
115
|
+
values
|
116
|
+
end
|
117
|
+
end
|
data/test/html/converter_test.rb
CHANGED
@@ -4,6 +4,8 @@ require "test_helper"
|
|
4
4
|
class HTMLConverterTest < Sablon::TestCase
|
5
5
|
def setup
|
6
6
|
super
|
7
|
+
@env = Sablon::Environment.new(nil)
|
8
|
+
@numbering = @env.numbering
|
7
9
|
@converter = Sablon::HTMLConverter.new
|
8
10
|
end
|
9
11
|
|
@@ -15,7 +17,7 @@ class HTMLConverterTest < Sablon::TestCase
|
|
15
17
|
<w:r><w:t xml:space="preserve">Lorem ipsum dolor sit amet</w:t></w:r>
|
16
18
|
</w:p>
|
17
19
|
DOCX
|
18
|
-
assert_equal normalize_wordml(expected_output),
|
20
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
19
21
|
end
|
20
22
|
|
21
23
|
def test_convert_text_inside_p
|
@@ -26,7 +28,7 @@ DOCX
|
|
26
28
|
<w:r><w:t xml:space="preserve">Lorem ipsum dolor sit amet</w:t></w:r>
|
27
29
|
</w:p>
|
28
30
|
DOCX
|
29
|
-
assert_equal normalize_wordml(expected_output),
|
31
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
30
32
|
end
|
31
33
|
|
32
34
|
def test_convert_text_inside_multiple_divs
|
@@ -41,7 +43,7 @@ DOCX
|
|
41
43
|
<w:r><w:t xml:space="preserve">dolor sit amet</w:t></w:r>
|
42
44
|
</w:p>
|
43
45
|
DOCX
|
44
|
-
assert_equal normalize_wordml(expected_output),
|
46
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
45
47
|
end
|
46
48
|
|
47
49
|
def test_convert_newline_inside_div
|
@@ -54,7 +56,7 @@ DOCX
|
|
54
56
|
<w:r><w:t xml:space="preserve">dolor sit amet</w:t></w:r>
|
55
57
|
</w:p>
|
56
58
|
DOCX
|
57
|
-
assert_equal normalize_wordml(expected_output),
|
59
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
58
60
|
end
|
59
61
|
|
60
62
|
def test_convert_strong_tags_inside_div
|
@@ -67,23 +69,36 @@ DOCX
|
|
67
69
|
<w:r><w:t xml:space="preserve"> sit amet</w:t></w:r>
|
68
70
|
</w:p>
|
69
71
|
DOCX
|
70
|
-
assert_equal normalize_wordml(expected_output),
|
72
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_convert_span_tags_inside_p
|
76
|
+
input = '<p>Lorem <span>ipsum dolor</span> sit amet</p>'
|
77
|
+
expected_output = <<-DOCX.strip
|
78
|
+
<w:p>
|
79
|
+
<w:pPr><w:pStyle w:val="Paragraph" /></w:pPr>
|
80
|
+
<w:r><w:t xml:space="preserve">Lorem </w:t></w:r>
|
81
|
+
<w:r><w:t xml:space="preserve">ipsum dolor</w:t></w:r>
|
82
|
+
<w:r><w:t xml:space="preserve"> sit amet</w:t></w:r></w:p>
|
83
|
+
DOCX
|
84
|
+
|
85
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
71
86
|
end
|
72
87
|
|
73
88
|
def test_convert_u_tags_inside_p
|
74
|
-
input = '<p>Lorem <u>ipsum dolor</u> sit amet</
|
89
|
+
input = '<p>Lorem <u>ipsum dolor</u> sit amet</p>'
|
75
90
|
expected_output = <<-DOCX.strip
|
76
91
|
<w:p>
|
77
92
|
<w:pPr><w:pStyle w:val="Paragraph" /></w:pPr>
|
78
93
|
<w:r><w:t xml:space="preserve">Lorem </w:t></w:r>
|
79
94
|
<w:r>
|
80
|
-
<w:rPr><w:u w:val="single"/></w:rPr>
|
95
|
+
<w:rPr><w:u w:val="single" /></w:rPr>
|
81
96
|
<w:t xml:space="preserve">ipsum dolor</w:t>
|
82
97
|
</w:r>
|
83
98
|
<w:r><w:t xml:space="preserve"> sit amet</w:t></w:r>
|
84
99
|
</w:p>
|
85
100
|
DOCX
|
86
|
-
assert_equal normalize_wordml(expected_output),
|
101
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
87
102
|
end
|
88
103
|
|
89
104
|
def test_convert_em_tags_inside_div
|
@@ -96,7 +111,55 @@ DOCX
|
|
96
111
|
<w:r><w:t xml:space="preserve"> sit amet</w:t></w:r>
|
97
112
|
</w:p>
|
98
113
|
DOCX
|
99
|
-
assert_equal normalize_wordml(expected_output),
|
114
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_convert_s_tags_inside_p
|
118
|
+
input = '<p>Lorem <s>ipsum dolor</s> sit amet</p>'
|
119
|
+
expected_output = <<-DOCX.strip
|
120
|
+
<w:p>
|
121
|
+
<w:pPr><w:pStyle w:val="Paragraph" /></w:pPr>
|
122
|
+
<w:r><w:t xml:space="preserve">Lorem </w:t></w:r>
|
123
|
+
<w:r>
|
124
|
+
<w:rPr><w:strike w:val="true" /></w:rPr>
|
125
|
+
<w:t xml:space="preserve">ipsum dolor</w:t>
|
126
|
+
</w:r>
|
127
|
+
<w:r><w:t xml:space="preserve"> sit amet</w:t></w:r>
|
128
|
+
</w:p>
|
129
|
+
DOCX
|
130
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_convert_sub_tags_inside_p
|
134
|
+
input = '<p>Lorem <sub>ipsum dolor</sub> sit amet</p>'
|
135
|
+
expected_output = <<-DOCX.strip
|
136
|
+
<w:p>
|
137
|
+
<w:pPr><w:pStyle w:val="Paragraph" /></w:pPr>
|
138
|
+
<w:r><w:t xml:space="preserve">Lorem </w:t></w:r>
|
139
|
+
<w:r>
|
140
|
+
<w:rPr><w:vertAlign w:val="subscript" /></w:rPr>
|
141
|
+
<w:t xml:space="preserve">ipsum dolor</w:t>
|
142
|
+
</w:r>
|
143
|
+
<w:r><w:t xml:space="preserve"> sit amet</w:t></w:r>
|
144
|
+
</w:p>
|
145
|
+
DOCX
|
146
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
147
|
+
end
|
148
|
+
|
149
|
+
def test_convert_sup_tags_inside_p
|
150
|
+
input = '<p>Lorem <sup>ipsum dolor</sup> sit amet</p>'
|
151
|
+
expected_output = <<-DOCX.strip
|
152
|
+
<w:p>
|
153
|
+
<w:pPr><w:pStyle w:val="Paragraph" /></w:pPr>
|
154
|
+
<w:r><w:t xml:space="preserve">Lorem </w:t></w:r>
|
155
|
+
<w:r>
|
156
|
+
<w:rPr><w:vertAlign w:val="superscript" /></w:rPr>
|
157
|
+
<w:t xml:space="preserve">ipsum dolor</w:t>
|
158
|
+
</w:r>
|
159
|
+
<w:r><w:t xml:space="preserve"> sit amet</w:t></w:r>
|
160
|
+
</w:p>
|
161
|
+
DOCX
|
162
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
100
163
|
end
|
101
164
|
|
102
165
|
def test_convert_br_tags_inside_strong
|
@@ -115,7 +178,7 @@ DOCX
|
|
115
178
|
</w:r>
|
116
179
|
</w:p>
|
117
180
|
DOCX
|
118
|
-
assert_equal normalize_wordml(expected_output),
|
181
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
119
182
|
end
|
120
183
|
|
121
184
|
def test_convert_h1
|
@@ -126,7 +189,7 @@ DOCX
|
|
126
189
|
<w:r><w:t xml:space="preserve">Lorem ipsum dolor</w:t></w:r>
|
127
190
|
</w:p>
|
128
191
|
DOCX
|
129
|
-
assert_equal normalize_wordml(expected_output),
|
192
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
130
193
|
end
|
131
194
|
|
132
195
|
def test_unorderd_lists
|
@@ -165,9 +228,9 @@ DOCX
|
|
165
228
|
<w:r><w:t xml:space="preserve">dolor</w:t></w:r>
|
166
229
|
</w:p>
|
167
230
|
DOCX
|
168
|
-
assert_equal normalize_wordml(expected_output),
|
231
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
169
232
|
|
170
|
-
assert_equal [Sablon::Numbering::Definition.new(1001, 'ListBullet')],
|
233
|
+
assert_equal [Sablon::Numbering::Definition.new(1001, 'ListBullet')], @numbering.definitions
|
171
234
|
end
|
172
235
|
|
173
236
|
def test_ordered_lists
|
@@ -206,9 +269,9 @@ DOCX
|
|
206
269
|
<w:r><w:t xml:space="preserve">dolor</w:t></w:r>
|
207
270
|
</w:p>
|
208
271
|
DOCX
|
209
|
-
assert_equal normalize_wordml(expected_output),
|
272
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
210
273
|
|
211
|
-
assert_equal [Sablon::Numbering::Definition.new(1001, 'ListNumber')],
|
274
|
+
assert_equal [Sablon::Numbering::Definition.new(1001, 'ListNumber')], @numbering.definitions
|
212
275
|
end
|
213
276
|
|
214
277
|
def test_mixed_lists
|
@@ -247,11 +310,11 @@ DOCX
|
|
247
310
|
<w:r><w:t xml:space="preserve">dolor</w:t></w:r>
|
248
311
|
</w:p>
|
249
312
|
DOCX
|
250
|
-
assert_equal normalize_wordml(expected_output),
|
313
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
251
314
|
|
252
315
|
assert_equal [Sablon::Numbering::Definition.new(1001, 'ListNumber'),
|
253
316
|
Sablon::Numbering::Definition.new(1002, 'ListBullet'),
|
254
|
-
Sablon::Numbering::Definition.new(1003, 'ListNumber')],
|
317
|
+
Sablon::Numbering::Definition.new(1003, 'ListNumber')], @numbering.definitions
|
255
318
|
end
|
256
319
|
|
257
320
|
def test_nested_unordered_lists
|
@@ -290,116 +353,352 @@ DOCX
|
|
290
353
|
<w:r><w:t xml:space="preserve">dolor</w:t></w:r>
|
291
354
|
</w:p>
|
292
355
|
DOCX
|
293
|
-
assert_equal normalize_wordml(expected_output),
|
356
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
357
|
+
|
358
|
+
assert_equal [Sablon::Numbering::Definition.new(1001, 'ListBullet')], @numbering.definitions
|
359
|
+
end
|
294
360
|
|
295
|
-
|
361
|
+
def test_unknown_tag
|
362
|
+
e = assert_raises ArgumentError do
|
363
|
+
process('<badtag/>')
|
364
|
+
end
|
365
|
+
assert_match(/Don't know how to handle HTML tag:/, e.message)
|
296
366
|
end
|
297
367
|
|
298
368
|
private
|
369
|
+
|
370
|
+
def process(input)
|
371
|
+
@converter.process(input, @env)
|
372
|
+
end
|
373
|
+
|
299
374
|
def normalize_wordml(wordml)
|
300
375
|
wordml.gsub(/^\s+/, '').tr("\n", '')
|
301
376
|
end
|
302
377
|
end
|
303
378
|
|
304
|
-
class
|
379
|
+
class HTMLConverterStyleTest < Sablon::TestCase
|
305
380
|
def setup
|
306
381
|
super
|
382
|
+
@env = Sablon::Environment.new(nil)
|
307
383
|
@converter = Sablon::HTMLConverter.new
|
308
384
|
end
|
309
385
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
386
|
+
# testing direct CSS style -> WordML conversion for paragraphs
|
387
|
+
|
388
|
+
def test_paragraph_with_background_color
|
389
|
+
input = '<p style="background-color: #123456"></p>'
|
390
|
+
expected_output = para_with_ppr('<w:shd w:val="clear" w:fill="123456" />')
|
391
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
392
|
+
end
|
393
|
+
|
394
|
+
def test_paragraph_with_borders
|
395
|
+
# Basic single line black border
|
396
|
+
input = '<p style="border: 1px"></p>'
|
397
|
+
ppr = <<-DOCX.strip
|
398
|
+
<w:pBdr>
|
399
|
+
<w:top w:sz="2" w:val="single" w:color="000000" />
|
400
|
+
<w:bottom w:sz="2" w:val="single" w:color="000000" />
|
401
|
+
<w:left w:sz="2" w:val="single" w:color="000000" />
|
402
|
+
<w:right w:sz="2" w:val="single" w:color="000000" />
|
403
|
+
</w:pBdr>
|
404
|
+
DOCX
|
405
|
+
expected_output = para_with_ppr(ppr)
|
406
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
407
|
+
# border with a line style
|
408
|
+
input = '<p style="border: 1px wavy"></p>'
|
409
|
+
ppr = <<-DOCX.strip
|
410
|
+
<w:pBdr>
|
411
|
+
<w:top w:sz="2" w:val="wavy" w:color="000000" />
|
412
|
+
<w:bottom w:sz="2" w:val="wavy" w:color="000000" />
|
413
|
+
<w:left w:sz="2" w:val="wavy" w:color="000000" />
|
414
|
+
<w:right w:sz="2" w:val="wavy" w:color="000000" />
|
415
|
+
</w:pBdr>
|
416
|
+
DOCX
|
417
|
+
expected_output = para_with_ppr(ppr)
|
418
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
419
|
+
# border with line style and color
|
420
|
+
input = '<p style="border: 1px wavy #123456"></p>'
|
421
|
+
ppr = <<-DOCX.strip
|
422
|
+
<w:pBdr>
|
423
|
+
<w:top w:sz="2" w:val="wavy" w:color="123456" />
|
424
|
+
<w:bottom w:sz="2" w:val="wavy" w:color="123456" />
|
425
|
+
<w:left w:sz="2" w:val="wavy" w:color="123456" />
|
426
|
+
<w:right w:sz="2" w:val="wavy" w:color="123456" />
|
427
|
+
</w:pBdr>
|
428
|
+
DOCX
|
429
|
+
expected_output = para_with_ppr(ppr)
|
430
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
431
|
+
end
|
432
|
+
|
433
|
+
def test_paragraph_with_text_align
|
434
|
+
input = '<p style="text-align: both"></p>'
|
435
|
+
expected_output = para_with_ppr('<w:jc w:val="both" />')
|
436
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
437
|
+
end
|
315
438
|
|
316
|
-
def
|
317
|
-
input = '<p
|
318
|
-
|
319
|
-
assert_equal
|
439
|
+
def test_paragraph_with_vertical_align
|
440
|
+
input = '<p style="vertical-align: baseline"></p>'
|
441
|
+
expected_output = para_with_ppr('<w:textAlignment w:val="baseline" />')
|
442
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
320
443
|
end
|
321
444
|
|
322
|
-
def
|
323
|
-
input = '<p
|
324
|
-
|
325
|
-
assert_equal
|
445
|
+
def test_paragraph_with_unsupported_property
|
446
|
+
input = '<p style="unsupported: true"></p>'
|
447
|
+
expected_output = para_with_ppr('')
|
448
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
326
449
|
end
|
327
450
|
|
328
|
-
def
|
329
|
-
input = '<p
|
330
|
-
|
331
|
-
assert_equal
|
451
|
+
def test_run_with_background_color
|
452
|
+
input = '<p><span style="background-color: #123456">test</span></p>'
|
453
|
+
expected_output = run_with_rpr('<w:shd w:val="clear" w:fill="123456" />')
|
454
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
332
455
|
end
|
333
456
|
|
334
|
-
def
|
335
|
-
input = '<
|
336
|
-
|
337
|
-
assert_equal
|
457
|
+
def test_run_with_color
|
458
|
+
input = '<p><span style="color: #123456">test</span></p>'
|
459
|
+
expected_output = run_with_rpr('<w:color w:val="123456" />')
|
460
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
338
461
|
end
|
339
462
|
|
340
|
-
def
|
341
|
-
input = '<
|
342
|
-
|
343
|
-
assert_equal
|
344
|
-
end
|
463
|
+
def test_run_with_font_size
|
464
|
+
input = '<p><span style="font-size: 20">test</span></p>'
|
465
|
+
expected_output = run_with_rpr('<w:sz w:val="40" />')
|
466
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
345
467
|
|
346
|
-
|
347
|
-
input = '<
|
348
|
-
|
349
|
-
assert_equal "[<Text{bold}: Lorem >, <Text{bold|italic}: ipsum>, <Text{bold}: dolor>]", par.runs.inspect
|
350
|
-
end
|
468
|
+
# test that non-numeric are ignored
|
469
|
+
input = '<p><span style="font-size: 20pts">test</span></p>'
|
470
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
351
471
|
|
352
|
-
|
353
|
-
input = '<
|
354
|
-
|
355
|
-
assert_equal "[<Text{}: Lorem ipsum dolor sit amet>]", par.runs.inspect
|
472
|
+
# test that floats round up
|
473
|
+
input = '<p><span style="font-size: 19.1pts">test</span></p>'
|
474
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
356
475
|
end
|
357
476
|
|
358
|
-
def
|
359
|
-
input = '<
|
360
|
-
|
361
|
-
assert_equal
|
477
|
+
def test_run_with_font_style
|
478
|
+
input = '<p><span style="font-style: bold">test</span></p>'
|
479
|
+
expected_output = run_with_rpr('<w:b />')
|
480
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
481
|
+
|
482
|
+
# test that non-numeric are ignored
|
483
|
+
input = '<p><span style="font-style: italic">test</span></p>'
|
484
|
+
expected_output = run_with_rpr('<w:i />')
|
485
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
486
|
+
end
|
487
|
+
|
488
|
+
def test_run_with_font_wieght
|
489
|
+
input = '<p><span style="font-weight: bold">test</span></p>'
|
490
|
+
expected_output = run_with_rpr('<w:b />')
|
491
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
492
|
+
end
|
493
|
+
|
494
|
+
def test_run_with_text_decoration
|
495
|
+
# testing underline configurations
|
496
|
+
input = '<p><span style="text-decoration: underline">test</span></p>'
|
497
|
+
expected_output = run_with_rpr('<w:u w:val="single" />')
|
498
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
499
|
+
|
500
|
+
input = '<p><span style="text-decoration: underline dash">test</span></p>'
|
501
|
+
expected_output = run_with_rpr('<w:u w:val="dash" w:color="auto" />')
|
502
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
503
|
+
|
504
|
+
input = '<p><span style="text-decoration: underline dash #123456">test</span></p>'
|
505
|
+
expected_output = run_with_rpr('<w:u w:val="dash" w:color="123456" />')
|
506
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
507
|
+
|
508
|
+
# testing line-through
|
509
|
+
input = '<p><span style="text-decoration: line-through">test</span></p>'
|
510
|
+
expected_output = run_with_rpr('<w:strike w:val="true" />')
|
511
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
512
|
+
|
513
|
+
# testing that unsupported values are passed through as a toggle
|
514
|
+
input = '<p><span style="text-decoration: strike">test</span></p>'
|
515
|
+
expected_output = run_with_rpr('<w:strike w:val="true" />')
|
516
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
517
|
+
|
518
|
+
input = '<p><span style="text-decoration: emboss">test</span></p>'
|
519
|
+
expected_output = run_with_rpr('<w:emboss w:val="true" />')
|
520
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
362
521
|
end
|
363
522
|
|
364
|
-
def
|
365
|
-
input = '<
|
366
|
-
|
367
|
-
assert_equal
|
523
|
+
def test_run_with_vertical_align
|
524
|
+
input = '<p><span style="vertical-align: subscript">test</span></p>'
|
525
|
+
expected_output = run_with_rpr('<w:vertAlign w:val="subscript" />')
|
526
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
527
|
+
|
528
|
+
input = '<p><span style="vertical-align: superscript">test</span></p>'
|
529
|
+
expected_output = run_with_rpr('<w:vertAlign w:val="superscript" />')
|
530
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
368
531
|
end
|
369
532
|
|
370
|
-
def
|
371
|
-
input = '<
|
372
|
-
|
373
|
-
assert_equal
|
533
|
+
def test_run_with_unsupported_property
|
534
|
+
input = '<p><span style="unsupported: true">test</span></p>'
|
535
|
+
expected_output = '<w:p><w:pPr><w:pStyle w:val="Paragraph" /></w:pPr><w:r><w:t xml:space="preserve">test</w:t></w:r></w:p>'
|
536
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
374
537
|
end
|
375
538
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
539
|
+
# tests with nested runs and styles
|
540
|
+
|
541
|
+
def test_paragraph_props_passed_to_runs
|
542
|
+
input = '<p style="color: #123456"><b>Lorem</b><span>ipsum</span></p>'
|
543
|
+
expected_output = <<-DOCX.strip
|
544
|
+
<w:p>
|
545
|
+
<w:pPr>
|
546
|
+
<w:pStyle w:val="Paragraph" />
|
547
|
+
</w:pPr>
|
548
|
+
<w:r>
|
549
|
+
<w:rPr>
|
550
|
+
<w:color w:val="123456" />
|
551
|
+
<w:b />
|
552
|
+
</w:rPr>
|
553
|
+
<w:t xml:space="preserve">Lorem</w:t>
|
554
|
+
</w:r>
|
555
|
+
<w:r>
|
556
|
+
<w:rPr>
|
557
|
+
<w:color w:val="123456" />
|
558
|
+
</w:rPr>
|
559
|
+
<w:t xml:space="preserve">ipsum</w:t>
|
560
|
+
</w:r>
|
561
|
+
</w:p>
|
562
|
+
DOCX
|
563
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
564
|
+
end
|
565
|
+
|
566
|
+
def test_run_prop_override_paragraph_prop
|
567
|
+
input = '<p style="text-align: center; color: #FF0000">Lorem<span style="color: blue;">ipsum</span></p>'
|
568
|
+
expected_output = <<-DOCX.strip
|
569
|
+
<w:p>
|
570
|
+
<w:pPr>
|
571
|
+
<w:pStyle w:val="Paragraph" />
|
572
|
+
<w:jc w:val="center" />
|
573
|
+
</w:pPr>
|
574
|
+
<w:r>
|
575
|
+
<w:rPr>
|
576
|
+
<w:color w:val="FF0000" />
|
577
|
+
</w:rPr>
|
578
|
+
<w:t xml:space="preserve">Lorem</w:t>
|
579
|
+
</w:r>
|
580
|
+
<w:r>
|
581
|
+
<w:rPr>
|
582
|
+
<w:color w:val="blue" />
|
583
|
+
</w:rPr>
|
584
|
+
<w:t xml:space="preserve">ipsum</w:t>
|
585
|
+
</w:r>
|
586
|
+
</w:p>
|
587
|
+
DOCX
|
588
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
589
|
+
end
|
590
|
+
|
591
|
+
def test_inline_style_overrides_tag_style
|
592
|
+
# Note: a toggle property can not be removed once it becomes a symbol
|
593
|
+
# unless there is a specific CSS style that will set it to false. This
|
594
|
+
# is because CSS styles can only override parent properties not remove them.
|
595
|
+
input = '<p><u style="text-decoration: underline wavyDouble">test</u></p>'
|
596
|
+
expected_output = run_with_rpr('<w:u w:val="wavyDouble" w:color="auto" />')
|
597
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
598
|
+
end
|
599
|
+
|
600
|
+
def test_conversion_of_a_registered_tag_without_ast_class
|
601
|
+
# This registers a new tag with the configuration object and then trys
|
602
|
+
# to convert it
|
603
|
+
Sablon.configure do |config|
|
604
|
+
config.register_html_tag(:bgcyan, :inline, properties: { 'highlight' => { val: 'cyan' } })
|
605
|
+
end
|
606
|
+
#
|
607
|
+
input = '<p><bgcyan>test</bgcyan></p>'
|
608
|
+
expected_output = run_with_rpr('<w:highlight w:val="cyan" />')
|
609
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
610
|
+
|
611
|
+
# remove the tag to avoid any accidental side effects
|
612
|
+
Sablon.configure do |config|
|
613
|
+
config.remove_html_tag(:bgcyan)
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
def test_conversion_of_a_registered_tag_with_ast_class
|
618
|
+
Sablon.configure do |config|
|
619
|
+
# create the AST class and then pass it onto the register tag method
|
620
|
+
ast_class = Class.new(Sablon::HTMLConverter::Node) do
|
621
|
+
def self.name
|
622
|
+
'TestInstr'
|
623
|
+
end
|
624
|
+
|
625
|
+
def initialize(_env, node, _properties)
|
626
|
+
@content = node.text
|
627
|
+
end
|
628
|
+
|
629
|
+
def inspect
|
630
|
+
@content
|
631
|
+
end
|
632
|
+
|
633
|
+
def to_docx
|
634
|
+
"<w:instrText xml:space=\"preserve\"> #{@content} </w:instrText>"
|
635
|
+
end
|
636
|
+
end
|
637
|
+
#
|
638
|
+
config.register_html_tag(:test_instr, :inline, ast_class: ast_class)
|
639
|
+
end
|
640
|
+
#
|
641
|
+
input = '<p><test_instr>test</test_instr></p>'
|
642
|
+
expected_output = <<-DOCX.strip
|
643
|
+
<w:p>
|
644
|
+
<w:pPr>
|
645
|
+
<w:pStyle w:val="Paragraph" />
|
646
|
+
</w:pPr>
|
647
|
+
<w:instrText xml:space="preserve"> test </w:instrText>
|
648
|
+
</w:p>
|
649
|
+
DOCX
|
650
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
651
|
+
|
652
|
+
# remove the tag to avoid any accidental side effects
|
653
|
+
Sablon.configure do |config|
|
654
|
+
config.remove_html_tag(:test_instr)
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
658
|
+
def test_conversion_of_registered_style_attribute
|
659
|
+
Sablon.configure do |config|
|
660
|
+
converter = ->(v) { return :highlight, v }
|
661
|
+
config.register_style_converter(:run, 'test-highlight', converter)
|
662
|
+
end
|
663
|
+
#
|
664
|
+
input = '<p><span style="test-highlight: green">test</span></p>'
|
665
|
+
expected_output = run_with_rpr('<w:highlight w:val="green" />')
|
666
|
+
assert_equal normalize_wordml(expected_output), process(input)
|
667
|
+
#
|
668
|
+
Sablon.configure do |config|
|
669
|
+
config.remove_style_converter(:run, 'test-highlight')
|
670
|
+
end
|
380
671
|
end
|
381
672
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
673
|
+
private
|
674
|
+
|
675
|
+
def process(input)
|
676
|
+
@converter.process(input, @env)
|
386
677
|
end
|
387
678
|
|
388
|
-
def
|
389
|
-
|
390
|
-
|
679
|
+
def para_with_ppr(ppr_str)
|
680
|
+
para_str = '<w:p><w:pPr><w:pStyle w:val="Paragraph" />%s</w:pPr></w:p>'
|
681
|
+
format(para_str, ppr_str)
|
391
682
|
end
|
392
683
|
|
393
|
-
def
|
394
|
-
|
395
|
-
|
684
|
+
def run_with_rpr(rpr_str)
|
685
|
+
para_str = <<-DOCX.strip
|
686
|
+
<w:p>
|
687
|
+
<w:pPr>
|
688
|
+
<w:pStyle w:val="Paragraph" />
|
689
|
+
</w:pPr>
|
690
|
+
<w:r>
|
691
|
+
<w:rPr>
|
692
|
+
%s
|
693
|
+
</w:rPr>
|
694
|
+
<w:t xml:space="preserve">test</w:t>
|
695
|
+
</w:r>
|
696
|
+
</w:p>
|
697
|
+
DOCX
|
698
|
+
format(para_str, rpr_str)
|
396
699
|
end
|
397
700
|
|
398
|
-
def
|
399
|
-
|
400
|
-
ast = @converter.processed_ast(input)
|
401
|
-
list_p = ast.grep(Sablon::HTMLConverter::ListParagraph)
|
402
|
-
assert_equal [1001], list_p.map(&:numid).uniq
|
403
|
-
assert_equal [0, 1, 2, 1, 0, 1, 2], list_p.map(&:ilvl)
|
701
|
+
def normalize_wordml(wordml)
|
702
|
+
wordml.gsub(/^\s+/, '').tr("\n", '')
|
404
703
|
end
|
405
704
|
end
|