sablon 0.0.21 → 0.0.22
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8dce87aaca368f43d657f2ff7fe7249e6f46ddee
|
4
|
+
data.tar.gz: 8fddd1630ba575d38c6ab790d82ee33e88c33b65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f9b5dfcec4a943d674e4144cfa9d545a99340d14592b4e6aa09e19199ae363f919d182473004787ca43e7153ffa1bd48bdfd828d83bb3474ee811feb301c8ba
|
7
|
+
data.tar.gz: 592faea43070727caf1608ce097aa10fc43f0821c0614a658c7a4af3474a37e501d90115d2d5ad54cdf6303250ed2a7ba2cb21ab6949d79d0cef4fabbe6bf26f
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
sablon (0.0.
|
4
|
+
sablon (0.0.22)
|
5
5
|
nokogiri (>= 1.6.0)
|
6
|
-
rubyzip (>= 1.1)
|
6
|
+
rubyzip (>= 1.1.1)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
mini_portile2 (2.
|
12
|
-
minitest (5.
|
13
|
-
nokogiri (1.
|
14
|
-
mini_portile2 (~> 2.
|
15
|
-
rake (
|
11
|
+
mini_portile2 (2.3.0)
|
12
|
+
minitest (5.10.3)
|
13
|
+
nokogiri (1.8.1)
|
14
|
+
mini_portile2 (~> 2.3.0)
|
15
|
+
rake (12.3.0)
|
16
16
|
rubyzip (1.2.1)
|
17
17
|
xml-simple (1.1.5)
|
18
18
|
|
@@ -22,9 +22,9 @@ PLATFORMS
|
|
22
22
|
DEPENDENCIES
|
23
23
|
bundler (>= 1.6)
|
24
24
|
minitest (~> 5.4)
|
25
|
-
rake (~>
|
25
|
+
rake (~> 12.0)
|
26
26
|
sablon!
|
27
27
|
xml-simple
|
28
28
|
|
29
29
|
BUNDLED WITH
|
30
|
-
1.
|
30
|
+
1.16.0
|
data/README.md
CHANGED
@@ -8,6 +8,28 @@ and efficient.
|
|
8
8
|
|
9
9
|
*Note: Sablon is still in early development. Please report if you encounter any issues along the way.*
|
10
10
|
|
11
|
+
#### Table of Contents
|
12
|
+
* [Installation](#installation)
|
13
|
+
* [Usage](#usage)
|
14
|
+
* [Writing Templates](#writing-templates)
|
15
|
+
* [Content Insertion](#content-insertion)
|
16
|
+
* [WordProcessingML](#wordprocessingml)
|
17
|
+
* [HTML](#html)
|
18
|
+
* [Conditionals](#conditionals)
|
19
|
+
* [Loops](#loops)
|
20
|
+
* [Nesting](#nesting)
|
21
|
+
* [Comments](#comments)
|
22
|
+
* [Configuration (Beta)](#configuration-beta)
|
23
|
+
* [Customizing HTML Tag Conversion](#customizing-html-tag-conversion)
|
24
|
+
* [Customizing CSS Style Conversion](#customizing-css-style-conversion)
|
25
|
+
* [Executable](#executable)
|
26
|
+
* [Examples](#examples)
|
27
|
+
* [Using a Ruby script](#using-a-ruby-script)
|
28
|
+
* [Using the sablon executable](#using-the-sablon-executable)
|
29
|
+
* [Contributing](#contributing)
|
30
|
+
* [Inspiration](#inspiration)
|
31
|
+
|
32
|
+
|
11
33
|
## Installation
|
12
34
|
|
13
35
|
Add this line to your application's Gemfile:
|
@@ -102,14 +124,13 @@ IMPORTANT: This feature is very much *experimental*. Currently, the insertion
|
|
102
124
|
will replace the containing paragraph. This means that other content in the same
|
103
125
|
paragraph is discarded.
|
104
126
|
|
105
|
-
##### HTML
|
127
|
+
##### HTML
|
106
128
|
|
107
|
-
Similar to WordProcessingML it's possible to use html as input while processing the
|
108
|
-
tempalte. You don't need to modify your templates, a simple insertion operation
|
129
|
+
Similar to WordProcessingML it's possible to use html as input while processing the template. You don't need to modify your templates, a simple insertion operation
|
109
130
|
is sufficient:
|
110
131
|
|
111
132
|
```
|
112
|
-
«=article
|
133
|
+
«=article»
|
113
134
|
```
|
114
135
|
|
115
136
|
To use HTML insertion prepare the context like so:
|
@@ -118,24 +139,40 @@ To use HTML insertion prepare the context like so:
|
|
118
139
|
html_body = <<-HTML
|
119
140
|
<div>This text can contain <em>additional formatting</em>
|
120
141
|
according to the <strong>HTML</strong> specification.</div>
|
142
|
+
<p style="text-align: right; background-color: #FFFF00">Right aligned
|
143
|
+
content with a yellow background color</p>
|
144
|
+
<div><span style="color: #123456">Inline styles</span> are possible as well</div>
|
121
145
|
HTML
|
122
146
|
context = {
|
123
|
-
article:
|
147
|
+
article: Sablon.content(:html, html_body) }
|
148
|
+
# alternative method using special key format
|
149
|
+
# 'html:article' => html_body
|
124
150
|
}
|
125
151
|
template.render_to_file File.expand_path("~/Desktop/output.docx"), context
|
126
152
|
```
|
127
153
|
|
128
|
-
Currently HTML insertion is
|
129
|
-
generated by [Trix editor](https://github.com/basecamp/trix).
|
154
|
+
Currently, HTML insertion is somewhat limited. It is recommended that the block level tags such as `p` and `div` are not nested within each other, otherwise the final document may not generate as anticipated. List tags (`ul` and `ol`) and inline tags (`span`, `b`, `em`, etc.) can be nested as deeply as needed.
|
130
155
|
|
131
|
-
|
132
|
-
|
133
|
-
|
156
|
+
Not all tags are supported. Currently supported tags are defined in [configuration.rb](lib/sablon/configuration/configuration.rb) for paragraphs in method `prepare_paragraph` and for text runs in `prepare_run`.
|
157
|
+
|
158
|
+
Basic conversion of CSS inline styles into matching WordML properties in supported through the `style=" ... "` attribute in the HTML markup. Not all possible styles are supported and only a small subset of CSS styles have a direct WordML equivalent. Styles are passed onto nested elements. The currently supported styles are also defined in [configuration.rb](lib/sablon/configuration/configuration.rb) in method `process_style`. Simple toggle properties that aren't directly supported can be added using the `text-decoration: ` style attribute with the proper WordML tag name as the value. Paragraph and Run property reference can be found at:
|
159
|
+
* http://officeopenxml.com/WPparagraphProperties.php
|
160
|
+
* http://officeopenxml.com/WPtextFormatting.php
|
161
|
+
|
162
|
+
If you wish to write out your HTML code in an indented human readable fashion, or you are pulling content from the ERB templating engine in rails the following regular expression can help eliminate extraneous whitespace in the final document.
|
163
|
+
```ruby
|
164
|
+
# combine all white space
|
165
|
+
html_str = html_str.gsub(/\s+/, ' ')
|
166
|
+
# clear any white space between block level tags and other content
|
167
|
+
html_str.gsub(%r{\s*<(/?(?:h\d|div|p|br|ul|ol|li).*?)>\s*}, '<\1>')
|
168
|
+
```
|
169
|
+
|
170
|
+
IMPORTANT: Currently, the insertion will replace the containing paragraph. This means that other content in the same paragraph is discarded.
|
134
171
|
|
135
172
|
|
136
173
|
#### Conditionals
|
137
174
|
|
138
|
-
Sablon can render parts of the template
|
175
|
+
Sablon can render parts of the template conditionally based on the value of a
|
139
176
|
context variable. Conditional fields are inserted around the content.
|
140
177
|
|
141
178
|
```
|
@@ -189,6 +226,78 @@ styles for HTML insertion.
|
|
189
226
|
«endComment»
|
190
227
|
```
|
191
228
|
|
229
|
+
### Configuration (Beta)
|
230
|
+
|
231
|
+
The Sablon::Configuration singleton is a new feature that allows the end user to customize HTML parsing to their needs without needing to fork and edit the source code of the gem. This API is still in a beta state and may be subject to change as future needs are identified beyond HTML conversion.
|
232
|
+
|
233
|
+
The example below show how to expose the configuration instance:
|
234
|
+
```ruby
|
235
|
+
Sablon.configure do |config|
|
236
|
+
# manipulate config object
|
237
|
+
end
|
238
|
+
```
|
239
|
+
|
240
|
+
The default set of registered HTML tags and CSS property conversions are defined in [configuration.rb](lib/sablon/configuration/configuration.rb).
|
241
|
+
|
242
|
+
#### Customizing HTML Tag Conversion
|
243
|
+
|
244
|
+
Any HTML tag can be added using the configuration object even if it needs a custom AST class to handle conversion logic. Simple inline tags that only modify the style of text (i.e. the already supported `<b>` tag) can be added without an AST class as shown below:
|
245
|
+
```ruby
|
246
|
+
Sablon.configure do |config|
|
247
|
+
config.register_html_tag(:bgcyan, :inline, properties: { highlight: 'cyan' })
|
248
|
+
end
|
249
|
+
```
|
250
|
+
The above tag simply adds a background color to text using the `<w:highlight w:val="cyan" />` property.
|
251
|
+
|
252
|
+
|
253
|
+
More complex business logic can be supported by adding a new class under the `Sablon::HTMLConverter` namespace. The new class will likely subclass `Sablon::HTMLConverter::Node` or `Sablon::HTMLConverter::Collection` depending on the needed behavior. The current AST classes serve as additional examples and can be found in [ast.rb](/lib/sablon/html/ast.rb). When registering a new HTML tag that uses a custom AST class the class must be passed in either by name using a lowercased and underscored symbol or the class object itself.
|
254
|
+
|
255
|
+
The block below shows how to register a new HTML tag that adds the following AST class: `Sablon::HTMLConverter::InstrText`.
|
256
|
+
```ruby
|
257
|
+
module Sablon
|
258
|
+
class HTMLConverter
|
259
|
+
class InstrText < Node
|
260
|
+
# implementation details ...
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
# register tag
|
265
|
+
Sablon.configure do |config|
|
266
|
+
config.register_html_tag(:bgcyan, :inline, ast_class: :instr_text)
|
267
|
+
end
|
268
|
+
```
|
269
|
+
|
270
|
+
Existing tags can be overwritten using the `config.register_html_tag` method or removed entirely using `config.remove_html_tag`.
|
271
|
+
```ruby
|
272
|
+
# remove tag
|
273
|
+
Sablon.configure do |config|
|
274
|
+
# remove support for the span tag
|
275
|
+
config.remove_html_tag(:span)
|
276
|
+
end
|
277
|
+
```
|
278
|
+
|
279
|
+
|
280
|
+
#### Customizing CSS Style Conversion
|
281
|
+
|
282
|
+
The conversion of CSS stored in an element's `style="..."` attribute can be customized using the configuration object as well. Adding a new style conversion or overriding an existing one is done using the `config.register_style_converter` method. It accepts three arguments the name of the AST node (as a lowercased and underscored symbol) the style applies to, the name of the CSS property (needs to be a string in most cases) and a lambda that accepts a single argument, the property value. The example below shows how to add a new style that sets the `<w:highlight />` property.
|
283
|
+
```ruby
|
284
|
+
# add style conversion
|
285
|
+
Sablon.configure do |config|
|
286
|
+
# register new conversion for the Sablon::HTMLConverter::Run AST class.
|
287
|
+
converter = lambda { |v| return 'highlight', v }
|
288
|
+
config.register_style_converter(:run, 'custom-highlight', converter)
|
289
|
+
end
|
290
|
+
```
|
291
|
+
|
292
|
+
Existing conversions can be overwritten using the `config.register_style_converter` method or removed entirely using `config.remove_style_converter`.
|
293
|
+
```ruby
|
294
|
+
# remove tag
|
295
|
+
Sablon.configure do |config|
|
296
|
+
# remove support for conversion of font-size for the Run AST class
|
297
|
+
config.remove_style_converter(:run, 'font-size')
|
298
|
+
end
|
299
|
+
```
|
300
|
+
|
192
301
|
### Executable
|
193
302
|
|
194
303
|
The `sablon` executable can be used to process templates on the command-line.
|
data/lib/sablon.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
-
require 'singleton'
|
2
1
|
require 'zip'
|
3
2
|
require 'nokogiri'
|
4
3
|
|
5
4
|
require "sablon/version"
|
5
|
+
require "sablon/configuration/configuration"
|
6
|
+
|
6
7
|
require "sablon/numbering"
|
7
8
|
require "sablon/context"
|
9
|
+
require "sablon/environment"
|
8
10
|
require "sablon/template"
|
9
11
|
require "sablon/processor/document"
|
10
12
|
require "sablon/processor/section_properties"
|
@@ -18,6 +20,10 @@ module Sablon
|
|
18
20
|
class TemplateError < ArgumentError; end
|
19
21
|
class ContextError < ArgumentError; end
|
20
22
|
|
23
|
+
def self.configure
|
24
|
+
yield(Configuration.instance) if block_given?
|
25
|
+
end
|
26
|
+
|
21
27
|
def self.template(path)
|
22
28
|
Template.new(path)
|
23
29
|
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'sablon/configuration/html_tag'
|
3
|
+
|
4
|
+
module Sablon
|
5
|
+
# Handles storing configuration data for the sablon module
|
6
|
+
class Configuration
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
attr_accessor :permitted_html_tags, :defined_style_conversions
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
initialize_html_tags
|
13
|
+
initialize_css_style_conversion
|
14
|
+
end
|
15
|
+
|
16
|
+
# Adds a new tag to the permitted tags hash or replaces an existing one
|
17
|
+
def register_html_tag(tag_name, type = :inline, **options)
|
18
|
+
tag = HTMLTag.new(tag_name, type, **options)
|
19
|
+
@permitted_html_tags[tag.name] = tag
|
20
|
+
end
|
21
|
+
|
22
|
+
# Removes a tag from the permitted tgs hash, returning it
|
23
|
+
def remove_html_tag(tag_name)
|
24
|
+
@permitted_html_tags.delete(tag_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Adds a new style property converter for the specified ast class and
|
28
|
+
# CSS property name. The ast_class variable should be the class name
|
29
|
+
# in lowercased snakecase as a symbol, i.e. MyClass -> :my_class.
|
30
|
+
# The converter passed in must be a proc that accepts
|
31
|
+
# a single argument (the value) and returns two values: the WordML property
|
32
|
+
# name and its value. The converted property value can be a string, hash
|
33
|
+
# or array.
|
34
|
+
def register_style_converter(ast_node, prop_name, converter)
|
35
|
+
# create a new ast node hash if needed
|
36
|
+
unless @defined_style_conversions[ast_node]
|
37
|
+
@defined_style_conversions[ast_node] = {}
|
38
|
+
end
|
39
|
+
# add the style converter to the node's hash
|
40
|
+
@defined_style_conversions[ast_node][prop_name] = converter
|
41
|
+
end
|
42
|
+
|
43
|
+
# Deletes a CSS converter from the hash by specifying the AST class
|
44
|
+
# in lowercased snake case and the property name.
|
45
|
+
def remove_style_converter(ast_node, prop_name)
|
46
|
+
@defined_style_conversions[ast_node].delete(prop_name)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# Defines all of the initial HTML tags to be used by HTMLconverter
|
52
|
+
def initialize_html_tags
|
53
|
+
@permitted_html_tags = {}
|
54
|
+
tags = {
|
55
|
+
# special tag used for elements with no parent, i.e. top level
|
56
|
+
'#document-fragment' => { type: :block, ast_class: :root, allowed_children: :_block },
|
57
|
+
|
58
|
+
# block level tags
|
59
|
+
div: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Normal' }, allowed_children: :_inline },
|
60
|
+
p: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Paragraph' }, allowed_children: :_inline },
|
61
|
+
h1: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Heading1' }, allowed_children: :_inline },
|
62
|
+
h2: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Heading2' }, allowed_children: :_inline },
|
63
|
+
h3: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Heading3' }, allowed_children: :_inline },
|
64
|
+
h4: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Heading4' }, allowed_children: :_inline },
|
65
|
+
h5: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Heading5' }, allowed_children: :_inline },
|
66
|
+
h6: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Heading6' }, allowed_children: :_inline },
|
67
|
+
ol: { type: :block, ast_class: :list, properties: { pStyle: 'ListNumber' }, allowed_children: %i[ol li] },
|
68
|
+
ul: { type: :block, ast_class: :list, properties: { pStyle: 'ListBullet' }, allowed_children: %i[ul li] },
|
69
|
+
li: { type: :block, ast_class: :list_paragraph },
|
70
|
+
|
71
|
+
# inline style tags
|
72
|
+
span: { type: :inline, ast_class: nil, properties: {} },
|
73
|
+
strong: { type: :inline, ast_class: nil, properties: { b: nil } },
|
74
|
+
b: { type: :inline, ast_class: nil, properties: { b: nil } },
|
75
|
+
em: { type: :inline, ast_class: nil, properties: { i: nil } },
|
76
|
+
i: { type: :inline, ast_class: nil, properties: { i: nil } },
|
77
|
+
u: { type: :inline, ast_class: nil, properties: { u: 'single' } },
|
78
|
+
s: { type: :inline, ast_class: nil, properties: { strike: 'true' } },
|
79
|
+
sub: { type: :inline, ast_class: nil, properties: { vertAlign: 'subscript' } },
|
80
|
+
sup: { type: :inline, ast_class: nil, properties: { vertAlign: 'superscript' } },
|
81
|
+
|
82
|
+
# inline content tags
|
83
|
+
text: { type: :inline, ast_class: :run, properties: {}, allowed_children: [] },
|
84
|
+
br: { type: :inline, ast_class: :newline, properties: {}, allowed_children: [] }
|
85
|
+
}
|
86
|
+
# add all tags to the config object
|
87
|
+
tags.each do |tag_name, settings|
|
88
|
+
type = settings.delete(:type)
|
89
|
+
register_html_tag(tag_name, type, **settings)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Defines an initial set of CSS -> WordML conversion lambdas stored in
|
94
|
+
# a nested hash structure where the first key is the AST class and the
|
95
|
+
# second is the conversion lambda
|
96
|
+
def initialize_css_style_conversion
|
97
|
+
@defined_style_conversions = {
|
98
|
+
# styles shared or common logic across all node types go here.
|
99
|
+
# Special conversion lambdas such as :_border can be
|
100
|
+
# defined here for reuse across several AST nodes. Care must
|
101
|
+
# be taken to avoid possible naming conflicts, hence the underscore.
|
102
|
+
# AST class keys should be stored with their names converted from
|
103
|
+
# camelcase to lowercased snakecase, i.e. TestCase = test_case
|
104
|
+
node: {
|
105
|
+
'background-color' => lambda { |v|
|
106
|
+
return 'shd', { val: 'clear', fill: v.delete('#') }
|
107
|
+
},
|
108
|
+
_border: lambda { |v|
|
109
|
+
props = { sz: 2, val: 'single', color: '000000' }
|
110
|
+
vals = v.split
|
111
|
+
vals[1] = 'single' if vals[1] == 'solid'
|
112
|
+
#
|
113
|
+
props[:sz] = @defined_style_conversions[:node][:_sz].call(vals[0])
|
114
|
+
props[:val] = vals[1] if vals[1]
|
115
|
+
props[:color] = vals[2].delete('#') if vals[2]
|
116
|
+
#
|
117
|
+
return props
|
118
|
+
},
|
119
|
+
_sz: lambda { |v|
|
120
|
+
return nil unless v
|
121
|
+
(2 * Float(v.gsub(/[^\d.]/, '')).ceil).to_s
|
122
|
+
},
|
123
|
+
'text-align' => ->(v) { return 'jc', v }
|
124
|
+
},
|
125
|
+
# Styles specific to the Paragraph AST class
|
126
|
+
paragraph: {
|
127
|
+
'border' => lambda { |v|
|
128
|
+
props = @defined_style_conversions[:node][:_border].call(v)
|
129
|
+
#
|
130
|
+
return 'pBdr', [
|
131
|
+
{ top: props }, { bottom: props },
|
132
|
+
{ left: props }, { right: props }
|
133
|
+
]
|
134
|
+
},
|
135
|
+
'vertical-align' => ->(v) { return 'textAlignment', v }
|
136
|
+
},
|
137
|
+
# Styles specific to a run of text
|
138
|
+
run: {
|
139
|
+
'color' => ->(v) { return 'color', v.delete('#') },
|
140
|
+
'font-size' => lambda { |v|
|
141
|
+
return 'sz', @defined_style_conversions[:node][:_sz].call(v)
|
142
|
+
},
|
143
|
+
'font-style' => lambda { |v|
|
144
|
+
return 'b', nil if v =~ /bold/
|
145
|
+
return 'i', nil if v =~ /italic/
|
146
|
+
},
|
147
|
+
'font-weight' => ->(v) { return 'b', nil if v =~ /bold/ },
|
148
|
+
'text-decoration' => lambda { |v|
|
149
|
+
supported = %w[line-through underline]
|
150
|
+
props = v.split
|
151
|
+
return props[0], 'true' unless supported.include? props[0]
|
152
|
+
return 'strike', 'true' if props[0] == 'line-through'
|
153
|
+
return 'u', 'single' if props.length == 1
|
154
|
+
return 'u', { val: props[1], color: 'auto' } if props.length == 2
|
155
|
+
return 'u', { val: props[1], color: props[2].delete('#') }
|
156
|
+
},
|
157
|
+
'vertical-align' => lambda { |v|
|
158
|
+
return 'vertAlign', 'subscript' if v =~ /sub/
|
159
|
+
return 'vertAlign', 'superscript' if v =~ /super/
|
160
|
+
}
|
161
|
+
}
|
162
|
+
}
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Sablon
|
2
|
+
class Configuration
|
3
|
+
# Stores the information for a single HTML tag. This information
|
4
|
+
# is used by the HTMLConverter. An optional AST class can be defined,
|
5
|
+
# and if so conversion stops there and it is assumed the AST class
|
6
|
+
# will handle any child nodes unless the element is a block level tag.
|
7
|
+
# In the case of a block level tag the child nodes are processed by the
|
8
|
+
# AST builder again. If the AST class is omitted it is assumed the node
|
9
|
+
# should be "passed through" only transferring it's properties onto
|
10
|
+
# children. A block level tag must have an AST class associated with
|
11
|
+
# it. The block and inline status of tags is not affected by CSS.
|
12
|
+
# Permitted child tags are specified using the :allowed_children optional
|
13
|
+
# arg. The default value is [:_inline, :ul, :ol]. :_inline is a special
|
14
|
+
# reference to all inline type tags, :_block is equivalent for block
|
15
|
+
# type tags.
|
16
|
+
#
|
17
|
+
# == Parameters
|
18
|
+
# * name - symbol or string of the HTML element tag name
|
19
|
+
# * type - The type of HTML tag needs to be :inline or :block
|
20
|
+
# * ast_class - class instance or symbol, the AST class or it's name
|
21
|
+
# used to process the HTML node
|
22
|
+
# * options - collects all other keyword arguments, Current kwargs are
|
23
|
+
# `:properties`, `:attributes` and `:allowed_children`.
|
24
|
+
#
|
25
|
+
# Example
|
26
|
+
# HTMLTag.new(:div, :block, ast_class: Sablon::HTMLConverter::Paragraph,
|
27
|
+
# properties: { pStyle: 'Normal' })
|
28
|
+
class HTMLTag
|
29
|
+
attr_reader :name, :type, :ast_class, :attributes, :properties,
|
30
|
+
:allowed_children
|
31
|
+
|
32
|
+
# Setup HTML tag information
|
33
|
+
def initialize(name, type, ast_class: nil, **options)
|
34
|
+
# Set basic params converting some args to symbols for consistency
|
35
|
+
@name = name.to_sym
|
36
|
+
@type = type.to_sym
|
37
|
+
@ast_class = nil
|
38
|
+
# use self.ast_class to trigger setter method
|
39
|
+
self.ast_class = ast_class if ast_class
|
40
|
+
|
41
|
+
# Ensure block level tags have an AST class
|
42
|
+
if @type == :block && @ast_class.nil?
|
43
|
+
raise ArgumentError, "Block level tag #{name} must have an AST class."
|
44
|
+
end
|
45
|
+
|
46
|
+
# Set attributes from optinos hash, currently unused during AST generation
|
47
|
+
@attributes = options.fetch(:attributes, {})
|
48
|
+
# WordML properties defined by the tag, i.e. <w:b /> for the <b> tag,
|
49
|
+
# etc. All the keys need to be symbols to avoid getting reparsed
|
50
|
+
# with the element's CSS attributes.
|
51
|
+
@properties = options.fetch(:properties, {})
|
52
|
+
@properties = Hash[@properties.map { |k, v| [k.to_sym, v] }]
|
53
|
+
# Set permitted child tags or tag groups
|
54
|
+
self.allowed_children = options[:allowed_children]
|
55
|
+
end
|
56
|
+
|
57
|
+
# checks if the given tag is a permitted child element
|
58
|
+
def allowed_child?(tag)
|
59
|
+
if @allowed_children.include?(tag.name)
|
60
|
+
true
|
61
|
+
elsif @allowed_children.include?(:_inline) && tag.type == :inline
|
62
|
+
true
|
63
|
+
elsif @allowed_children.include?(:_block) && tag.type == :block
|
64
|
+
true
|
65
|
+
else
|
66
|
+
false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def allowed_children=(value)
|
73
|
+
if value.nil?
|
74
|
+
@allowed_children = %i[_inline ol ul]
|
75
|
+
return
|
76
|
+
else
|
77
|
+
value = [value] unless value.is_a? Array
|
78
|
+
end
|
79
|
+
@allowed_children = value.map(&:to_sym)
|
80
|
+
end
|
81
|
+
|
82
|
+
# converts a string or symbol to a class defined under
|
83
|
+
# Sablon::HTMLConverter
|
84
|
+
def ast_class=(value)
|
85
|
+
if value.is_a? Class
|
86
|
+
@ast_class = value
|
87
|
+
return
|
88
|
+
else
|
89
|
+
value = value.to_s
|
90
|
+
end
|
91
|
+
# camel case the word and get class, similar logic to
|
92
|
+
# ActiveSupport::Inflector.constantize but refactored to be specific
|
93
|
+
# to the HTMLConverter class
|
94
|
+
value.gsub!(/(?:^|_)([a-z])/) { Regexp.last_match[1].capitalize }
|
95
|
+
@ast_class = Sablon::HTMLConverter.const_get(value)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|