sablon 0.0.22 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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>
|