sheng 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitattributes +1 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.watchr +99 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +62 -0
- data/Rakefile +10 -0
- data/docs/creating_templates.docx +0 -0
- data/docs/creating_templates.md +392 -0
- data/lib/sheng/block.rb +59 -0
- data/lib/sheng/check_box.rb +43 -0
- data/lib/sheng/conditional_block.rb +30 -0
- data/lib/sheng/data_set.rb +45 -0
- data/lib/sheng/docx.rb +75 -0
- data/lib/sheng/merge_field.rb +208 -0
- data/lib/sheng/merge_field_set.rb +90 -0
- data/lib/sheng/path_helpers.rb +15 -0
- data/lib/sheng/sequence.rb +66 -0
- data/lib/sheng/support.rb +57 -0
- data/lib/sheng/version.rb +3 -0
- data/lib/sheng/wml_file.rb +47 -0
- data/lib/sheng.rb +21 -0
- data/sheng.gemspec +31 -0
- data/spec/fixtures/bad_docx_files/with_field_not_in_dataset.docx +0 -0
- data/spec/fixtures/bad_docx_files/with_missing_sequence_start.docx +0 -0
- data/spec/fixtures/bad_docx_files/with_old_mergefields.docx +0 -0
- data/spec/fixtures/bad_docx_files/with_poorly_nested_sequences.docx +0 -0
- data/spec/fixtures/bad_docx_files/with_unended_sequence.docx +0 -0
- data/spec/fixtures/docx_files/input_document.docx +0 -0
- data/spec/fixtures/docx_files/old_style/input_document.docx +0 -0
- data/spec/fixtures/docx_files/old_style/output_document.docx +0 -0
- data/spec/fixtures/docx_files/output_document.docx +0 -0
- data/spec/fixtures/inputs/complete.json +61 -0
- data/spec/fixtures/inputs/incomplete.json +52 -0
- data/spec/fixtures/trees/embedded_sequence.yml +13 -0
- data/spec/fixtures/trees/merge_field_set.yml +16 -0
- data/spec/fixtures/xml_fragments/input/check_box/check_box.xml +11 -0
- data/spec/fixtures/xml_fragments/input/conditional_block/bad/badly_nested_conditional.xml +105 -0
- data/spec/fixtures/xml_fragments/input/conditional_block/bad/unclosed_conditional.xml +35 -0
- data/spec/fixtures/xml_fragments/input/conditional_block/conditional_block_if.xml +84 -0
- data/spec/fixtures/xml_fragments/input/conditional_block/conditional_block_inline.xml +59 -0
- data/spec/fixtures/xml_fragments/input/conditional_block/conditional_block_unless.xml +51 -0
- data/spec/fixtures/xml_fragments/input/conditional_block/conditional_in_table.xml +176 -0
- data/spec/fixtures/xml_fragments/input/conditional_block/embedded_conditional.xml +79 -0
- data/spec/fixtures/xml_fragments/input/merge_field/bad/not_a_real_mergefield_new.xml +19 -0
- data/spec/fixtures/xml_fragments/input/merge_field/bad/not_a_real_mergefield_old.xml +9 -0
- data/spec/fixtures/xml_fragments/input/merge_field/bad/unclosed_merge_field.xml +25 -0
- data/spec/fixtures/xml_fragments/input/merge_field/inline_merge_field.xml +26 -0
- data/spec/fixtures/xml_fragments/input/merge_field/merge_field.xml +14 -0
- data/spec/fixtures/xml_fragments/input/merge_field/new_merge_field.xml +28 -0
- data/spec/fixtures/xml_fragments/input/merge_field/split_merge_field.xml +33 -0
- data/spec/fixtures/xml_fragments/input/merge_field_set/complex_nesting_and_reuse.xml +231 -0
- data/spec/fixtures/xml_fragments/input/merge_field_set/merge_field_set.xml +89 -0
- data/spec/fixtures/xml_fragments/input/merge_field_set/with_non_mergefield_fields.xml +43 -0
- data/spec/fixtures/xml_fragments/input/sequence/array_sequence.xml +23 -0
- data/spec/fixtures/xml_fragments/input/sequence/bad/badly_nested_sequence.xml +62 -0
- data/spec/fixtures/xml_fragments/input/sequence/bad/unclosed_sequence.xml +32 -0
- data/spec/fixtures/xml_fragments/input/sequence/embedded_sequence.xml +68 -0
- data/spec/fixtures/xml_fragments/input/sequence/inline_sequence.xml +24 -0
- data/spec/fixtures/xml_fragments/input/sequence/overridden_iterator_array_sequence.xml +23 -0
- data/spec/fixtures/xml_fragments/input/sequence/sequence.xml +39 -0
- data/spec/fixtures/xml_fragments/input/sequence/sequence_in_table.xml +125 -0
- data/spec/fixtures/xml_fragments/input/sequence/sequence_with_section_formatting.xml +41 -0
- data/spec/fixtures/xml_fragments/input/sequence/series_with_commas.xml +73 -0
- data/spec/fixtures/xml_fragments/output/check_box/check_box.xml +11 -0
- data/spec/fixtures/xml_fragments/output/conditional_block/conditional_in_table_does_not_exist.xml +52 -0
- data/spec/fixtures/xml_fragments/output/conditional_block/conditional_in_table_exists.xml +84 -0
- data/spec/fixtures/xml_fragments/output/conditional_block/embedded_conditional_both.xml +11 -0
- data/spec/fixtures/xml_fragments/output/conditional_block/embedded_conditional_inside.xml +5 -0
- data/spec/fixtures/xml_fragments/output/conditional_block/embedded_conditional_outside.xml +8 -0
- data/spec/fixtures/xml_fragments/output/conditional_block/if_does_not_exist.xml +5 -0
- data/spec/fixtures/xml_fragments/output/conditional_block/if_exists.xml +19 -0
- data/spec/fixtures/xml_fragments/output/conditional_block/inline_does_not_exist.xml +10 -0
- data/spec/fixtures/xml_fragments/output/conditional_block/inline_exists.xml +13 -0
- data/spec/fixtures/xml_fragments/output/conditional_block/unless_does_not_exist.xml +11 -0
- data/spec/fixtures/xml_fragments/output/conditional_block/unless_exists.xml +5 -0
- data/spec/fixtures/xml_fragments/output/merge_field/inline_merge_field.xml +11 -0
- data/spec/fixtures/xml_fragments/output/merge_field/merge_field.xml +12 -0
- data/spec/fixtures/xml_fragments/output/merge_field/split_merge_field.xml +10 -0
- data/spec/fixtures/xml_fragments/output/merge_field_set/complex_nesting_and_reuse.xml +190 -0
- data/spec/fixtures/xml_fragments/output/merge_field_set/merge_field_set.xml +75 -0
- data/spec/fixtures/xml_fragments/output/merge_field_set/with_non_mergefield_fields.xml +31 -0
- data/spec/fixtures/xml_fragments/output/sequence/array_sequence.xml +17 -0
- data/spec/fixtures/xml_fragments/output/sequence/embedded_sequence.xml +56 -0
- data/spec/fixtures/xml_fragments/output/sequence/inline_sequence.xml +16 -0
- data/spec/fixtures/xml_fragments/output/sequence/overridden_iterator_array_sequence.xml +12 -0
- data/spec/fixtures/xml_fragments/output/sequence/sequence.xml +28 -0
- data/spec/fixtures/xml_fragments/output/sequence/sequence_in_table.xml +120 -0
- data/spec/fixtures/xml_fragments/output/sequence/sequence_with_section_formatting.xml +46 -0
- data/spec/fixtures/xml_fragments/output/sequence/series_with_commas.xml +43 -0
- data/spec/fixtures/xml_fragments/output/sequence/series_with_commas_two_items.xml +31 -0
- data/spec/lib/sheng/check_box_spec.rb +87 -0
- data/spec/lib/sheng/conditional_block_spec.rb +151 -0
- data/spec/lib/sheng/data_set_spec.rb +65 -0
- data/spec/lib/sheng/docx_spec.rb +146 -0
- data/spec/lib/sheng/merge_field_set_spec.rb +165 -0
- data/spec/lib/sheng/merge_field_spec.rb +276 -0
- data/spec/lib/sheng/sequence_spec.rb +201 -0
- data/spec/lib/sheng/wml_file_spec.rb +38 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/path_helper.rb +15 -0
- data/spec/support/xml_helper.rb +28 -0
- metadata +355 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7386c3df4fdcb5b5f1e28da4fb95113f0917da2f
|
4
|
+
data.tar.gz: 909c428a901d9929d3a1e544e0a27b8f8c962cb1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e0a14147b7127d05ed9888367ee724bb8aa34e4f28573a6d79a52cf4ca999a81e14c6b088861c7b7c11dc28a36db1a1bcfdd7274286d09342748287b72f30792
|
7
|
+
data.tar.gz: 7ff88f50e5c6dfb3e4ed0931955bae7a2f3bce43202dbdaa63490d2498091fe7f372c173e961754f453f76c96b439c5416cfe96e4ec5466c252d14e3f7b701fc
|
data/.gitattributes
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.docx diff=pandoc
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.1
|
data/.watchr
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
if __FILE__ == $0
|
2
|
+
puts "Run with: watchr #{__FILE__}. \n\nRequired gems: watchr rev"
|
3
|
+
exit 1
|
4
|
+
end
|
5
|
+
|
6
|
+
# --------------------------------------------------
|
7
|
+
# Convenience Methods
|
8
|
+
# --------------------------------------------------
|
9
|
+
def run(cmd)
|
10
|
+
sleep(2)
|
11
|
+
puts("%s %s [%s]" % ["|\n" * 5 , cmd , Time.now.to_s])
|
12
|
+
$last_test = cmd
|
13
|
+
system(cmd)
|
14
|
+
end
|
15
|
+
|
16
|
+
def run_all_specs
|
17
|
+
tags = "--tag #{ARGV[1]}" if ARGV[1]
|
18
|
+
run "bundle exec rake -s spec SPEC_OPTS=' --order rand #{tags.to_s}'"
|
19
|
+
end
|
20
|
+
|
21
|
+
def run_last_test
|
22
|
+
run($last_test)
|
23
|
+
end
|
24
|
+
|
25
|
+
def run_single_spec *spec
|
26
|
+
tags = "--tag #{ARGV[1]}" if ARGV[1]
|
27
|
+
spec = spec.join(' ')
|
28
|
+
run "bundle exec rspec #{spec} --order rand #{tags}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def run_specs_with_shared_examples(shared_example_filename, spec_path = 'spec')
|
32
|
+
|
33
|
+
# Returns the names of the shared examples in filename
|
34
|
+
def shared_examples(filename)
|
35
|
+
lines = File.readlines(filename)
|
36
|
+
lines.grep(/shared_examples_for[\s'"]+(.+)['"]\s*[do|\{]/) do |matching_line|
|
37
|
+
$1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns array with filenames of the specs using shared_example
|
42
|
+
def specs_with_shared_example(shared_example, path)
|
43
|
+
command = "grep -lrE 'it_should_behave_like .(#{shared_example}).' #{path}"
|
44
|
+
`#{command}`.split
|
45
|
+
end
|
46
|
+
|
47
|
+
shared_examples(shared_example_filename).each do |shared_example|
|
48
|
+
specs_to_run = specs_with_shared_example(shared_example, spec_path)
|
49
|
+
run_single_spec(specs_to_run) unless specs_to_run.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
def run_cucumber_scenario scenario_path
|
55
|
+
if scenario_path !~ /.*\.feature$/
|
56
|
+
scenario_path = $last_scenario
|
57
|
+
end
|
58
|
+
$last_scenario = scenario_path
|
59
|
+
run "bundle exec cucumber #{scenario_path} --tags @dev"
|
60
|
+
end
|
61
|
+
|
62
|
+
# --------------------------------------------------
|
63
|
+
# Watchr Rules
|
64
|
+
# --------------------------------------------------
|
65
|
+
watch( '^spec/spec_helper\.rb' ) { run_all_specs }
|
66
|
+
watch( '^spec/shared_behaviors/.*\.rb' ) { |m| run_specs_with_shared_examples(m[0]) }
|
67
|
+
watch( '^spec/.*_spec\.rb' ) { |m| run_single_spec(m[0]) }
|
68
|
+
watch( '^spec/.*_spec\.rb' ) { |m| run_single_spec(m[0]) }
|
69
|
+
watch( '^spec/requests/requests_spec_helper.*' ) { run_last_test }
|
70
|
+
watch( '^importers/.*' ) { |m| run_last_test }
|
71
|
+
watch( '^spec/factories.*' ) { |m| run_last_test }
|
72
|
+
watch( '^test_harness/.*' ) { |m| run_last_test }
|
73
|
+
watch( '^models/.*' ) { |m| run_last_test }
|
74
|
+
watch( '^presenters/.*' ) { |m| run_last_test }
|
75
|
+
watch( '^lib/(.*)\.rb' ) { |m| run_last_test }
|
76
|
+
watch( '^app/(.*)\.rb' ) { run_last_test }
|
77
|
+
watch( '^features/*/.*' ) { |m| run_cucumber_scenario(m[0]) }
|
78
|
+
watch( '^test-harness/*/.*' ) { |m| run_cucumber_scenario(m[0]) }
|
79
|
+
|
80
|
+
|
81
|
+
# --------------------------------------------------
|
82
|
+
# Signal Handling
|
83
|
+
# --------------------------------------------------
|
84
|
+
# Ctrl-\
|
85
|
+
Signal.trap('QUIT') do
|
86
|
+
puts " --- Running all tests ---\n\n"
|
87
|
+
run_all_specs
|
88
|
+
end
|
89
|
+
|
90
|
+
# Ctrl-T
|
91
|
+
Signal.trap('TSTP') do
|
92
|
+
puts " --- Running last test --\n\n"
|
93
|
+
run_cucumber_scenario nil
|
94
|
+
end
|
95
|
+
|
96
|
+
# Ctrl-C
|
97
|
+
Signal.trap('INT') { abort("\n") }
|
98
|
+
|
99
|
+
puts "Watching.."
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Renewable Funding
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# Sheng
|
2
|
+
|
3
|
+
A Ruby gem for data merging Word documents. Given a set of data (as a Hash), and a `.docx` template created in [a specific way](docs/creating_templates.md), you can generate a new Word document with the values from the data substituted in place.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'sheng'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install sheng
|
18
|
+
|
19
|
+
## Quick Start
|
20
|
+
|
21
|
+
#### 1. Create Your Template
|
22
|
+
Follow the instructions in [creating_templates.docx](https://github.com/renewablefunding/sheng/raw/master/docs/creating_templates.docx) (there's also a Markdown version here: [creating_templates.md](docs/creating_templates.md), but it obviously doesn't have example mergefields in it like the `docx` file does, so we recommend the `docx` file). Store the template you created somewhere in the filesystem where you'll have access to it from your Ruby app.
|
23
|
+
|
24
|
+
#### 2. Generate a Data Set Hash
|
25
|
+
In your application, generate a data set Hash (see [the relevant section in the creation instructions](docs/creating_templates.md#the-data-set) to learn what a data set should look like); for the purposes of these instructions, we'll assume you stored that Hash in a variable called `data_set`.
|
26
|
+
|
27
|
+
#### 3. Write Some Code
|
28
|
+
```ruby
|
29
|
+
docx = Sheng::Docx.new("path/to/template.docx", data_set)
|
30
|
+
docx.generate("path/to/store/merged/document.docx")
|
31
|
+
```
|
32
|
+
|
33
|
+
#### 4. Rejoice
|
34
|
+
\\(• ◡ •)/
|
35
|
+
|
36
|
+
## Other Helpful Features
|
37
|
+
|
38
|
+
### Generating a Hash of Required Data From a Template
|
39
|
+
To generate a list of all the mergefield variables expected to be substituted (this can be helpful for developers to ensure they're providing all the necessary data after a template author has added their mergefields):
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
docx = Sheng::Docx.new("path/to/template.docx", {})
|
43
|
+
docx.required_hash
|
44
|
+
```
|
45
|
+
|
46
|
+
You'll get, in response, a Hash that you'll want to imitate. Note that for now, the *values* in the hash won't be very meaningful (they'll just be nils and empty arrays), but we plan on adding helpful metadata here about how the fields are actually being used in the document.
|
47
|
+
|
48
|
+
### Viewing a Tree of All Mergefields in a Template
|
49
|
+
This method will show you the actual raw keys being used in the template (including any filters), and also what kind of node Sheng instantiated for each mergefield it encountered (check box, sequence, etc).
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
docx = Sheng::Docx.new("path/to/template.docx", {})
|
53
|
+
docx.to_tree
|
54
|
+
```
|
55
|
+
|
56
|
+
## Contributing
|
57
|
+
|
58
|
+
1. Fork it
|
59
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
60
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
61
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
62
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
Binary file
|
@@ -0,0 +1,392 @@
|
|
1
|
+
# Sheng
|
2
|
+
|
3
|
+
Thank you for using Sheng, our Ruby gem for data merging Word documents! We've designed Sheng to be intuitive (both for developers and document template authors), but there are some guidelines you need to follow to make the best use of its powerful features.
|
4
|
+
|
5
|
+
This reference is written for template authors, so you won't need any Ruby programming knowledge to use it. All you'll need is a copy of Microsoft Word. You'll be creating Word MergeFields, but we'll give you instructions on how to do so.
|
6
|
+
|
7
|
+
Note that any instructions on how specifically to use certain commands in Word are based on the version of Word that comes with Microsoft Office 2011 for Mac, but they should be the same or similar in recent versions on either platform.
|
8
|
+
|
9
|
+
# Table of Contents
|
10
|
+
|
11
|
+
* [Sheng](#sheng)
|
12
|
+
* [The Data Set](#the-data-set)
|
13
|
+
* [An Example JSON Data Set](#an-example-json-data-set)
|
14
|
+
* [A JSON Reference Data Set](#a-json-reference-data-set)
|
15
|
+
* [Accessing the Data Set: Finding Variable Names](#accessing-the-data-set-finding-variable-names)
|
16
|
+
* [Creating MergeFields in Microsoft Word](#creating-mergefields-in-microsoft-word)
|
17
|
+
* [Editing an Existing MergeField](#editing-an-existing-mergefield)
|
18
|
+
* [Basic MergeField Substitution](#basic-mergefield-substitution)
|
19
|
+
* [Inline Basic MergeFields](#inline-basic-mergefields)
|
20
|
+
* [Filters on String Values](#filters-on-string-values)
|
21
|
+
* [Checkboxes](#checkboxes)
|
22
|
+
* [Sequences](#sequences)
|
23
|
+
* [Arrays of Primitives](#arrays-of-primitives)
|
24
|
+
* [Inline Sequences](#inline-sequences)
|
25
|
+
* [Comma-Separated Series](#comma-separated-series)
|
26
|
+
* [Sequences in Tables](#sequences-in-tables)
|
27
|
+
* [Embedded Sequences](#embedded-sequences)
|
28
|
+
* [Conditional Blocks](#conditional-blocks)
|
29
|
+
|
30
|
+
# The Data Set
|
31
|
+
|
32
|
+
Before we get started on how Sheng substitutes values into Word document templates, let's talk about where Sheng **gets** these values: the data set. The data set is basically a list of all of the possible dynamic variables you can use in your templates, and will be provided by your developers. The data set your developers will provide you will have meaningful placeholder values for each dynamic variable, and soon you'll learn more about those placeholder values. But for now, let's look at something a bit simpler.
|
33
|
+
|
34
|
+
## An Example JSON Data Set
|
35
|
+
|
36
|
+
This is an example of an **actual** data set, with real values instead of placeholder values, that Sheng would use to populate a document template:
|
37
|
+
|
38
|
+
```
|
39
|
+
{
|
40
|
+
"last_user": {
|
41
|
+
"first_name": "Pat",
|
42
|
+
"last_name": "Shoeshine",
|
43
|
+
"phone_numbers": [
|
44
|
+
{
|
45
|
+
"number": "555-123-1234",
|
46
|
+
"is_mobile": true
|
47
|
+
},
|
48
|
+
{
|
49
|
+
"number": "555-456-4567",
|
50
|
+
"is_mobile": false
|
51
|
+
}
|
52
|
+
],
|
53
|
+
"favorite_colors": ["red", "white", "green"]
|
54
|
+
},
|
55
|
+
"website_language": "English",
|
56
|
+
"daily_visits": 10,
|
57
|
+
"percent_of_clicks": 83.14,
|
58
|
+
...
|
59
|
+
}
|
60
|
+
```
|
61
|
+
|
62
|
+
The format you see above is called JSON (JavaScript Object Notation). It's commonly used to send data between different services on the internet, so it's designed to be easy for a computer to generate and parse, but it's also designed to be easy for humans to read and write.
|
63
|
+
|
64
|
+
A block of JSON text contains, at the root level, a single object. Objects, which are always surrounded by curly braces, are a set of key-value pairs. The root level object above has three key-value pairs – the keys are "last_user", "website_language", and "visits". Each of those keys is followed by a colon, then the actual value. A value can be any of the following:
|
65
|
+
|
66
|
+
* A string (in double quotes)
|
67
|
+
* A number (integer or decimal)
|
68
|
+
* A "Boolean" value (true or false, not in quotes)
|
69
|
+
* An object (surrounded by curly braces, { and })
|
70
|
+
* An array of any of the above values (surrounded by square brackets, [ and ])
|
71
|
+
* The word null, which represents missing or unknown data
|
72
|
+
|
73
|
+
In the example above, the value for "last_user" is another object, and that object in turn has key-value pairs for the user's attributes. The "first_name" and "last_name" values are strings, so they're in double quotes.
|
74
|
+
|
75
|
+
The value for "favorite_colors" is a bit more complicated – because a user can have more than one favorite color, this value is an array (which basically just means an ordered list). It's a group of values separated by commas, just like you'd expect to write a list in plain language, and the whole list is surrounded by square brackets ([ and ]).
|
76
|
+
|
77
|
+
The next key-value pair, "phone_numbers", is even more complicated, but follows the same principles. A user can have more than one phone number, so this value is also an array, but instead of just being an array of strings or numbers, it's an array of objects. This is because each phone number has a few traits of its own – the number itself, and whether or not it's a mobile phone. So each of the two phone number "values", separated by a comma, is an object with two key-value pairs. Notice that the second key-value pair in each phone number object has a "Boolean" value (true or false), and it's not in quotes. You can use this data type for things that are black or white, yes or no questions – in this case, whether the phone number is mobile or not. Other examples might be "agreed_to_terms", "over_18", or "opted_in_to_newsletter". In Sheng, these are mostly useful for automatically checking a checkbox according to the value in the data set, but they'll also display as the words "true" or "false" if not used with a checkbox.
|
78
|
+
|
79
|
+
Finally, there are two other values at the "root" level: "website_language" and "visits". The former is simply another string. The latter, "visits", is an integer, so it doesn't need to be in double quotes.
|
80
|
+
|
81
|
+
And that's it! Now that you know what a sample data set looks like, let's look at what an actual data set reference might look like from your development team.
|
82
|
+
|
83
|
+
## A JSON Reference Data Set
|
84
|
+
|
85
|
+
The example above is great, but your developers will probably send you something a bit more "abstract" – a reference data set that reveals, by definition rather than example, what values you'll be working with. Here's a sample reference data set that correlates to the example we've already seen:
|
86
|
+
|
87
|
+
```
|
88
|
+
{
|
89
|
+
"last_user": {
|
90
|
+
"first_name": "string",
|
91
|
+
"last_name": "string",
|
92
|
+
"phone_numbers": [
|
93
|
+
{
|
94
|
+
"number": "string",
|
95
|
+
"is_mobile": "Boolean"
|
96
|
+
}
|
97
|
+
],
|
98
|
+
"favorite_colors": ["string"]
|
99
|
+
},
|
100
|
+
"website_language": "string",
|
101
|
+
"daily_visits": "integer",
|
102
|
+
"percent_of_clicks": "decimal",
|
103
|
+
...
|
104
|
+
}
|
105
|
+
```
|
106
|
+
|
107
|
+
That looks familiar, right? All the keys are the same, but the values are different. Those values are placeholders that describe what **kind** of values you can expect to receive for that given key. They're mostly intuitive, but here's a quick guide:
|
108
|
+
|
109
|
+
| Placeholder | Description | Example |
|
110
|
+
| --- | --- | --- |
|
111
|
+
| string | An arbitrary string | "Arthur von Blasterface" |
|
112
|
+
| integer | A whole number (no decimal or fraction) | 18493 |
|
113
|
+
| decimal | A number with a decimal component | 6.143 |
|
114
|
+
| Boolean | A "Boolean" value (true or false, not in quotes) | true |
|
115
|
+
|
116
|
+
Note that arrays each only contain one item in the data set reference, but that single item indicates what each value should look like.
|
117
|
+
|
118
|
+
Compare the data set reference above with the first example JSON given, and see how the example adheres to the data types shown in the reference file.
|
119
|
+
|
120
|
+
Phew, that's enough staring at JSON. Well, almost.. we still need to talk about how to access these values when creating your templates.
|
121
|
+
|
122
|
+
## Accessing the Data Set: Finding Variable Names
|
123
|
+
|
124
|
+
When you want to access a certain value, you need to tell Sheng how to find it in the data set. For key-value pairs at the root level, this is easy – you just use the key itself. So, for "visits" in the example above, you use just that – the string "visits".
|
125
|
+
|
126
|
+
However, this gets complicated once you get beyond the root level. How, for example, do you tell Sheng you want the "first_name" of the "last_user" object?
|
127
|
+
|
128
|
+
To do so, you have to combine the hierarchy you want to traverse into a single string, and Sheng uses something we call "dot notation" for this. In dot notation, a single period (.) is an instruction to move down the hierarchy one step. Thus, to get the "first_name" from the "last_user" object, you would create the variable name "last_user.first_name", and Sheng would find the string "Pat".
|
129
|
+
|
130
|
+
What about accessing something inside an array, like the number of the last_user's first phone number? With Sheng templating, you wouldn't access that value directly – you'd do it using sequences, which you'll learn about soon.
|
131
|
+
|
132
|
+
So that's data sets. Now you're ready to start creating document templates!
|
133
|
+
|
134
|
+
# Creating MergeFields in Microsoft Word
|
135
|
+
|
136
|
+
Let's get acquainted with mergefields in Microsoft Word, since you'll be using these to indicate places in your document template where you want data from the data set to be inserted (replacing the mergefield).
|
137
|
+
|
138
|
+
Before inserting a mergefield, look through the data set reference provided by your developers, and find the variable name that corresponds to the value you want inserted.
|
139
|
+
|
140
|
+
To insert a mergefield into your document, place the cursor where you want the new mergefield to appear, then select _Insert > Field…_ from the menu. You'll get a dialog box that looks something like the one on the right.
|
141
|
+
|
142
|
+
Make sure "Mail Merge" is selected from the "Categories:" column on the left, and then select "MergeField" from the "Field names:" column on the right. The "Field codes" text field will change to read "MERGEFIELD". Put the variable name you've selected at the end (ensure there is a space between). Click "OK", and you'll see the variable appear. That's it!
|
143
|
+
|
144
|
+
## Editing an Existing MergeField
|
145
|
+
|
146
|
+
Unfortunately, editing the variable for a mergefield is a bit more complex. Microsoft Word stores two pieces of text when you create a mergefield – one is the label shown in the document template, and the other is the actual mergefield variable, which is hidden by default. When you first create the mergefield, these are the same, but if you try to edit the label shown on the screen (between the « and » characters), you won't actually succeed in changing the variable used for substitution.
|
147
|
+
|
148
|
+
You have two options here – 1) the brute force, but simpler, option; or 2) the more surgical one.
|
149
|
+
|
150
|
+
**Option 1:**
|
151
|
+
|
152
|
+
Just delete the existing mergefield, then recreate it using the steps above. Make sure, when you do this, to select everything including the « and » characters, but nothing more, and then just hit Delete.
|
153
|
+
|
154
|
+
**Option 2:**
|
155
|
+
|
156
|
+
Place your cursor within the mergefield label, then hit Shift + F9. The mergefield will change from something like this:
|
157
|
+
|
158
|
+
«my_merge_field»
|
159
|
+
|
160
|
+
To something like this:
|
161
|
+
|
162
|
+
**{** MERGEFIELD my_merge_field \\* MERGEFORMAT **}**
|
163
|
+
|
164
|
+
Now you can edit the label. **Do not** change anything outside of the label itself – leave everything else as is. So, for example, if you're changing _my_merge_field_ to _another_merge_field_, after you're done editing it should look like this:
|
165
|
+
|
166
|
+
**{** MERGEFIELD another_merge_field \\* MERGEFORMAT **}**
|
167
|
+
|
168
|
+
Once you're finished, make sure your cursor is within the label, and hit Shift + F9 again. Hang on, what happened?! It looks the same as it did before! That's because you changed the important part (the variable), but the label shown in the template is still what it used to be. To force this to update, hit F9 (without the Shift key this time) while your cursor is in the label, and it will automatically update itself to look like this:
|
169
|
+
|
170
|
+
«another_merge_field»
|
171
|
+
|
172
|
+
And that's it! You're probably wishing you used **Option 1** now, aren't you?
|
173
|
+
|
174
|
+
# Basic MergeField Substitution
|
175
|
+
|
176
|
+
Given a single primitive value (a string, integer, Boolean, or floating point number) in the data set, just create a mergefield with that value's variable name, and it will be substituted with the value.
|
177
|
+
|
178
|
+
### Examples:
|
179
|
+
|
180
|
+
«a_basic_string»
|
181
|
+
|
182
|
+
«a_basic_integer»
|
183
|
+
|
184
|
+
«a_basic_boolean»
|
185
|
+
|
186
|
+
«a_basic_float»
|
187
|
+
|
188
|
+
## Inline Basic MergeFields
|
189
|
+
|
190
|
+
Basic mergefields can be placed in the middle of a paragraph of text, and will substitute without breaking the flow of the text.
|
191
|
+
|
192
|
+
### Example:
|
193
|
+
|
194
|
+
The haberdasher, a gentle fellow by the name of «a_basic_string», meticulously pinned «a_basic_integer» hats.
|
195
|
+
|
196
|
+
## Filters on String Values
|
197
|
+
|
198
|
+
There are some built-in filters available to modify the value of a string variable (e.g. to display a value as fully uppercase regardless of its case in the data set). These filters, if used on non-string values, will have no effect. The filters are:
|
199
|
+
|
200
|
+
| Name | Description | Example Input | Example Output |
|
201
|
+
| --- | --- | --- | --- |
|
202
|
+
| upcase | Uppercases entire string | Doctor special | DOCTOR SPECIAL |
|
203
|
+
| downcase | Downcases entire string | What Is Going ON? | what is going on? |
|
204
|
+
| capitalize | Capitalizes only first letter, downcases rest | how many KIDS do you HAVE? | How many kids do you have? |
|
205
|
+
| titleize | Capitalizes first letter of each word | there is no excuse for BADNESS | There Is No Excuse For Badness |
|
206
|
+
| reverse | Reverses characters in string | This is not a palindrome. | .emordnilap a ton si sihT |
|
207
|
+
|
208
|
+
Filters can also be chained, and will be applied in which they appear (from left to right).
|
209
|
+
|
210
|
+
### Examples:
|
211
|
+
|
212
|
+
«a_basic_string|upcase»
|
213
|
+
|
214
|
+
«a_basic_string|downcase»
|
215
|
+
|
216
|
+
«a_basic_string|capitalize»
|
217
|
+
|
218
|
+
«a_basic_string|titleize»
|
219
|
+
|
220
|
+
«a_basic_string|reverse»
|
221
|
+
|
222
|
+
«a_basic_string|capitalize|reverse»
|
223
|
+
|
224
|
+
«a_basic_integer|downcase»
|
225
|
+
|
226
|
+
# Checkboxes
|
227
|
+
|
228
|
+
To create a checkbox:
|
229
|
+
|
230
|
+
1. Go to the Developer tab in the Ribbon (you may have to turn it on under Preferences > Ribbon), and click on the "Check Box" button.
|
231
|
+
2. When the checkbox appears on the page, double click it, or right-click it and choose "Properties".
|
232
|
+
3. Locate the relevant variable name from the data set, which should be a Boolean (i.e. have a true or false value, without double quotes).
|
233
|
+
4. Find the "Bookmark" field under Field Settings, and enter the variable name in that field.
|
234
|
+
5. Click "OK" to close the dialog box.
|
235
|
+
|
236
|
+
Checkboxes with a missing "Bookmark" value, or with one that doesn't match a variable from the data set, will be left alone.
|
237
|
+
|
238
|
+
### Examples:
|
239
|
+
|
240
|
+
`[ ]` This checkbox will be checked (variable is true)
|
241
|
+
|
242
|
+
`[x]` This checkbox will be unchecked (variable is false)
|
243
|
+
|
244
|
+
`[ ]` This checkbox will be left unchecked (variable is false)
|
245
|
+
|
246
|
+
`[x]` This checkbox will be ignored completely (variable is missing)
|
247
|
+
|
248
|
+
# Sequences
|
249
|
+
|
250
|
+
Sheng's most powerful feature is the sequence. Sequences are used when you have a an array of objects in the data set that you want to iterate over, and repeat a certain block of text (or other element) once for each member of that array. Each member of the array should have key-value pairs itself, to be used for substitution _within_ the repeating section.
|
251
|
+
|
252
|
+
A sequence is created by wrapping the repeating elements with special "bookend" mergefields. These bookend mergefields should be named with "_start:_" and "_end:_" followed by the name of the variable pointing to the desired array. So if your variable is named "birds", your bookend mergefields will look like _«start:birds»_ and _«end:birds»_.
|
253
|
+
|
254
|
+
The repeated block itself can have mergefields in it; these mergefields will be populated using the each member's values. Arrays of basic primitive values (like strings, integers, Booleans, etc) are also allowed; see "Arrays of Primitives" below.
|
255
|
+
|
256
|
+
Within the repeated block, the variable names you concoct should pretend that the member object itself is at the root level. So if you have a data set that looks like this...
|
257
|
+
|
258
|
+
```
|
259
|
+
{
|
260
|
+
"ghosts": [
|
261
|
+
{
|
262
|
+
"name": "string",
|
263
|
+
"age": "integer",
|
264
|
+
"favorites": {
|
265
|
+
"snack": "string",
|
266
|
+
"haunts": ["string"]
|
267
|
+
}
|
268
|
+
}
|
269
|
+
]
|
270
|
+
}
|
271
|
+
```
|
272
|
+
|
273
|
+
... and you're creating a sequence of ghosts, you'd use _«start:ghosts»_ and _«end:ghosts»_ as your bookend mergefields, but in between, you'd just use «name» instead of «ghosts.name». So your mergefields might look like this:
|
274
|
+
|
275
|
+
«start:ghosts»
|
276
|
+
Name: «name»
|
277
|
+
Age: «age»
|
278
|
+
Favorite Snack: «favorites.snack»
|
279
|
+
Favorite haunts: «start:favorites.haunts»«item», «end:favorites.haunts»
|
280
|
+
«end:ghosts»
|
281
|
+
|
282
|
+
Wait, we snuck in something new there, didn't we? That array of haunts – where did the "item" variable come from when trying to iterate on an array of strings? Well, that's coming up in the next section.
|
283
|
+
|
284
|
+
### Examples:
|
285
|
+
|
286
|
+
«start:classroom.students»
|
287
|
+
|
288
|
+
Please welcome «first_name» «last_name» to our institution. «pronoun|capitalize» will be a wonderful addition!
|
289
|
+
|
290
|
+
«end:classroom.students»
|
291
|
+
|
292
|
+
## Arrays of Primitives
|
293
|
+
|
294
|
+
An array does not have to contain objects with key-value pairs; it can simply be a array of primitives (like strings, integers, etc). When using a sequence like this, some variable name must be used within the repeating block to represent the member; by default this will be "item", but this can be overridden using a special "as(_variable_)" filter.
|
295
|
+
|
296
|
+
### Examples:
|
297
|
+
|
298
|
+
«start:adjectives»
|
299
|
+
|
300
|
+
«item»
|
301
|
+
|
302
|
+
«end:adjectives»
|
303
|
+
|
304
|
+
«start:adjectives|as(an_adjective)»
|
305
|
+
|
306
|
+
«an_adjective»
|
307
|
+
|
308
|
+
«end:adjectives»
|
309
|
+
|
310
|
+
## Inline Sequences
|
311
|
+
|
312
|
+
Sequences can be placed in the middle of a paragraph of text, and will iterate and substitute without breaking the flow of the text.
|
313
|
+
|
314
|
+
### Example:
|
315
|
+
|
316
|
+
The chemist was thrilled when she produced a «start:adjectives»«item» «end:adjectives»mammoth from her test tube.
|
317
|
+
|
318
|
+
## Comma-Separated Series
|
319
|
+
|
320
|
+
A special filter is also available on sequences that will automatically turn the repeated block into a member of a comma-separated series, complete with conjunction. The conjunction defaults to the English "and", but can be customized with an argument. Note that currently, an Oxford comma will always be used.
|
321
|
+
|
322
|
+
### Example:
|
323
|
+
|
324
|
+
The chemist and the haberdasher partnered to create hats for the newly spawned mammoth: «start:adjectives|series_with_commas|as(hat_type)»a «hat_type» one«end:adjectives».
|
325
|
+
|
326
|
+
El químico y la mercería se asociaron para crear sombreros para el mamut recién engendrado: «start:español.adjetivos|series_with_commas(y)»uno «item»«end:español.adjetivos».
|
327
|
+
|
328
|
+
## Sequences in Tables
|
329
|
+
|
330
|
+
Using a sequence in a table is easy, but you must follow some specific rules for how to set up your table. The "bookend" markers must be on rows by themselves, bracketing the row(s) to be repeated, in the first column of their row (the other column(s) should remain empty). Your member substitution mergefields can appear anywhere in the row(s) between, even separated onto multiple rows.
|
331
|
+
|
332
|
+
### Examples:
|
333
|
+
|
334
|
+
| Last Name | First Name | Favorite Juice | Smart? |
|
335
|
+
| --- | --- | --- | --- |
|
336
|
+
| «start:classroom.students» | | | |
|
337
|
+
| «last_name|upcase» | **«first_name»** | «fruit» juice | `[x]`|
|
338
|
+
| «end:classroom.students» | | | |
|
339
|
+
| _All students above are special, even if not smart._ |
|
340
|
+
|
341
|
+
## Embedded Sequences
|
342
|
+
|
343
|
+
Any block in Sheng can be nested in another block, theoretically as deeply as you want. Therefore, if you have members of a array who, themselves, contain arrays, you can embed a sequence within another sequence and the result should be intuitive.
|
344
|
+
|
345
|
+
### Examples:
|
346
|
+
|
347
|
+
The 2016 Student List:
|
348
|
+
|
349
|
+
- «start:classroom.students»
|
350
|
+
- «first_name» «last_name»; «pronoun|capitalize» grades:
|
351
|
+
- «start:classes»
|
352
|
+
- «name»: «grade»
|
353
|
+
- «end:classes»
|
354
|
+
- «end:classroom.students»
|
355
|
+
|
356
|
+
# Conditional Blocks
|
357
|
+
|
358
|
+
If you have a section in your document that should or should not display based on the existence of a certain variable, you can use conditional blocks. These come in two flavors – _if:_ and _unless:_ – and are used as expected, given their names.
|
359
|
+
|
360
|
+
Anything within an _if:_ block will only be included if the given variable exists (and is not an empty array).
|
361
|
+
|
362
|
+
The _unless:_ block is the inverse – if the given variable exists, the block will **not** be included.
|
363
|
+
|
364
|
+
The bookend mergefields for conditional blocks are similar to those for sequences, but instead of _start:_ and _end:_, you use _if:_ and _end_if:_ (or _unless:_ and _end_unless:_ for an unless block).
|
365
|
+
|
366
|
+
As seen in the example below, sequences can be nested within conditional blocks, and vice versa, creating a powerful way to tailor your document based on the data.
|
367
|
+
|
368
|
+
### Examples:
|
369
|
+
|
370
|
+
«if:classroom.students»
|
371
|
+
|
372
|
+
We have a bunch of students! Their names are «start:classroom.students|series_with_commas»«first_name» «if:nickname»"«nickname»" «end_if:nickname»«last_name»«end:classroom.students».
|
373
|
+
|
374
|
+
«end_if:classroom.students»
|
375
|
+
|
376
|
+
«unless:classroom.students»
|
377
|
+
|
378
|
+
Oh dear. We have no students.
|
379
|
+
|
380
|
+
«end_unless:classroom.students»
|
381
|
+
|
382
|
+
«if:classroom.teachers»
|
383
|
+
|
384
|
+
We have a bunch of teachers! Their names are «start:classroom.teachers|series_with_commas»«first_name» «if:nickname»"«nickname»" «end_if:nickname»«last_name»«end:classroom.teachers».
|
385
|
+
|
386
|
+
«end_if:classroom.teachers»
|
387
|
+
|
388
|
+
«unless:classroom.teachers»
|
389
|
+
|
390
|
+
Oh dear. We have no teachers.
|
391
|
+
|
392
|
+
«end_unless:classroom.teachers»
|