sablon 0.0.22 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +94 -22
- data/lib/sablon.rb +1 -0
- data/lib/sablon/configuration/configuration.rb +73 -1
- data/lib/sablon/content.rb +77 -3
- data/lib/sablon/environment.rb +3 -0
- data/lib/sablon/html/ast.rb +249 -76
- data/lib/sablon/html/ast_builder.rb +2 -7
- data/lib/sablon/html/node_properties.rb +91 -0
- data/lib/sablon/relationship.rb +47 -0
- data/lib/sablon/template.rb +30 -0
- data/lib/sablon/version.rb +1 -1
- data/test/content_test.rb +121 -42
- data/test/fixtures/html/html_test_content.html +106 -15
- data/test/fixtures/html_sample.docx +0 -0
- data/test/fixtures/insertion_template.docx +0 -0
- data/test/html/ast_builder_test.rb +0 -5
- data/test/html/ast_test.rb +35 -0
- data/test/html/converter_style_test.rb +535 -0
- data/test/html/converter_test.rb +412 -528
- data/test/html/node_properties_test.rb +21 -0
- data/test/html_test.rb +12 -3
- data/test/test_helper.rb +16 -0
- metadata +7 -3
@@ -52,13 +52,8 @@ module Sablon
|
|
52
52
|
# Checking that the current tag is an allowed child of the parent_tag.
|
53
53
|
# If the parent tag is nil then a block level tag is required.
|
54
54
|
def validate_structure(parent, child)
|
55
|
-
|
56
|
-
|
57
|
-
elsif parent && !parent.allowed_child?(child)
|
58
|
-
msg = "#{child.name} is not a valid child element of #{parent.name}."
|
59
|
-
else
|
60
|
-
return
|
61
|
-
end
|
55
|
+
return unless parent && !parent.allowed_child?(child)
|
56
|
+
msg = "#{child.name} is not a valid child element of #{parent.name}."
|
62
57
|
raise ContextError, "Invalid HTML structure: #{msg}"
|
63
58
|
end
|
64
59
|
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Sablon
|
2
|
+
class HTMLConverter
|
3
|
+
# Manages the properties for an AST node, includes factory methods
|
4
|
+
# for easy use at calling sites.
|
5
|
+
class NodeProperties
|
6
|
+
attr_reader :transferred_properties
|
7
|
+
|
8
|
+
def self.paragraph(properties)
|
9
|
+
new('w:pPr', properties, Paragraph::PROPERTIES)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.table(properties)
|
13
|
+
new('w:tblPr', properties, Table::PROPERTIES)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.table_row(properties)
|
17
|
+
new('w:trPr', properties, TableRow::PROPERTIES)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.table_cell(properties)
|
21
|
+
new('w:tcPr', properties, TableCell::PROPERTIES)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.run(properties)
|
25
|
+
new('w:rPr', properties, Run::PROPERTIES)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(tagname, properties, whitelist)
|
29
|
+
@tagname = tagname
|
30
|
+
filter_properties(properties, whitelist)
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
@properties.map { |k, v| v ? "#{k}=#{v}" : k }.join(';')
|
35
|
+
end
|
36
|
+
|
37
|
+
def [](key)
|
38
|
+
@properties[key]
|
39
|
+
end
|
40
|
+
|
41
|
+
def []=(key, value)
|
42
|
+
@properties[key] = value
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_docx
|
46
|
+
"<#{@tagname}>#{properties_word_ml}</#{@tagname}>" unless @properties.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# processes properties adding those on the whitelist to the
|
52
|
+
# properties instance variable and those not to the transferred_properties
|
53
|
+
# isntance variable
|
54
|
+
def filter_properties(properties, whitelist)
|
55
|
+
@transferred_properties = {}
|
56
|
+
@properties = {}
|
57
|
+
#
|
58
|
+
properties.each do |key, value|
|
59
|
+
if whitelist.include? key.to_s
|
60
|
+
@properties[key] = value
|
61
|
+
else
|
62
|
+
@transferred_properties[key] = value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# processes attributes defined on the node into wordML property syntax
|
68
|
+
def properties_word_ml
|
69
|
+
@properties.map { |k, v| transform_attr(k, v) }.join
|
70
|
+
end
|
71
|
+
|
72
|
+
# properties that have a list as the value get nested in tags and
|
73
|
+
# each entry in the list is transformed. When a value is a hash the
|
74
|
+
# keys in the hash are used to explicitly build the XML tag attributes.
|
75
|
+
def transform_attr(key, value)
|
76
|
+
if value.is_a? Array
|
77
|
+
sub_attrs = value.map do |sub_prop|
|
78
|
+
sub_prop.map { |k, v| transform_attr(k, v) }
|
79
|
+
end
|
80
|
+
"<w:#{key}>#{sub_attrs.join}</w:#{key}>"
|
81
|
+
elsif value.is_a? Hash
|
82
|
+
props = value.map { |k, v| format('w:%s="%s"', k, v) if v }
|
83
|
+
"<w:#{key} #{props.compact.join(' ')} />"
|
84
|
+
else
|
85
|
+
value = format('w:val="%s" ', value) if value
|
86
|
+
"<w:#{key} #{value}/>"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Sablon
|
2
|
+
# Handles storing referenced relationships in the document.xml file and
|
3
|
+
# writing them to the document.xml.rels file
|
4
|
+
class Relationship
|
5
|
+
attr_accessor :relationships
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@relationships = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_found_relationships(content, output_stream)
|
12
|
+
output_stream.put_next_entry('word/_rels/document.xml.rels')
|
13
|
+
#
|
14
|
+
unless @relationships.empty?
|
15
|
+
rels_doc = Nokogiri::XML(content)
|
16
|
+
rels_doc_root = rels_doc.root
|
17
|
+
# convert new rels to nodes
|
18
|
+
node_set = convert_relationships_to_node_set(rels_doc)
|
19
|
+
@relationships = []
|
20
|
+
# add new nodes to XML content
|
21
|
+
rels_doc_root.last_element_child.after(node_set)
|
22
|
+
content = rels_doc.to_xml(indent: 0, save_with: 0)
|
23
|
+
end
|
24
|
+
#
|
25
|
+
output_stream.write(content)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Builds a set of Relationship XML nodes from the stored relationships
|
31
|
+
def convert_relationships_to_node_set(doc)
|
32
|
+
node_set = Nokogiri::XML::NodeSet.new(doc)
|
33
|
+
@relationships.each do |relationship|
|
34
|
+
rel_tag = "<Relationship#{relationship_attributes(relationship)}/>"
|
35
|
+
node_set << Nokogiri::XML.fragment(rel_tag).children.first
|
36
|
+
end
|
37
|
+
#
|
38
|
+
node_set
|
39
|
+
end
|
40
|
+
|
41
|
+
# Builds the attribute string for the relationship XML node
|
42
|
+
def relationship_attributes(relationship)
|
43
|
+
return '' if relationship.nil? || relationship.empty?
|
44
|
+
' ' + relationship.map { |k, v| %(#{k}="#{v}") }.join(' ')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/sablon/template.rb
CHANGED
@@ -19,11 +19,14 @@ module Sablon
|
|
19
19
|
private
|
20
20
|
|
21
21
|
def render(context, properties = {})
|
22
|
+
created_dirs = []
|
23
|
+
relations_file_content = nil
|
22
24
|
env = Sablon::Environment.new(self, context)
|
23
25
|
Zip.sort_entries = true # required to process document.xml before numbering.xml
|
24
26
|
Zip::OutputStream.write_buffer(StringIO.new) do |out|
|
25
27
|
Zip::File.open(@path).each do |entry|
|
26
28
|
entry_name = entry.name
|
29
|
+
created_dirs = create_dirs_in_zipfile(created_dirs, entry_name, out)
|
27
30
|
out.put_next_entry(entry_name)
|
28
31
|
content = entry.get_input_stream.read
|
29
32
|
if entry_name == 'word/document.xml'
|
@@ -32,11 +35,38 @@ module Sablon
|
|
32
35
|
out.write(process(Processor::Document, content, env))
|
33
36
|
elsif entry_name == 'word/numbering.xml'
|
34
37
|
out.write(process(Processor::Numbering, content, env))
|
38
|
+
elsif entry_name == 'word/_rels/document.xml.rels'
|
39
|
+
relations_file_content = content
|
35
40
|
else
|
36
41
|
out.write(content)
|
37
42
|
end
|
38
43
|
end
|
44
|
+
if relations_file_content
|
45
|
+
env.relationship.add_found_relationships(relations_file_content, out)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# creates directories of the unzipped docx file in the newly created docx file e.g. in case of
|
51
|
+
# word/_rels/document.xml.rels it creates word/ and _rels directories to apply recursive zipping.
|
52
|
+
# This is a hack to fix the issue of getting a corrupted file when any referencing between the
|
53
|
+
# xml files happen like in the case of implementing hyperlinks
|
54
|
+
#
|
55
|
+
def create_dirs_in_zipfile(previous_created_dirs, entry_name, output_stream)
|
56
|
+
created_dirs = previous_created_dirs
|
57
|
+
entry_name_tokens = entry_name.split('/')
|
58
|
+
entry_name_tokens.pop()
|
59
|
+
if entry_name_tokens.length > 1
|
60
|
+
prev_dir = ''
|
61
|
+
entry_name_tokens.each do |dir_name|
|
62
|
+
prev_dir += dir_name + '/'
|
63
|
+
unless created_dirs.include? prev_dir
|
64
|
+
output_stream.put_next_entry(prev_dir)
|
65
|
+
created_dirs << prev_dir
|
66
|
+
end
|
67
|
+
end
|
39
68
|
end
|
69
|
+
created_dirs
|
40
70
|
end
|
41
71
|
|
42
72
|
# process the sablon xml template with the given +context+.
|
data/lib/sablon/version.rb
CHANGED
data/test/content_test.rb
CHANGED
@@ -1,6 +1,36 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
require "test_helper"
|
3
3
|
|
4
|
+
module XmlContentTestSetup
|
5
|
+
def setup
|
6
|
+
super
|
7
|
+
@template_text = '<w:p><w:r><w:t>template</w:t></w:r></w:p><w:p>AFTER</w:p>'
|
8
|
+
#
|
9
|
+
@document = Nokogiri::XML(doc_wrapper(@template_text))
|
10
|
+
@paragraph = @document.xpath('//w:p').first
|
11
|
+
@node = @paragraph.xpath('.//w:r').first.at_xpath('./w:t')
|
12
|
+
@env = Sablon::Environment.new(nil)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def doc_wrapper(content)
|
18
|
+
doc = <<-XML.gsub(/^\s+|\n/, '')
|
19
|
+
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
20
|
+
<w:body>
|
21
|
+
%<content>s
|
22
|
+
</w:body>
|
23
|
+
</w:document>
|
24
|
+
XML
|
25
|
+
format(doc, content: content)
|
26
|
+
end
|
27
|
+
|
28
|
+
def assert_xml_equal(expected, document)
|
29
|
+
expected = Nokogiri::XML(doc_wrapper(expected)).to_xml(indent: 0, save_with: 0)
|
30
|
+
assert_equal expected, document.to_xml(indent: 0, save_with: 0)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
4
34
|
class ContentTest < Sablon::TestCase
|
5
35
|
def test_can_build_content_objects
|
6
36
|
content = Sablon.content(:string, "a string")
|
@@ -69,30 +99,14 @@ class CustomContentTest < Sablon::TestCase
|
|
69
99
|
end
|
70
100
|
end
|
71
101
|
|
72
|
-
module ContentTestSetup
|
73
|
-
def setup
|
74
|
-
super
|
75
|
-
@template_text = '<w:p><span>template</span></w:p><w:p>AFTER</w:p>'
|
76
|
-
@document = Nokogiri::XML.fragment(@template_text)
|
77
|
-
@paragraph = @document.children.first
|
78
|
-
@node = @document.css("span").first
|
79
|
-
@env = Sablon::Environment.new(nil)
|
80
|
-
end
|
81
|
-
|
82
|
-
private
|
83
|
-
def assert_xml_equal(expected, document)
|
84
|
-
assert_equal expected, document.to_xml(indent: 0, save_with: 0)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
102
|
class ContentStringTest < Sablon::TestCase
|
89
|
-
include
|
103
|
+
include XmlContentTestSetup
|
90
104
|
|
91
105
|
def test_single_line_string
|
92
|
-
Sablon.content(:string,
|
106
|
+
Sablon.content(:string, 'a normal string').append_to @paragraph, @node, @env
|
93
107
|
|
94
108
|
output = <<-XML.strip
|
95
|
-
<w:p><
|
109
|
+
<w:p><w:r><w:t>template</w:t><w:t>a normal string</w:t></w:r></w:p><w:p>AFTER</w:p>
|
96
110
|
XML
|
97
111
|
assert_xml_equal output, @document
|
98
112
|
end
|
@@ -101,7 +115,7 @@ class ContentStringTest < Sablon::TestCase
|
|
101
115
|
Sablon.content(:string, 42).append_to @paragraph, @node, @env
|
102
116
|
|
103
117
|
output = <<-XML.strip
|
104
|
-
<w:p><
|
118
|
+
<w:p><w:r><w:t>template</w:t><w:t>42</w:t></w:r></w:p><w:p>AFTER</w:p>
|
105
119
|
XML
|
106
120
|
assert_xml_equal output, @document
|
107
121
|
end
|
@@ -109,53 +123,118 @@ class ContentStringTest < Sablon::TestCase
|
|
109
123
|
def test_string_with_newlines
|
110
124
|
Sablon.content(:string, "a\nmultiline\n\nstring").append_to @paragraph, @node, @env
|
111
125
|
|
112
|
-
output = <<-XML.
|
113
|
-
<w:p>
|
114
|
-
<
|
115
|
-
<
|
116
|
-
<w:
|
117
|
-
<
|
118
|
-
<w:
|
119
|
-
<w:br/>
|
120
|
-
<
|
121
|
-
</w:
|
122
|
-
|
126
|
+
output = <<-XML.gsub(/\s/, '')
|
127
|
+
<w:p>
|
128
|
+
<w:r>
|
129
|
+
<w:t>template</w:t>
|
130
|
+
<w:t>a</w:t>
|
131
|
+
<w:br/>
|
132
|
+
<w:t>multiline</w:t>
|
133
|
+
<w:br/>
|
134
|
+
<w:br/>
|
135
|
+
<w:t>string</w:t>
|
136
|
+
</w:r>
|
137
|
+
</w:p><w:p>AFTER</w:p>
|
123
138
|
XML
|
124
139
|
|
125
140
|
assert_xml_equal output, @document
|
126
141
|
end
|
127
142
|
|
128
143
|
def test_blank_string
|
129
|
-
Sablon.content(:string,
|
144
|
+
Sablon.content(:string, '').append_to @paragraph, @node, @env
|
130
145
|
|
131
146
|
assert_xml_equal @template_text, @document
|
132
147
|
end
|
133
148
|
end
|
134
149
|
|
135
150
|
class ContentWordMLTest < Sablon::TestCase
|
136
|
-
include
|
151
|
+
include XmlContentTestSetup
|
137
152
|
|
138
153
|
def test_blank_word_ml
|
139
|
-
|
154
|
+
# blank strings in word_ml are an odd corner case, they get treated
|
155
|
+
# as inline so the paragraph is retained but the display node is still
|
156
|
+
# removed with nothing being inserted in it's place. Nokogiri automatically
|
157
|
+
# collapsed the empty <w:p></w:P> tag into a <w:/p> form.
|
158
|
+
Sablon.content(:word_ml, '').append_to @paragraph, @node, @env
|
159
|
+
assert_xml_equal "<w:p/><w:p>AFTER</w:p>", @document
|
160
|
+
end
|
140
161
|
|
141
|
-
|
162
|
+
def test_plain_text_word_ml
|
163
|
+
# text isn't a valid child element of a w:p tag, so the whole paragraph
|
164
|
+
# gets replaced.
|
165
|
+
Sablon.content(:word_ml, "test").append_to @paragraph, @node, @env
|
166
|
+
assert_xml_equal "test<w:p>AFTER</w:p>", @document
|
142
167
|
end
|
143
168
|
|
144
|
-
def
|
169
|
+
def test_inserts_paragraph_word_ml_into_the_document
|
145
170
|
@word_ml = '<w:p><w:r><w:t xml:space="preserve">a </w:t></w:r></w:p>'
|
146
171
|
Sablon.content(:word_ml, @word_ml).append_to @paragraph, @node, @env
|
147
172
|
|
148
|
-
output = <<-XML.
|
149
|
-
<w:p>
|
150
|
-
<w:r><w:t xml:space=\"preserve\">a </w:t></w:r>
|
151
|
-
</w:p>
|
152
|
-
<w:p>AFTER</w:p>
|
173
|
+
output = <<-XML.gsub(/^\s+|\n/, '')
|
174
|
+
<w:p>
|
175
|
+
<w:r><w:t xml:space=\"preserve\">a </w:t></w:r>
|
176
|
+
</w:p>
|
177
|
+
<w:p>AFTER</w:p>
|
178
|
+
XML
|
179
|
+
|
180
|
+
assert_xml_equal output, @document
|
181
|
+
end
|
182
|
+
|
183
|
+
def test_inserts_inline_word_ml_into_the_document
|
184
|
+
@word_ml = '<w:r><w:t xml:space="preserve">inline text </w:t></w:r>'
|
185
|
+
Sablon.content(:word_ml, @word_ml).append_to @paragraph, @node, @env
|
186
|
+
|
187
|
+
output = <<-XML.gsub(/^\s+|\n/, '')
|
188
|
+
<w:p>
|
189
|
+
<w:r><w:t xml:space="preserve">inline text </w:t></w:r>
|
190
|
+
</w:p>
|
191
|
+
<w:p>AFTER</w:p>
|
153
192
|
XML
|
154
193
|
|
155
194
|
assert_xml_equal output, @document
|
156
195
|
end
|
157
196
|
|
158
197
|
def test_inserting_word_ml_multiple_times_into_same_paragraph
|
159
|
-
|
198
|
+
@word_ml = '<w:r><w:t xml:space="preserve">inline text </w:t></w:r>'
|
199
|
+
Sablon.content(:word_ml, @word_ml).append_to @paragraph, @node, @env
|
200
|
+
@word_ml = '<w:r><w:t xml:space="preserve">inline text2 </w:t></w:r>'
|
201
|
+
Sablon.content(:word_ml, @word_ml).append_to @paragraph, @node, @env
|
202
|
+
@word_ml = '<w:r><w:t xml:space="preserve">inline text3 </w:t></w:r>'
|
203
|
+
Sablon.content(:word_ml, @word_ml).append_to @paragraph, @node, @env
|
204
|
+
|
205
|
+
# Only a single insertion should work because the node that we insert
|
206
|
+
# the content afer contains a merge field that needs removed. That means
|
207
|
+
# in the next two appends the @node variable doesn't exist on the document
|
208
|
+
# tree
|
209
|
+
output = <<-XML.gsub(/^\s+|\n/, '')
|
210
|
+
<w:p>
|
211
|
+
<w:r><w:t xml:space="preserve">inline text </w:t></w:r>
|
212
|
+
</w:p>
|
213
|
+
<w:p>AFTER</w:p>
|
214
|
+
XML
|
215
|
+
|
216
|
+
assert_xml_equal output, @document
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_inserting_multiple_runs_into_same_paragraph
|
220
|
+
@word_ml = <<-XML.gsub(/^\s+|\n/, '')
|
221
|
+
<w:r><w:t xml:space="preserve">inline text </w:t></w:r>
|
222
|
+
<w:r><w:t xml:space="preserve">inline text2 </w:t></w:r>
|
223
|
+
<w:r><w:t xml:space="preserve">inline text3 </w:t></w:r>
|
224
|
+
XML
|
225
|
+
Sablon.content(:word_ml, @word_ml).append_to @paragraph, @node, @env
|
226
|
+
|
227
|
+
# This works because all three runs are added as a single insertion
|
228
|
+
# event
|
229
|
+
output = <<-XML.gsub(/^\s+|\n/, '')
|
230
|
+
<w:p>
|
231
|
+
<w:r><w:t xml:space="preserve">inline text </w:t></w:r>
|
232
|
+
<w:r><w:t xml:space="preserve">inline text2 </w:t></w:r>
|
233
|
+
<w:r><w:t xml:space="preserve">inline text3 </w:t></w:r>
|
234
|
+
</w:p>
|
235
|
+
<w:p>AFTER</w:p>
|
236
|
+
XML
|
237
|
+
|
238
|
+
assert_xml_equal output, @document
|
160
239
|
end
|
161
240
|
end
|
@@ -30,9 +30,20 @@
|
|
30
30
|
</div>
|
31
31
|
|
32
32
|
|
33
|
+
<h2>Hyper Links</h2>
|
34
|
+
|
35
|
+
<div>
|
36
|
+
<a href="http://www.google.com">Hyperlink</a>
|
37
|
+
<br/>
|
38
|
+
<span style="font-style: bold"><a href="http://www.google.com">Hyperlink with bold style</a></span>
|
39
|
+
<br/>
|
40
|
+
<u><a href="http://www.google.com" style="color: #1022DD">Hyperlink with color and underline</a></u>
|
41
|
+
<br/>
|
42
|
+
</div>
|
43
|
+
|
33
44
|
<h2>Lists</h2>
|
34
45
|
|
35
|
-
<ol>
|
46
|
+
<ol>
|
36
47
|
<li>
|
37
48
|
Vestibulum
|
38
49
|
<ol>
|
@@ -78,23 +89,23 @@
|
|
78
89
|
Duis non porttitor nulla, ut eleifend enim. Pellentesque non tempor sem.
|
79
90
|
</div>
|
80
91
|
|
81
|
-
<div>Mauris auctor egestas arcu, </div>
|
92
|
+
<div>Mauris auctor egestas arcu, </div>
|
82
93
|
|
83
94
|
<ol>
|
84
|
-
<li>id venenatis nibh dignissim id. </li>
|
85
|
-
<li>In non placerat metus. </li>
|
95
|
+
<li>id venenatis nibh dignissim id. </li>
|
96
|
+
<li>In non placerat metus. </li>
|
86
97
|
</ol>
|
87
98
|
|
88
|
-
<ul>
|
89
|
-
<li>Nunc sed consequat metus. </li>
|
90
|
-
<li>Nulla consectetur lorem consequat, </li>
|
91
|
-
<li>malesuada dui at, lacinia lectus. </li>
|
99
|
+
<ul>
|
100
|
+
<li>Nunc sed consequat metus. </li>
|
101
|
+
<li>Nulla consectetur lorem consequat, </li>
|
102
|
+
<li>malesuada dui at, lacinia lectus. </li>
|
92
103
|
</ul>
|
93
104
|
|
94
|
-
<ol>
|
95
|
-
<li>Aliquam efficitur </li>
|
96
|
-
<li>lorem a mauris feugiat, </li>
|
97
|
-
<li>at semper eros pellentesque. </li>
|
105
|
+
<ol>
|
106
|
+
<li>Aliquam efficitur </li>
|
107
|
+
<li>lorem a mauris feugiat, </li>
|
108
|
+
<li>at semper eros pellentesque. </li>
|
98
109
|
</ol>
|
99
110
|
|
100
111
|
<div>
|
@@ -105,7 +116,7 @@
|
|
105
116
|
tristique ultrices elit. Nulla in turpis nibh.
|
106
117
|
</div>
|
107
118
|
|
108
|
-
<ul>
|
119
|
+
<ul>
|
109
120
|
<li>
|
110
121
|
Nam consectetur
|
111
122
|
<ul>
|
@@ -133,14 +144,14 @@
|
|
133
144
|
<li>Duis faucibus nunc nec venenatis faucibus. </li>
|
134
145
|
<li>Aliquam erat volutpat. </li>
|
135
146
|
</ul>
|
136
|
-
<div style="border: 5px double #FF00FF">
|
147
|
+
<div style="border: 5px double #FF00FF">
|
137
148
|
<strong>Quisque non neque ut lacus eleifend volutpat quis sed lacus.
|
138
149
|
<br />Praesent ultrices purus eu quam elementum, sit amet faucibus elit
|
139
150
|
interdum. In lectus orci,<br /> elementum quis dictum ac, porta ac ante.
|
140
151
|
Fusce tempus ac mauris id cursus. Phasellus a erat nulla. <em>Mauris dolor orci</em>,
|
141
152
|
malesuada auctor dignissim non, <u>posuere nec odio</u>. Etiam hendrerit
|
142
153
|
justo nec diam ullamcorper, nec blandit elit sodales.</strong>
|
143
|
-
</div>
|
154
|
+
</div>
|
144
155
|
|
145
156
|
|
146
157
|
<div style="text-align: both; background-color: #EAFEDA; vertical-align: top">
|
@@ -172,3 +183,83 @@
|
|
172
183
|
</ul>
|
173
184
|
<li>Item 3</li>
|
174
185
|
</ul>
|
186
|
+
|
187
|
+
|
188
|
+
<h2>Tables</h2>
|
189
|
+
|
190
|
+
<table>
|
191
|
+
<caption>Table 1: Example</caption>
|
192
|
+
<tr>
|
193
|
+
<th>Head Cell 1</th>
|
194
|
+
<th>Head Cell 2</th>
|
195
|
+
</tr>
|
196
|
+
<tr>
|
197
|
+
<td>Data Cell 1</td>
|
198
|
+
<td>Data Cell 2</td>
|
199
|
+
</tr>
|
200
|
+
</table>
|
201
|
+
|
202
|
+
<p>
|
203
|
+
<br/>
|
204
|
+
<br/>
|
205
|
+
</p>
|
206
|
+
|
207
|
+
<table style="border: 1px solid #FF0000">
|
208
|
+
<caption style="caption-side: bottom; highlight: cyan; text-align: center">
|
209
|
+
Table 1: Example With Formatting
|
210
|
+
</caption>
|
211
|
+
<thead>
|
212
|
+
<tr style="border: 1px solid #FF00FF">
|
213
|
+
<th>Head Cell 1</th>
|
214
|
+
<th>Head Cell 2</th>
|
215
|
+
</tr>
|
216
|
+
</thead>
|
217
|
+
<tbody>
|
218
|
+
<tr style="border: 1px solid #0000FF">
|
219
|
+
<td style="color: #FFAA22">Data Cell 1</td>
|
220
|
+
<td style="background-color: #123456">Data Cell 2</td>
|
221
|
+
</tr>
|
222
|
+
</tbody>
|
223
|
+
<tfoot>
|
224
|
+
<tr>
|
225
|
+
<td>Data Cell 3</td>
|
226
|
+
<td>Data Cell 4</td>
|
227
|
+
</tr>
|
228
|
+
</tfoot>
|
229
|
+
</table>
|
230
|
+
|
231
|
+
<table style="border: 1px solid #000000; width: 4000dxa">
|
232
|
+
<tr>
|
233
|
+
<td>Above paragraph tag<p>In paragraph</p>below paragraph tag</td>
|
234
|
+
|
235
|
+
<td>
|
236
|
+
<ul>
|
237
|
+
<li>Item A</li>
|
238
|
+
<li>Item B</li>
|
239
|
+
</ul>
|
240
|
+
<a href="http://www.github.com" style="color: #0000dd">GitHub</a>
|
241
|
+
</td>
|
242
|
+
</tr>
|
243
|
+
|
244
|
+
<tr>
|
245
|
+
<td>
|
246
|
+
<ol>
|
247
|
+
<li>Item 1</li>
|
248
|
+
<li>Item 2
|
249
|
+
<ol>
|
250
|
+
<li>Item 2a</li>
|
251
|
+
<li>Item 2b</li>
|
252
|
+
</ol>
|
253
|
+
</li>
|
254
|
+
</ol>
|
255
|
+
</td>
|
256
|
+
|
257
|
+
<td>
|
258
|
+
<table style="border: 1px solid #FF0000">
|
259
|
+
<caption style="text-align: center">Sub table header</caption>
|
260
|
+
<tr><td>A</td><td>B</td></tr>
|
261
|
+
<tr><td>C</td><td>D</td></tr>
|
262
|
+
</table>
|
263
|
+
</td>
|
264
|
+
</tr>
|
265
|
+
</table>
|