sablon 0.0.18 → 0.0.19.beta1

Sign up to get free protection for your applications and to get access to all the features.
data/test/html_test.rb ADDED
@@ -0,0 +1,45 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "test_helper"
3
+ require "support/xml_snippets"
4
+
5
+ class SablonHTMLTest < Sablon::TestCase
6
+ include Sablon::Test::Assertions
7
+
8
+ def setup
9
+ super
10
+ @base_path = Pathname.new(File.expand_path("../", __FILE__))
11
+
12
+ @sample_path = @base_path + "fixtures/html_sample.docx"
13
+ end
14
+
15
+ def test_generate_document_from_template_with_styles_and_html
16
+ template_path = @base_path + "fixtures/insertion_template.docx"
17
+ output_path = @base_path + "sandbox/html.docx"
18
+ template = Sablon.template template_path
19
+ context = {'html:content' => content}
20
+ template.render_to_file output_path, context
21
+
22
+ assert_docx_equal @sample_path, output_path
23
+ end
24
+
25
+ def test_generate_document_from_template_without_styles_and_html
26
+ template_path = @base_path + "fixtures/insertion_template_no_styles.docx"
27
+ output_path = @base_path + "sandbox/html_no_styles.docx"
28
+ template = Sablon.template template_path
29
+ context = {'html:content' => content}
30
+
31
+ e = assert_raises(ArgumentError) do
32
+ template.render_to_file output_path, context
33
+ end
34
+ assert_equal 'Could not find w:abstractNum definition for style: "ListNumber"', e.message
35
+
36
+ skip 'implement default styles'
37
+ end
38
+
39
+ private
40
+ def content
41
+ <<-HTML
42
+ <div>Lorem&nbsp;<strong>ipsum</strong>&nbsp;<em>dolor</em>&nbsp;<strong>sit</strong>&nbsp;<em>amet</em>,&nbsp;<strong>consectetur adipiscing elit</strong>.&nbsp;<em>Suspendisse a tempus turpis</em>. Duis urna justo, vehicula vitae ultricies vel, congue at sem. Fusce turpis turpis, aliquet id pulvinar aliquam, iaculis non elit. Nulla feugiat lectus nulla, in dictum ipsum cursus ac. Quisque at odio neque. Sed ac tortor iaculis, bibendum leo ut, malesuada velit. Donec iaculis sed urna eget pharetra. Praesent ornare fermentum turpis, placerat iaculis urna bibendum vitae. Nunc in quam consequat, tristique tellus in, commodo turpis. Curabitur ullamcorper odio purus, lobortis egestas magna laoreet vitae. Nunc fringilla velit ante, eu aliquam nisi cursus vitae. Suspendisse sit amet dui egestas, volutpat nisi vel, mattis justo. Nullam pellentesque, ipsum eget blandit pharetra, augue elit aliquam mauris, vel mollis nisl augue ut ipsum.</div><ol><li>Vestibulum&nbsp;<ol><li>ante ipsum primis&nbsp;</li></ol></li><li>in faucibus orci luctus&nbsp;<ol><li>et ultrices posuere cubilia Curae;&nbsp;<ol><li>Aliquam vel dolor&nbsp;</li><li>sed sem maximus&nbsp;</li></ol></li><li>fermentum in non odio.&nbsp;<ol><li>Fusce hendrerit ornare mollis.&nbsp;</li></ol></li><li>Nunc scelerisque nibh nec turpis tempor pulvinar.&nbsp;</li></ol></li><li>Donec eros turpis,&nbsp;</li><li>aliquet vel volutpat sit amet,&nbsp;<ol><li>semper eu purus.&nbsp;</li><li>Proin ac erat nec urna efficitur vulputate.&nbsp;<ol><li>Quisque varius convallis ultricies.&nbsp;</li><li>Nullam vel fermentum eros.&nbsp;</li></ol></li></ol></li></ol><div>Pellentesque nulla leo, auctor ornare erat sed, rhoncus congue diam. Duis non porttitor nulla, ut eleifend enim. Pellentesque non tempor sem.</div><div>Mauris auctor egestas arcu,&nbsp;</div><ol><li>id venenatis nibh dignissim id.&nbsp;</li><li>In non placerat metus.&nbsp;</li></ol><ul><li>Nunc sed consequat metus.&nbsp;</li><li>Nulla consectetur lorem consequat,&nbsp;</li><li>malesuada dui at, lacinia lectus.&nbsp;</li></ul><ol><li>Aliquam efficitur&nbsp;</li><li>lorem a mauris feugiat,&nbsp;</li><li>at semper eros pellentesque.&nbsp;</li></ol><div>Nunc lacus diam, consectetur ut odio sit amet, placerat pharetra erat. Sed commodo ut sem id congue. Sed eget neque elit. Curabitur at erat tortor. Maecenas eget sapien vitae est sagittis accumsan et nec orci. Integer luctus at nisl eget venenatis. Nunc nunc eros, consectetur at tortor et, tristique ultrices elit. Nulla in turpis nibh.</div><ul><li>Nam consectetur&nbsp;<ul><li>venenatis tempor.&nbsp;</li></ul></li><li>Aenean&nbsp;<ul><li>blandit<ul><li>porttitor massa,&nbsp;<ul><li>non efficitur&nbsp;<ul><li>metus.&nbsp;</li></ul></li></ul></li></ul></li></ul></li><li>Duis faucibus nunc nec venenatis faucibus.&nbsp;</li><li>Aliquam erat volutpat.&nbsp;</li></ul><div><strong>Quisque non neque ut lacus eleifend volutpat quis sed lacus. Praesent ultrices purus eu quam elementum, sit amet faucibus elit interdum. In lectus orci, elementum quis dictum ac, porta ac ante. Fusce tempus ac mauris id cursus. Phasellus a erat nulla. Mauris dolor orci, malesuada auctor dignissim non, posuere nec odio. Etiam hendrerit justo nec diam ullamcorper, nec blandit elit sodales.</strong></div>
43
+ HTML
44
+ end
45
+ end
@@ -3,13 +3,13 @@ require "test_helper"
3
3
  require "support/document_xml_helper"
4
4
  require "support/xml_snippets"
5
5
 
6
- class ProcessorTest < Sablon::TestCase
6
+ class ProcessorDocumentTest < Sablon::TestCase
7
7
  include DocumentXMLHelper
8
8
  include XMLSnippets
9
9
 
10
10
  def setup
11
11
  super
12
- @processor = Sablon::Processor
12
+ @processor = Sablon::Processor::Document
13
13
  end
14
14
 
15
15
  def test_simple_field_replacement
data/test/test_helper.rb CHANGED
@@ -11,4 +11,8 @@ require "sablon"
11
11
  require "sablon/test"
12
12
 
13
13
  class Sablon::TestCase < MiniTest::Test
14
+ def teardown
15
+ super
16
+ Sablon::Numbering.instance.reset!
17
+ end
14
18
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sablon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.18
4
+ version: 0.0.19.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yves Senn
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-10-27 00:00:00.000000000 Z
11
+ date: 2016-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -129,9 +129,14 @@ files:
129
129
  - lib/sablon.rb
130
130
  - lib/sablon/content.rb
131
131
  - lib/sablon/context.rb
132
+ - lib/sablon/html/ast.rb
133
+ - lib/sablon/html/converter.rb
134
+ - lib/sablon/html/visitor.rb
135
+ - lib/sablon/numbering.rb
132
136
  - lib/sablon/operations.rb
133
137
  - lib/sablon/parser/mail_merge.rb
134
- - lib/sablon/processor.rb
138
+ - lib/sablon/processor/document.rb
139
+ - lib/sablon/processor/numbering.rb
135
140
  - lib/sablon/processor/section_properties.rb
136
141
  - lib/sablon/redcarpet/render/word_ml.rb
137
142
  - lib/sablon/template.rb
@@ -151,6 +156,9 @@ files:
151
156
  - test/fixtures/conditionals_template.docx
152
157
  - test/fixtures/cv_sample.docx
153
158
  - test/fixtures/cv_template.docx
159
+ - test/fixtures/html_sample.docx
160
+ - test/fixtures/insertion_template.docx
161
+ - test/fixtures/insertion_template_no_styles.docx
154
162
  - test/fixtures/recipe_context.json
155
163
  - test/fixtures/recipe_sample.docx
156
164
  - test/fixtures/recipe_template.docx
@@ -169,8 +177,10 @@ files:
169
177
  - test/fixtures/xml/simple_fields.xml
170
178
  - test/fixtures/xml/table_multi_row_loop.xml
171
179
  - test/fixtures/xml/table_row_loop.xml
180
+ - test/html/converter_test.rb
181
+ - test/html_test.rb
172
182
  - test/mail_merge_parser_test.rb
173
- - test/processor_test.rb
183
+ - test/processor/document_test.rb
174
184
  - test/redcarpet_render_word_ml_test.rb
175
185
  - test/sablon_test.rb
176
186
  - test/sandbox/.gitkeep
@@ -193,9 +203,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
193
203
  version: '0'
194
204
  required_rubygems_version: !ruby/object:Gem::Requirement
195
205
  requirements:
196
- - - ">="
206
+ - - ">"
197
207
  - !ruby/object:Gem::Version
198
- version: '0'
208
+ version: 1.3.1
199
209
  requirements: []
200
210
  rubyforge_project:
201
211
  rubygems_version: 2.4.5.1
@@ -211,6 +221,9 @@ test_files:
211
221
  - test/fixtures/conditionals_template.docx
212
222
  - test/fixtures/cv_sample.docx
213
223
  - test/fixtures/cv_template.docx
224
+ - test/fixtures/html_sample.docx
225
+ - test/fixtures/insertion_template.docx
226
+ - test/fixtures/insertion_template_no_styles.docx
214
227
  - test/fixtures/recipe_context.json
215
228
  - test/fixtures/recipe_sample.docx
216
229
  - test/fixtures/recipe_template.docx
@@ -229,8 +242,10 @@ test_files:
229
242
  - test/fixtures/xml/simple_fields.xml
230
243
  - test/fixtures/xml/table_multi_row_loop.xml
231
244
  - test/fixtures/xml/table_row_loop.xml
245
+ - test/html/converter_test.rb
246
+ - test/html_test.rb
232
247
  - test/mail_merge_parser_test.rb
233
- - test/processor_test.rb
248
+ - test/processor/document_test.rb
234
249
  - test/redcarpet_render_word_ml_test.rb
235
250
  - test/sablon_test.rb
236
251
  - test/sandbox/.gitkeep
@@ -1,191 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- module Sablon
3
- class Processor
4
- def self.process(xml_node, context, properties = {})
5
- processor = new(parser)
6
- processor.manipulate xml_node, Sablon::Context.transform(context)
7
- processor.write_properties xml_node, properties if properties.any?
8
- xml_node
9
- end
10
-
11
- def self.parser
12
- @parser ||= Sablon::Parser::MailMerge.new
13
- end
14
-
15
- def initialize(parser)
16
- @parser = parser
17
- end
18
-
19
- def manipulate(xml_node, context)
20
- operations = build_operations(@parser.parse_fields(xml_node))
21
- operations.each do |step|
22
- step.evaluate context
23
- end
24
- cleanup(xml_node)
25
- xml_node
26
- end
27
-
28
- def write_properties(xml_node, properties)
29
- if start_page_number = properties[:start_page_number] || properties["start_page_number"]
30
- section_properties = SectionProperties.from_document(xml_node)
31
- section_properties.start_page_number = start_page_number
32
- end
33
- end
34
-
35
- private
36
- def build_operations(fields)
37
- OperationConstruction.new(fields).operations
38
- end
39
-
40
- def cleanup(xml_node)
41
- fill_empty_table_cells xml_node
42
- end
43
-
44
- def fill_empty_table_cells(xml_node)
45
- xml_node.xpath("//w:tc[count(*[name() = 'w:p'])=0 or not(*)]").each do |blank_cell|
46
- filler = Nokogiri::XML::Node.new("w:p", xml_node.document)
47
- blank_cell.add_child filler
48
- end
49
- end
50
-
51
- class Block < Struct.new(:start_field, :end_field)
52
- def self.enclosed_by(start_field, end_field)
53
- @blocks ||= [RowBlock, ParagraphBlock, InlineParagraphBlock]
54
- block_class = @blocks.detect { |klass| klass.encloses?(start_field, end_field) }
55
- block_class.new start_field, end_field
56
- end
57
-
58
- def process(context)
59
- replaced_node = Nokogiri::XML::Node.new("tmp", start_node.document)
60
- replaced_node.children = Nokogiri::XML::NodeSet.new(start_node.document, body.map(&:dup))
61
- Processor.process replaced_node, context
62
- replaced_node.children
63
- end
64
-
65
- def replace(content)
66
- content.each { |n| start_node.add_next_sibling n }
67
- remove_control_elements
68
- end
69
-
70
- def remove_control_elements
71
- body.each &:remove
72
- start_node.remove
73
- end_node.remove
74
- end
75
-
76
- def body
77
- return @body if defined?(@body)
78
- @body = []
79
- node = start_node
80
- while (node = node.next_element) && node != end_node
81
- @body << node
82
- end
83
- @body
84
- end
85
-
86
- def start_node
87
- @start_node ||= self.class.parent(start_field).first
88
- end
89
-
90
- def end_node
91
- @end_node ||= self.class.parent(end_field).first
92
- end
93
-
94
- def self.encloses?(start_field, end_field)
95
- parent(start_field).any? && parent(end_field).any?
96
- end
97
- end
98
-
99
- class RowBlock < Block
100
- def self.parent(node)
101
- node.ancestors ".//w:tr"
102
- end
103
-
104
- def self.encloses?(start_field, end_field)
105
- super && parent(start_field) != parent(end_field)
106
- end
107
- end
108
-
109
- class ParagraphBlock < Block
110
- def self.parent(node)
111
- node.ancestors ".//w:p"
112
- end
113
-
114
- def self.encloses?(start_field, end_field)
115
- super && parent(start_field) != parent(end_field)
116
- end
117
- end
118
-
119
- class InlineParagraphBlock < Block
120
- def self.parent(node)
121
- node.ancestors ".//w:p"
122
- end
123
-
124
- def remove_control_elements
125
- body.each &:remove
126
- start_field.remove
127
- end_field.remove
128
- end
129
-
130
- def start_node
131
- @start_node ||= start_field.end_node
132
- end
133
-
134
- def end_node
135
- @end_node ||= end_field.start_node
136
- end
137
-
138
- def self.encloses?(start_field, end_field)
139
- super && parent(start_field) == parent(end_field)
140
- end
141
- end
142
-
143
- class OperationConstruction
144
- def initialize(fields)
145
- @fields = fields
146
- @operations = []
147
- end
148
-
149
- def operations
150
- while @fields.any?
151
- @operations << consume(true)
152
- end
153
- @operations.compact
154
- end
155
-
156
- def consume(allow_insertion)
157
- @field = @fields.shift
158
- return unless @field
159
- case @field.expression
160
- when /^=/
161
- if allow_insertion
162
- Statement::Insertion.new(Expression.parse(@field.expression[1..-1]), @field)
163
- end
164
- when /([^ ]+):each\(([^ ]+)\)/
165
- block = consume_block("#{$1}:endEach")
166
- Statement::Loop.new(Expression.parse($1), $2, block)
167
- when /([^ ]+):if\(([^)]+)\)/
168
- block = consume_block("#{$1}:endIf")
169
- Statement::Condition.new(Expression.parse($1), block, $2)
170
- when /([^ ]+):if/
171
- block = consume_block("#{$1}:endIf")
172
- Statement::Condition.new(Expression.parse($1), block)
173
- end
174
- end
175
-
176
- def consume_block(end_expression)
177
- start_field = end_field = @field
178
- while end_field && end_field.expression != end_expression
179
- consume(false)
180
- end_field = @field
181
- end
182
-
183
- if end_field
184
- Block.enclosed_by start_field, end_field
185
- else
186
- raise TemplateError, "Could not find end field for «#{start_field.expression}». Was looking for «#{end_expression}»"
187
- end
188
- end
189
- end
190
- end
191
- end