sablon 0.0.10 → 0.0.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -1
- data/README.md +84 -19
- data/lib/sablon.rb +2 -0
- data/lib/sablon/content.rb +17 -0
- data/lib/sablon/redcarpet/render/word_ml.rb +57 -0
- data/lib/sablon/version.rb +1 -1
- data/misc/cv_sample.png +0 -0
- data/misc/cv_template.png +0 -0
- data/misc/recipe_sample.png +0 -0
- data/misc/recipe_template.png +0 -0
- data/sablon.gemspec +1 -0
- data/test/content_test.rb +35 -0
- data/test/executable_test.rb +6 -6
- data/test/fixtures/cv_sample.docx +0 -0
- data/test/fixtures/cv_template.docx +0 -0
- data/test/fixtures/recipe_context.json +24 -0
- data/test/fixtures/recipe_sample.docx +0 -0
- data/test/fixtures/recipe_template.docx +0 -0
- data/test/redcarpet_render_word_ml_test.rb +103 -0
- data/test/sablon_test.rb +61 -17
- metadata +33 -16
- data/misc/output.png +0 -0
- data/misc/template.png +0 -0
- data/test/fixtures/sablon_sample.docx +0 -0
- data/test/fixtures/sablon_template.docx +0 -0
- data/test/fixtures/shopping_list_context.json +0 -15
- data/test/fixtures/shopping_list_sample.docx +0 -0
- data/test/fixtures/shopping_list_template.docx +0 -0
- data/test/fixtures/xml/about_me_snippet.xml +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a4d14328ddbea68fddbbc747b5a8e4fe89b7e1b
|
4
|
+
data.tar.gz: 45c9af0a71ce34c1c2211474bf75f0f60fd611fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5d4a12a4ec9c75a08fb18a729775072ee96cd029cbd064a8295832be6c9a70409e27c80dfd0ea92354a10d2c4715946e03d414a52d1514bd8300ef56c6c00cf
|
7
|
+
data.tar.gz: 9525095be7b154d71e307ec3bdebb6a026c6b514b03323b09151a58ec2376c22f8c66921ac70525c1f87f0005e999670c6b2c12097f0b736fe642db6284627f4
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
sablon (0.0.
|
4
|
+
sablon (0.0.11)
|
5
5
|
nokogiri (>= 1.6.0)
|
6
|
+
redcarpet (>= 3.2)
|
6
7
|
rubyzip (>= 1.1)
|
7
8
|
|
8
9
|
GEM
|
@@ -13,6 +14,7 @@ GEM
|
|
13
14
|
nokogiri (1.6.6.2)
|
14
15
|
mini_portile (~> 0.6.0)
|
15
16
|
rake (10.4.2)
|
17
|
+
redcarpet (3.2.2)
|
16
18
|
rubyzip (1.1.7)
|
17
19
|
xml-simple (1.1.5)
|
18
20
|
|
data/README.md
CHANGED
@@ -32,8 +32,8 @@ template.render_to_file File.expand_path("~/Desktop/output.docx"), context
|
|
32
32
|
|
33
33
|
### Writing Templates
|
34
34
|
|
35
|
-
Sablon templates are normal
|
36
|
-
to perform operations. The following section
|
35
|
+
Sablon templates are normal Word documents (`.docx`) sprinkled with MailMerge fields
|
36
|
+
to perform operations. The following section uses the notation `«=title»` to
|
37
37
|
refer to [Word MailMerge](http://en.wikipedia.org/wiki/Mail_merge) fields.
|
38
38
|
|
39
39
|
#### Content Insertion
|
@@ -70,34 +70,76 @@ format ahead of processing time (in the template) you can insert
|
|
70
70
|
[WordProcessingML](http://en.wikipedia.org/wiki/Microsoft_Office_XML_formats)
|
71
71
|
directly.
|
72
72
|
|
73
|
-
|
73
|
+
It's enough to use a simply insertion operation in the template:
|
74
74
|
|
75
75
|
```
|
76
76
|
«=long_description»
|
77
77
|
```
|
78
78
|
|
79
|
-
|
79
|
+
To insert WordProcessingML prepare the context accordingly:
|
80
80
|
|
81
81
|
```ruby
|
82
|
-
word_processing_ml = <<-XML
|
82
|
+
word_processing_ml = <<-XML.gsub("\n", "")
|
83
83
|
<w:p>
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
84
|
+
<w:r w:rsidRPr="00B97C39">
|
85
|
+
<w:rPr>
|
86
|
+
<w:b />
|
87
|
+
</w:rPr>
|
88
|
+
<w:t>this is bold text</w:t>
|
89
|
+
</w:r>
|
90
90
|
</w:p>
|
91
91
|
XML
|
92
|
+
|
92
93
|
context = {
|
93
94
|
long_description: Sablon.content(:word_ml, word_processing_ml)
|
94
95
|
}
|
95
96
|
template.render_to_file File.expand_path("~/Desktop/output.docx"), context
|
96
97
|
```
|
97
98
|
|
98
|
-
|
99
|
-
|
100
|
-
|
99
|
+
IMPORTANT: This feature is very much *experimental*. Currently, the insertion
|
100
|
+
will replace the containing paragraph. This means that other content in the same
|
101
|
+
paragraph is discarded.
|
102
|
+
|
103
|
+
##### Markdown
|
104
|
+
|
105
|
+
Similar to WordProcessingML it's possible to use markdown while processing the
|
106
|
+
tempalte. You don't need to modify your templates, a simple insertion operation
|
107
|
+
is sufficient:
|
108
|
+
|
109
|
+
```
|
110
|
+
«=article.body»
|
111
|
+
```
|
112
|
+
|
113
|
+
To use Markdown insertion prepare the context like so:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
markdown_body = <<-MD
|
117
|
+
This text can contain *additional formatting*
|
118
|
+
according to the **Markdown** specification.
|
119
|
+
MD
|
120
|
+
context = {
|
121
|
+
article: { body: Sablon.content(:markdown, markdown_body) }
|
122
|
+
}
|
123
|
+
template.render_to_file File.expand_path("~/Desktop/output.docx"), context
|
124
|
+
```
|
125
|
+
|
126
|
+
Markdown insertion has built-in support for:
|
127
|
+
|
128
|
+
* [Headers](http://spec.commonmark.org/0.17/#atx-header)
|
129
|
+
* [Paragraphs](http://spec.commonmark.org/0.17/#paragraphs)
|
130
|
+
* [Emphasis and strong emphasis](http://spec.commonmark.org/0.17/#emphasis-and-strong-emphasis)
|
131
|
+
* [Hard line breaks](http://spec.commonmark.org/0.17/#hard-line-breaks)
|
132
|
+
* [Lists](http://spec.commonmark.org/0.17/#lists)
|
133
|
+
|
134
|
+
For headings and lists to function properly it is necessary that the template
|
135
|
+
defines specific styles. Headings use styles called `Heading1`, `Heading2`,
|
136
|
+
etc. according to the header level. Ordered lists will use the style
|
137
|
+
`ListNumber` and unordered lists use `ListBullet`. Nested lists are not
|
138
|
+
supported.
|
139
|
+
|
140
|
+
IMPORTANT: This feature is very much *experimental*. Currently, the insertion
|
141
|
+
will replace the containing paragraph. This means that other content in the same
|
142
|
+
paragraph is discarded.
|
101
143
|
|
102
144
|
#### Conditionals
|
103
145
|
|
@@ -136,7 +178,7 @@ Loops repeat parts of the document.
|
|
136
178
|
Loops can be used to repeat table rows or list enumerations. The fields need to
|
137
179
|
be placed in within table cells or enumeration items enclosing the rows or items
|
138
180
|
to repeat. Have a look at the
|
139
|
-
[example template](test/fixtures/
|
181
|
+
[example template](test/fixtures/cv_template.docx) for more details.
|
140
182
|
|
141
183
|
|
142
184
|
#### Nesting
|
@@ -160,25 +202,48 @@ Have a look at [this test](test/executable_test.rb) for examples.
|
|
160
202
|
|
161
203
|
### Examples
|
162
204
|
|
163
|
-
|
205
|
+
#### Using a Ruby script
|
206
|
+
|
207
|
+
There is a [sample template](test/fixtures/cv_template.docx) in the
|
164
208
|
repository, which illustrates the functionality of sablon:
|
165
209
|
|
166
210
|
<p align="center">
|
167
211
|
<img
|
168
|
-
src="https://raw.githubusercontent.com/senny/sablon/master/misc/
|
212
|
+
src="https://raw.githubusercontent.com/senny/sablon/master/misc/cv_template.png"
|
169
213
|
alt="Sablon Template"/>
|
170
214
|
</p>
|
171
215
|
|
172
216
|
Processing this template with some sample data yields the following
|
173
|
-
[output document](test/fixtures/
|
217
|
+
[output document](test/fixtures/cv_sample.docx).
|
174
218
|
For more details, check out this [test case](test/sablon_test.rb).
|
175
219
|
|
176
220
|
<p align="center">
|
177
221
|
<img
|
178
|
-
src="https://raw.githubusercontent.com/senny/sablon/master/misc/
|
222
|
+
src="https://raw.githubusercontent.com/senny/sablon/master/misc/cv_sample.png"
|
179
223
|
alt="Sablon Output"/>
|
180
224
|
</p>
|
181
225
|
|
226
|
+
#### Using the sablon executable
|
227
|
+
|
228
|
+
The [executable test](test/executable_test.rb) showcases the `sablon`
|
229
|
+
executable.
|
230
|
+
|
231
|
+
The [template](test/fixtures/recipe_template.docx)
|
232
|
+
|
233
|
+
<p align="center">
|
234
|
+
<img
|
235
|
+
src="https://raw.githubusercontent.com/senny/sablon/master/misc/recipe_template.png"
|
236
|
+
alt="Sablon Output"/>
|
237
|
+
</p>
|
238
|
+
|
239
|
+
is rendered using a [json context](test/fixtures/recipe_context.json) to provide
|
240
|
+
the data. Following is the resulting [output](test/fixtures/recipe_sample.docx):
|
241
|
+
|
242
|
+
<p align="center">
|
243
|
+
<img
|
244
|
+
src="https://raw.githubusercontent.com/senny/sablon/master/misc/recipe_sample.png"
|
245
|
+
alt="Sablon Output"/>
|
246
|
+
</p>
|
182
247
|
|
183
248
|
## Contributing
|
184
249
|
|
data/lib/sablon.rb
CHANGED
data/lib/sablon/content.rb
CHANGED
@@ -77,7 +77,24 @@ module Sablon
|
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
+
class Markdown < Struct.new(:word_ml)
|
81
|
+
include Sablon::Content
|
82
|
+
def self.id; :markdown end
|
83
|
+
def self.wraps?(value) false end
|
84
|
+
|
85
|
+
def initialize(markdown)
|
86
|
+
redcarpet = ::Redcarpet::Markdown.new(Sablon::Redcarpet::Render::WordML)
|
87
|
+
word_ml = Sablon.content(:word_ml, redcarpet.render(markdown))
|
88
|
+
super word_ml
|
89
|
+
end
|
90
|
+
|
91
|
+
def append_to(*args)
|
92
|
+
word_ml.append_to(*args)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
80
96
|
register Sablon::Content::String
|
81
97
|
register Sablon::Content::WordML
|
98
|
+
register Sablon::Content::Markdown
|
82
99
|
end
|
83
100
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Sablon
|
2
|
+
module Redcarpet
|
3
|
+
module Render
|
4
|
+
class WordML < ::Redcarpet::Render::Base
|
5
|
+
def linebreak
|
6
|
+
"</w:p><w:p>"
|
7
|
+
end
|
8
|
+
|
9
|
+
def header(title, level)
|
10
|
+
style = "Heading#{level}"
|
11
|
+
|
12
|
+
"<w:p><w:pPr><w:pStyle w:val=\"#{style}\"/></w:pPr>#{title}</w:p>"
|
13
|
+
end
|
14
|
+
|
15
|
+
def paragraph(text)
|
16
|
+
"<w:p>#{text}</w:p>"
|
17
|
+
end
|
18
|
+
|
19
|
+
def normal_text(text)
|
20
|
+
@raw_text = text
|
21
|
+
return '' if text.nil? || text == '' || text == "\n"
|
22
|
+
"<w:r><w:t xml:space=\"preserve\">#{text}</w:t></w:r>"
|
23
|
+
end
|
24
|
+
|
25
|
+
def emphasis(text)
|
26
|
+
"<w:r><w:rPr><w:i /></w:rPr><w:t xml:space=\"preserve\">#{@raw_text}</w:t></w:r>"
|
27
|
+
end
|
28
|
+
|
29
|
+
def double_emphasis(text)
|
30
|
+
"<w:r><w:rPr><w:b /></w:rPr><w:t xml:space=\"preserve\">#{@raw_text}</w:t></w:r>"
|
31
|
+
end
|
32
|
+
|
33
|
+
def list(content, list_type)
|
34
|
+
content
|
35
|
+
end
|
36
|
+
|
37
|
+
LIST_PATTERN = <<-XML.gsub("\n", "")
|
38
|
+
<w:p>
|
39
|
+
<w:pPr>
|
40
|
+
<w:pStyle w:val="%s" />
|
41
|
+
</w:pPr>
|
42
|
+
%s
|
43
|
+
</w:p>
|
44
|
+
XML
|
45
|
+
LIST_STYLE_MAPPING = {
|
46
|
+
ordered: "ListNumber",
|
47
|
+
unordered: "ListBullet"
|
48
|
+
}
|
49
|
+
|
50
|
+
def list_item(content, list_type)
|
51
|
+
list_style = LIST_STYLE_MAPPING[list_type]
|
52
|
+
LIST_PATTERN % [list_style, content]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/sablon/version.rb
CHANGED
data/misc/cv_sample.png
ADDED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/sablon.gemspec
CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_runtime_dependency 'nokogiri', ">= 1.6.0"
|
22
22
|
spec.add_runtime_dependency 'rubyzip', ">= 1.1"
|
23
|
+
spec.add_runtime_dependency 'redcarpet', ">= 3.2"
|
23
24
|
|
24
25
|
spec.add_development_dependency "bundler", ">= 1.6"
|
25
26
|
spec.add_development_dependency "rake", "~> 10.0"
|
data/test/content_test.rb
CHANGED
@@ -158,3 +158,38 @@ class ContentWordMLTest < Sablon::TestCase
|
|
158
158
|
skip "Content::WordML currently removes the paragraph..."
|
159
159
|
end
|
160
160
|
end
|
161
|
+
|
162
|
+
|
163
|
+
class ContentMarkdownTest < Sablon::TestCase
|
164
|
+
include ContentTestSetup
|
165
|
+
|
166
|
+
def test_blank_markdown
|
167
|
+
Sablon.content(:markdown, "").append_to @paragraph, @node
|
168
|
+
|
169
|
+
assert_xml_equal "<w:p>AFTER</w:p>", @document
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_inserts_markdown
|
173
|
+
Sablon.content(:markdown, "yay **bold** text").append_to @paragraph, @node
|
174
|
+
|
175
|
+
output = <<-XML.strip.gsub("\n", "")
|
176
|
+
<w:p>
|
177
|
+
<w:r><w:t xml:space=\"preserve\">yay </w:t></w:r>
|
178
|
+
<w:r>
|
179
|
+
<w:rPr><w:b/></w:rPr>
|
180
|
+
<w:t xml:space=\"preserve\">bold</w:t>
|
181
|
+
</w:r>
|
182
|
+
<w:r>
|
183
|
+
<w:t xml:space=\"preserve\"> text</w:t>
|
184
|
+
</w:r>
|
185
|
+
</w:p>
|
186
|
+
<w:p>AFTER</w:p>
|
187
|
+
XML
|
188
|
+
|
189
|
+
assert_xml_equal output, @document
|
190
|
+
end
|
191
|
+
|
192
|
+
def test_inserting_markdown_multiple_times_into_same_paragraph
|
193
|
+
skip "Content::Markdown currently removes the paragraph..."
|
194
|
+
end
|
195
|
+
end
|
data/test/executable_test.rb
CHANGED
@@ -6,23 +6,23 @@ class ExecutableTest < Sablon::TestCase
|
|
6
6
|
def setup
|
7
7
|
super
|
8
8
|
@base_path = Pathname.new(File.expand_path("../", __FILE__))
|
9
|
-
@output_path = @base_path + "sandbox/
|
10
|
-
@template_path = @base_path + "fixtures/
|
11
|
-
@
|
9
|
+
@output_path = @base_path + "sandbox/recipe.docx"
|
10
|
+
@template_path = @base_path + "fixtures/recipe_template.docx"
|
11
|
+
@sample_path = @base_path + "fixtures/recipe_sample.docx"
|
12
|
+
@context_path = @base_path + "fixtures/recipe_context.json"
|
12
13
|
@executable_path = @base_path + '../exe/sablon'
|
13
|
-
|
14
14
|
@output_path.delete if @output_path.exist?
|
15
15
|
end
|
16
16
|
|
17
17
|
def test_generate_document_from_template_output_to_file
|
18
18
|
`cat #{@context_path} | #{@executable_path} #{@template_path} #{@output_path}`
|
19
19
|
|
20
|
-
assert_docx_equal @
|
20
|
+
assert_docx_equal @sample_path, @output_path
|
21
21
|
end
|
22
22
|
|
23
23
|
def test_generate_document_from_template_output_to_stdout
|
24
24
|
`cat #{@context_path} | #{@executable_path} #{@template_path} > #{@output_path}`
|
25
25
|
|
26
|
-
assert_docx_equal @
|
26
|
+
assert_docx_equal @sample_path, @output_path
|
27
27
|
end
|
28
28
|
end
|
Binary file
|
Binary file
|
@@ -0,0 +1,24 @@
|
|
1
|
+
{
|
2
|
+
"metadata": {
|
3
|
+
"generator": "Sablon",
|
4
|
+
"author": {
|
5
|
+
"name": "Brett",
|
6
|
+
"profile": "http://www.opensourcefood.com/people/Brett"
|
7
|
+
}
|
8
|
+
},
|
9
|
+
"title": "Bruschetta",
|
10
|
+
"word_ml:intro": "<w:p><w:r><w:rPr><w:b/></w:rPr><w:t xml:space=\"preserve\">Bruschetta</w:t></w:r><w:r><w:t xml:space=\"preserve\"> </w:t></w:r><w:r><w:rPr><w:i/></w:rPr><w:t xml:space=\"preserve\">(Italian pronunciation: [bruˈsketta])</w:t></w:r><w:r><w:t xml:space=\"preserve\"> is an antipasto (starter dish) from Italy consisting of grilled bread rubbed with garlic and topped with tomatoes, olive oil, salt and pepper. Variations may include toppings of tomato, vegetables, beans, cured meat, or cheese, a popular dish is Bruschetta pomodoro; the most popular recipe outside of Italy involves basil, fresh tomato, garlic and onion or mozzarella. Bruschetta is usually served as a snack or appetizer. In some countries, a topping of chopped tomato, olive oil and herbs is marketed under the bruschetta name.</w:t></w:r></w:p>",
|
11
|
+
"markdown:preparation": "## How to make Baby's Bruschetta\n\n### Preparation\nMake the bruschetta mixture one day before the event and let it sit in the fridge overnight to absorb all the flavours(be aware your fridge will smell entirely like the *tomatoes* and *garlic* \nI find it works best to slice the *baguette* on an angle to create more space. Put the broiler on the oven on to toast the bread. Line them up on a baking sheet and make a small mixture of chopped garlic and olive oil to lightly spread on the slices of *baguette*, then place into the oven. \nOnce Half toasted remove bread and place bruschetta mixture on top (after draining off the majority of fluid). Then sprinkle the grated cheese on top and place back into the oven to broil/toast to your desired effect. ~ Please note that if you prefer to not have your *cheese* melted you can skip this step and just toast the bread fully, add the mixture and *cheese* and serve. \nOnce melted, remove and place directly on a serving platter and let rest for one minute while adding fresh chopped *basil* for garnish, and **ENJOY!**\n\n### Variations\n\n1. Use fresh oregano instead of basil.\n1. Other possible toppings: prosciutto, salami, bean spreads\n\n### Notes\n- In **Italy**, bruschetta is often prepared using a brustolina grill. In the Abruzzo region of Italy a variation of bruschetta made with a salame called *ventricina* is served.\n- In **Tuscany** it is called *fettunta* and it is usually served without toppings, especially in November, to taste the very first oil of the season.\n- The chinese translation of 'Bruschetta' is '大蒜麵包' (Google Translator)",
|
12
|
+
"ingredients": [
|
13
|
+
{"name": "Ripened, chopped, medium sized tomatoes ", "amount": "6"},
|
14
|
+
{"name": "medium sized red (Spanish) onion", "amount": "1"},
|
15
|
+
{"name": "finely chopped cloves of garlic", "amount": "3 or 4"},
|
16
|
+
{"name": "fresh basil leaves finely chopped", "amount": "5 or 6"},
|
17
|
+
{"name": "a lemon's juice", "amount": "half"},
|
18
|
+
{"name": "of dried Oregano", "amount": "a small dash"},
|
19
|
+
{"name": "of balsamic vinegar", "amount": "a small dash"},
|
20
|
+
{"name": "olive oil, sea salt and cracked pepper", "amount": "to taste;"},
|
21
|
+
{"name": "grated cheese of your choice (romano/parmigiano/mozzarella)", "amount": "some"},
|
22
|
+
{"name": "baguette", "amount": "one"}
|
23
|
+
]
|
24
|
+
}
|
Binary file
|
Binary file
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "test_helper"
|
3
|
+
|
4
|
+
class RedcarpetRenderWordMLTest < Sablon::TestCase
|
5
|
+
def setup
|
6
|
+
@redcarpet = ::Redcarpet::Markdown.new(Sablon::Redcarpet::Render::WordML)
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_normal_text
|
10
|
+
word_ml = <<-XML.gsub("\n", "")
|
11
|
+
<w:p>
|
12
|
+
<w:r><w:t xml:space=\"preserve\">normal</w:t></w:r>
|
13
|
+
</w:p>
|
14
|
+
XML
|
15
|
+
assert_equal word_ml, @redcarpet.render("normal")
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_empty_string
|
19
|
+
assert_equal "", @redcarpet.render("")
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_blank_string_with_newline
|
23
|
+
assert_equal "", @redcarpet.render("\n")
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_newline_in_a_paragraph_starts_new_paragraph
|
27
|
+
word_ml = <<-XML.gsub("\n", "")
|
28
|
+
<w:p>
|
29
|
+
<w:r><w:t xml:space=\"preserve\">some </w:t></w:r>
|
30
|
+
</w:p>
|
31
|
+
<w:p>
|
32
|
+
<w:r><w:t xml:space=\"preserve\">text</w:t></w:r>
|
33
|
+
</w:p>
|
34
|
+
XML
|
35
|
+
assert_equal word_ml, @redcarpet.render("some \ntext")
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_bold_text
|
39
|
+
word_ml = <<-XML.gsub("\n", "")
|
40
|
+
<w:p>
|
41
|
+
<w:r>
|
42
|
+
<w:rPr><w:b /></w:rPr>
|
43
|
+
<w:t xml:space="preserve">bold</w:t>
|
44
|
+
</w:r>
|
45
|
+
</w:p>
|
46
|
+
XML
|
47
|
+
assert_equal word_ml, @redcarpet.render("**bold**")
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_italic_text
|
51
|
+
word_ml = <<-XML.gsub("\n", "")
|
52
|
+
<w:p>
|
53
|
+
<w:r>
|
54
|
+
<w:rPr><w:i /></w:rPr>
|
55
|
+
<w:t xml:space="preserve">italic</w:t>
|
56
|
+
</w:r>
|
57
|
+
</w:p>
|
58
|
+
XML
|
59
|
+
assert_equal word_ml, @redcarpet.render("*italic*")
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_single_line_mixed_text
|
63
|
+
word_ml = <<-XML.gsub("\n", "")
|
64
|
+
<w:p>
|
65
|
+
|
66
|
+
<w:r><w:t xml:space="preserve">some </w:t></w:r>
|
67
|
+
|
68
|
+
<w:r>
|
69
|
+
<w:rPr><w:i /></w:rPr>
|
70
|
+
<w:t xml:space="preserve">random</w:t>
|
71
|
+
</w:r>
|
72
|
+
|
73
|
+
<w:r><w:t xml:space="preserve"> </w:t></w:r>
|
74
|
+
<w:r>
|
75
|
+
<w:rPr><w:b /></w:rPr>
|
76
|
+
<w:t xml:space="preserve">text</w:t>
|
77
|
+
</w:r>
|
78
|
+
</w:p>
|
79
|
+
XML
|
80
|
+
assert_equal word_ml, @redcarpet.render("some *random* **text**")
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_unordered_lists
|
84
|
+
word_ml = <<-XML.gsub("\n", "")
|
85
|
+
<w:p>
|
86
|
+
<w:pPr><w:pStyle w:val="ListBullet" /></w:pPr>
|
87
|
+
<w:r><w:t xml:space="preserve">first</w:t></w:r>
|
88
|
+
</w:p>
|
89
|
+
|
90
|
+
<w:p>
|
91
|
+
<w:pPr><w:pStyle w:val="ListBullet" /></w:pPr>
|
92
|
+
<w:r><w:t xml:space="preserve">second</w:t></w:r>
|
93
|
+
</w:p>
|
94
|
+
|
95
|
+
<w:p>
|
96
|
+
<w:pPr><w:pStyle w:val="ListBullet" /></w:pPr>
|
97
|
+
<w:r><w:t xml:space="preserve">third</w:t></w:r>
|
98
|
+
</w:p>
|
99
|
+
XML
|
100
|
+
|
101
|
+
assert_equal word_ml, @redcarpet.render("- first\n- second\n- third")
|
102
|
+
end
|
103
|
+
end
|
data/test/sablon_test.rb
CHANGED
@@ -9,34 +9,78 @@ class SablonTest < Sablon::TestCase
|
|
9
9
|
def setup
|
10
10
|
super
|
11
11
|
@base_path = Pathname.new(File.expand_path("../", __FILE__))
|
12
|
-
@
|
12
|
+
@template_path = @base_path + "fixtures/cv_template.docx"
|
13
|
+
@output_path = @base_path + "sandbox/cv.docx"
|
14
|
+
@sample_path = @base_path + "fixtures/cv_sample.docx"
|
13
15
|
end
|
14
16
|
|
15
17
|
def test_generate_document_from_template
|
16
|
-
template = Sablon.template
|
17
|
-
|
18
|
-
|
19
|
-
position = Struct.new(:
|
18
|
+
template = Sablon.template @template_path
|
19
|
+
|
20
|
+
skill = Struct.new(:index, :label, :rating)
|
21
|
+
position = Struct.new(:when, :where, :tasks, :description)
|
20
22
|
language = Struct.new(:name, :skill)
|
23
|
+
education = Struct.new(:when, :where, :what)
|
24
|
+
referee = Struct.new(:name, :company, :position, :phone)
|
25
|
+
|
21
26
|
context = {
|
22
27
|
current_time: Time.now.strftime("%d.%m.%Y %H:%M"),
|
23
|
-
|
24
|
-
title: "
|
25
|
-
person:
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
metadata: { generator: "Sablon" },
|
29
|
+
title: "Resume",
|
30
|
+
person: OpenStruct.new("first_name" => "Ronald", "last_name" => "Anderson",
|
31
|
+
"phone" => "630-384-2975",
|
32
|
+
"email" => "ron.anderson@gmail.com",
|
33
|
+
"address" => {
|
34
|
+
"street" => "1009 Fraggle Drive",
|
35
|
+
"municipality" => "Wheaton",
|
36
|
+
"province_zip" => "IL 60187"}),
|
37
|
+
skills: [skill.new("1.", "Java", "★" * 5),
|
38
|
+
skill.new("2.", "Ruby", "★" * 3),
|
39
|
+
skill.new("3.", "Python", "★" * 1),
|
40
|
+
skill.new("4.", "XML, XSLT, JSP"),
|
41
|
+
skill.new("5.", "automated testing", "★" * 3),
|
42
|
+
],
|
43
|
+
education: [
|
44
|
+
education.new("2005 – 2008", "Birmingham University", "Degree: BSc Hons Computer Science. 2:1 Attained."),
|
45
|
+
education.new("2003 – 2005", "Yale Sixth Form College, Bristol.", "3 A Levels - Mathematics (A), Science (A), History (B)"),
|
46
|
+
education.new("1997 – 2003", "Berry High School, Bristol.", "11 GCSE’s – 5 As, 5 Bs, 1 C")
|
47
|
+
],
|
48
|
+
certifications: [],
|
49
|
+
career: [position.new("February 2013 - Present", "Apps Limited", [],
|
50
|
+
"Ruby on Rails Web Developer for this retail merchandising company."),
|
51
|
+
position.new("June 2010 - December 2012", "Digital Design Limited",
|
52
|
+
["Ongoing ASP.NET website development using C#.",
|
53
|
+
"Developed CRM web application using SQL Server 2008.",
|
54
|
+
"SQL Server Reporting.",
|
55
|
+
"Helped junior developers gain understanding of C# and .NET framework and apply this accordingly."],
|
56
|
+
"Software Engineer for this financial services provider."),
|
57
|
+
position.new("June 2008 - June 2010", "Development Consultancy Limited",
|
58
|
+
["Development of new features and testing of functionality.",
|
59
|
+
"Assisted in development and documentation of several ASP.NET based applications.",
|
60
|
+
"Web application maintenance.",
|
61
|
+
"Ensured development was signed off prior to unit testing.",
|
62
|
+
"Liaised with various service providers."])
|
63
|
+
],
|
64
|
+
languages: [language.new("English", "native speaker"),
|
65
|
+
language.new("German", "fluent"),
|
66
|
+
language.new("French", "basics"),
|
67
|
+
],
|
68
|
+
about_me: Sablon.content(:markdown, "I am fond of writing *short stories* and *poems* in my spare time, and have won several literary contests in pursuit of my **passion**."),
|
69
|
+
activities: ["Writing", "Photography", "Traveling"],
|
70
|
+
referees: [
|
71
|
+
referee.new("Mary P. Larsen", "Strongbod",
|
72
|
+
"Organizational development consultant", "509-471-9365"),
|
73
|
+
referee.new("Jeanne P. Eldridge", "Widdmann",
|
74
|
+
"Information designer", "530-376-1628")
|
75
|
+
]
|
34
76
|
}
|
77
|
+
|
35
78
|
properties = {
|
36
79
|
start_page_number: 7
|
37
80
|
}
|
81
|
+
|
38
82
|
template.render_to_file @output_path, context, properties
|
39
83
|
|
40
|
-
assert_docx_equal @
|
84
|
+
assert_docx_equal @sample_path, @output_path
|
41
85
|
end
|
42
86
|
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.
|
4
|
+
version: 0.0.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yves Senn
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-04-
|
11
|
+
date: 2015-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: redcarpet
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.2'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: bundler
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -119,23 +133,25 @@ files:
|
|
119
133
|
- lib/sablon/parser/mail_merge.rb
|
120
134
|
- lib/sablon/processor.rb
|
121
135
|
- lib/sablon/processor/section_properties.rb
|
136
|
+
- lib/sablon/redcarpet/render/word_ml.rb
|
122
137
|
- lib/sablon/template.rb
|
123
138
|
- lib/sablon/test.rb
|
124
139
|
- lib/sablon/test/assertions.rb
|
125
140
|
- lib/sablon/version.rb
|
126
|
-
- misc/
|
127
|
-
- misc/
|
141
|
+
- misc/cv_sample.png
|
142
|
+
- misc/cv_template.png
|
143
|
+
- misc/recipe_sample.png
|
144
|
+
- misc/recipe_template.png
|
128
145
|
- sablon.gemspec
|
129
146
|
- test/content_test.rb
|
130
147
|
- test/context_test.rb
|
131
148
|
- test/executable_test.rb
|
132
149
|
- test/expression_test.rb
|
133
|
-
- test/fixtures/
|
134
|
-
- test/fixtures/
|
135
|
-
- test/fixtures/
|
136
|
-
- test/fixtures/
|
137
|
-
- test/fixtures/
|
138
|
-
- test/fixtures/xml/about_me_snippet.xml
|
150
|
+
- test/fixtures/cv_sample.docx
|
151
|
+
- test/fixtures/cv_template.docx
|
152
|
+
- test/fixtures/recipe_context.json
|
153
|
+
- test/fixtures/recipe_sample.docx
|
154
|
+
- test/fixtures/recipe_template.docx
|
139
155
|
- test/fixtures/xml/complex_field.xml
|
140
156
|
- test/fixtures/xml/conditional.xml
|
141
157
|
- test/fixtures/xml/conditional_with_predicate.xml
|
@@ -151,6 +167,7 @@ files:
|
|
151
167
|
- test/fixtures/xml/table_row_loop.xml
|
152
168
|
- test/mail_merge_parser_test.rb
|
153
169
|
- test/processor_test.rb
|
170
|
+
- test/redcarpet_render_word_ml_test.rb
|
154
171
|
- test/sablon_test.rb
|
155
172
|
- test/sandbox/.gitkeep
|
156
173
|
- test/section_properties_test.rb
|
@@ -186,12 +203,11 @@ test_files:
|
|
186
203
|
- test/context_test.rb
|
187
204
|
- test/executable_test.rb
|
188
205
|
- test/expression_test.rb
|
189
|
-
- test/fixtures/
|
190
|
-
- test/fixtures/
|
191
|
-
- test/fixtures/
|
192
|
-
- test/fixtures/
|
193
|
-
- test/fixtures/
|
194
|
-
- test/fixtures/xml/about_me_snippet.xml
|
206
|
+
- test/fixtures/cv_sample.docx
|
207
|
+
- test/fixtures/cv_template.docx
|
208
|
+
- test/fixtures/recipe_context.json
|
209
|
+
- test/fixtures/recipe_sample.docx
|
210
|
+
- test/fixtures/recipe_template.docx
|
195
211
|
- test/fixtures/xml/complex_field.xml
|
196
212
|
- test/fixtures/xml/conditional.xml
|
197
213
|
- test/fixtures/xml/conditional_with_predicate.xml
|
@@ -207,6 +223,7 @@ test_files:
|
|
207
223
|
- test/fixtures/xml/table_row_loop.xml
|
208
224
|
- test/mail_merge_parser_test.rb
|
209
225
|
- test/processor_test.rb
|
226
|
+
- test/redcarpet_render_word_ml_test.rb
|
210
227
|
- test/sablon_test.rb
|
211
228
|
- test/sandbox/.gitkeep
|
212
229
|
- test/section_properties_test.rb
|
data/misc/output.png
DELETED
Binary file
|
data/misc/template.png
DELETED
Binary file
|
Binary file
|
Binary file
|
@@ -1,15 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"title": "Shopping List",
|
3
|
-
"details": {
|
4
|
-
"author": {
|
5
|
-
"first_name": "John",
|
6
|
-
"last_name": "Doe"
|
7
|
-
},
|
8
|
-
"word_ml:description": "<w:p><w:r><w:t xml:space=\"preserve\">A list of items to buy at the next </w:t></w:r><w:r><w:rPr><w:b /></w:rPr><w:t>Shop</w:t></w:r><w:r><w:t xml:space=\"preserve\">.</w:t></w:r></w:p>"
|
9
|
-
},
|
10
|
-
"items": [
|
11
|
-
{"name": "Milk", "amount": "1L"},
|
12
|
-
{"name": "Sugar", "amount": "1kg"},
|
13
|
-
{"name": "Tomatoes", "amount": "500g"}
|
14
|
-
]
|
15
|
-
}
|
Binary file
|
Binary file
|
@@ -1 +0,0 @@
|
|
1
|
-
<w:p><w:r><w:t xml:space="preserve">I </w:t></w:r><w:r><w:rPr><w:i /></w:rPr><w:t>am</w:t></w:r><w:r><w:t xml:space="preserve"> </w:t></w:r></w:p><w:p><w:r><w:t xml:space="preserve">a </w:t></w:r><w:r><w:rPr><w:b /></w:rPr><w:t>Software Developer</w:t></w:r></w:p>
|