ruby-rtf 0.0.1
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.
- data/.gitignore +10 -0
- data/.infinity_test +24 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +101 -0
- data/LICENSE +18 -0
- data/README +12 -0
- data/Rakefile +20 -0
- data/bin/rtf_parse +112 -0
- data/lib/ruby-rtf.rb +11 -0
- data/lib/ruby-rtf/colour.rb +50 -0
- data/lib/ruby-rtf/document.rb +36 -0
- data/lib/ruby-rtf/font.rb +83 -0
- data/lib/ruby-rtf/invalid_document.rb +4 -0
- data/lib/ruby-rtf/parser.rb +492 -0
- data/lib/ruby-rtf/ruby-rtf.rb +7 -0
- data/lib/ruby-rtf/table.rb +72 -0
- data/lib/ruby-rtf/version.rb +5 -0
- data/ruby-rtf.gemspec +30 -0
- data/spec/colour_spec.rb +12 -0
- data/spec/document_spec.rb +38 -0
- data/spec/font_spec.rb +15 -0
- data/spec/parser_spec.rb +926 -0
- data/spec/spec_helper.rb +1 -0
- metadata +130 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
module RubyRTF
|
2
|
+
class Table
|
3
|
+
attr_accessor :rows, :half_gap, :left_margin
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@left_margin = 0
|
7
|
+
@half_gap = 0
|
8
|
+
|
9
|
+
@rows = []
|
10
|
+
add_row
|
11
|
+
end
|
12
|
+
|
13
|
+
def current_row
|
14
|
+
@rows.last
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_row
|
18
|
+
@rows << RubyRTF::Table::Row.new(self)
|
19
|
+
@rows.last
|
20
|
+
end
|
21
|
+
|
22
|
+
class Row
|
23
|
+
attr_accessor :table, :end_positions, :cells
|
24
|
+
|
25
|
+
def initialize(table)
|
26
|
+
@table = table
|
27
|
+
@end_positions = []
|
28
|
+
|
29
|
+
@cells = [RubyRTF::Table::Row::Cell.new(self, 0)]
|
30
|
+
end
|
31
|
+
|
32
|
+
def current_cell
|
33
|
+
@cells.last
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_cell
|
37
|
+
return @cells.last if @cells.last.sections.empty?
|
38
|
+
|
39
|
+
@cells << RubyRTF::Table::Row::Cell.new(self, @cells.length)
|
40
|
+
@cells.last
|
41
|
+
end
|
42
|
+
|
43
|
+
class Cell
|
44
|
+
attr_accessor :sections, :row, :idx
|
45
|
+
|
46
|
+
def initialize(row, idx)
|
47
|
+
@row = row
|
48
|
+
@idx = idx
|
49
|
+
@sections = []
|
50
|
+
end
|
51
|
+
|
52
|
+
def <<(obj)
|
53
|
+
@sections << obj
|
54
|
+
end
|
55
|
+
|
56
|
+
def table
|
57
|
+
row.table
|
58
|
+
end
|
59
|
+
|
60
|
+
def width
|
61
|
+
gap = row.table.half_gap
|
62
|
+
left_margin = row.table.left_margin
|
63
|
+
|
64
|
+
end_pos = row.end_positions[idx]
|
65
|
+
prev_pos = idx == 0 ? 0 : row.end_positions[idx - 1]
|
66
|
+
|
67
|
+
((end_pos - prev_pos - (2 * gap) - left_margin) / row.end_positions[-1]) * 100
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/ruby-rtf.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
$: << "./lib"
|
2
|
+
require 'ruby-rtf/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'ruby-rtf'
|
6
|
+
|
7
|
+
s.version = RubyRTF::VERSION
|
8
|
+
|
9
|
+
s.authors = 'dan sinclair'
|
10
|
+
s.email = 'dj2@everburning.com'
|
11
|
+
|
12
|
+
s.homepage = 'http://github.com/dj2/ruby-rtf'
|
13
|
+
s.summary = 'Library for working with RTF files'
|
14
|
+
s.description = s.summary
|
15
|
+
|
16
|
+
s.add_development_dependency 'infinity_test'
|
17
|
+
s.add_development_dependency 'rspec', '>2.0'
|
18
|
+
s.add_development_dependency 'metric_fu'
|
19
|
+
|
20
|
+
s.add_development_dependency 'yard'
|
21
|
+
s.add_development_dependency 'bluecloth'
|
22
|
+
|
23
|
+
s.bindir = 'bin'
|
24
|
+
s.executables << 'rtf_parse'
|
25
|
+
|
26
|
+
s.files = `git ls-files`.split("\n")
|
27
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
28
|
+
|
29
|
+
s.require_paths = ['lib']
|
30
|
+
end
|
data/spec/colour_spec.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RubyRTF::Colour do
|
4
|
+
it 'also responds to Color' do
|
5
|
+
lambda { RubyRTF::Color.new }.should_not raise_error
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'returns the rgb when to_s is called' do
|
9
|
+
c = RubyRTF::Colour.new(255, 200, 199)
|
10
|
+
c.to_s.should == '[255, 200, 199]'
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RubyRTF::Document do
|
4
|
+
it 'provides a font table' do
|
5
|
+
doc = RubyRTF::Document.new
|
6
|
+
table = nil
|
7
|
+
lambda { table = doc.font_table }.should_not raise_error
|
8
|
+
table.should_not be_nil
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'colour table' do
|
12
|
+
it 'provides a colour table' do
|
13
|
+
doc = RubyRTF::Document.new
|
14
|
+
tbl = nil
|
15
|
+
lambda { tbl = doc.colour_table }.should_not raise_error
|
16
|
+
tbl.should_not be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'provdies access as color table' do
|
20
|
+
doc = RubyRTF::Document.new
|
21
|
+
tbl = nil
|
22
|
+
lambda { tbl = doc.color_table }.should_not raise_error
|
23
|
+
tbl.should == doc.colour_table
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'provides a stylesheet'
|
28
|
+
|
29
|
+
context 'defaults to' do
|
30
|
+
it 'character set ansi' do
|
31
|
+
RubyRTF::Document.new.character_set.should == :ansi
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'font 0' do
|
35
|
+
RubyRTF::Document.new.default_font.should == 0
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/spec/font_spec.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RubyRTF::Font do
|
4
|
+
let(:font) { RubyRTF::Font.new }
|
5
|
+
|
6
|
+
it 'has a name' do
|
7
|
+
font.name = 'Arial'
|
8
|
+
font.name.should == 'Arial'
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'has a command' do
|
12
|
+
font.family_command = :swiss
|
13
|
+
font.family_command.should == :swiss
|
14
|
+
end
|
15
|
+
end
|
data/spec/parser_spec.rb
ADDED
@@ -0,0 +1,926 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe RubyRTF::Parser do
|
6
|
+
let(:parser) { RubyRTF::Parser.new }
|
7
|
+
let(:doc) { parser.doc }
|
8
|
+
|
9
|
+
it 'parses hello world' do
|
10
|
+
src = '{\rtf1\ansi\deff0 {\fonttbl {\f0 Times New Roman;}}\f0 \fs60 Hello, World!}'
|
11
|
+
lambda { parser.parse(src) }.should_not raise_error
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'returns a RTF::Document' do
|
15
|
+
src = '{\rtf1\ansi\deff0 {\fonttbl {\f0 Times New Roman;}}\f0 \fs60 Hello, World!}'
|
16
|
+
d = parser.parse(src)
|
17
|
+
d.is_a?(RubyRTF::Document).should be_true
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'parses a default font (\deffN)' do
|
21
|
+
src = '{\rtf1\ansi\deff10 {\fonttbl {\f10 Times New Roman;}}\f0 \fs60 Hello, World!}'
|
22
|
+
d = parser.parse(src)
|
23
|
+
d.default_font.should == 10
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'invalid document' do
|
27
|
+
it 'raises exception if \rtf is missing' do
|
28
|
+
src = '{\ansi\deff0 {\fonttbl {\f0 Times New Roman;}}\f0 \fs60 Hello, World!}'
|
29
|
+
lambda { parser.parse(src) }.should raise_error(RubyRTF::InvalidDocument)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'raises exception if the document does not start with \rtf' do
|
33
|
+
src = '{\ansi\deff0\rtf1 {\fonttbl {\f0 Times New Roman;}}\f0 \fs60 Hello, World!}'
|
34
|
+
lambda { parser.parse(src) }.should raise_error(RubyRTF::InvalidDocument)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'raises exception if the {}s are unbalanced' do
|
38
|
+
src = '{\rtf1\ansi\deff0 {\fonttbl {\f0 Times New Roman;}\f0 \fs60 Hello, World!}'
|
39
|
+
lambda { parser.parse(src) }.should raise_error(RubyRTF::InvalidDocument)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context '#parse' do
|
44
|
+
it 'parses text into the current section' do
|
45
|
+
src = '{\rtf1\ansi\deff10 {\fonttbl {\f10 Times New Roman;}}\f0 \fs60 Hello, World!}'
|
46
|
+
d = parser.parse(src)
|
47
|
+
d.sections.first[:text].should == 'Hello, World!'
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'adds a new section on {' do
|
51
|
+
src = '{\rtf1 \fs60 Hello {\fs30 World}}'
|
52
|
+
d = parser.parse(src)
|
53
|
+
d.sections.first[:modifiers][:font_size].should == 30
|
54
|
+
d.sections.first[:text].should == 'Hello '
|
55
|
+
|
56
|
+
d.sections.last[:modifiers][:font_size].should == 15
|
57
|
+
d.sections.last[:text].should == 'World'
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'adds a new section on }' do
|
61
|
+
src = '{\rtf1 \fs60 Hello {\fs30 World}\fs12 Goodbye, cruel world.}'
|
62
|
+
|
63
|
+
section = parser.parse(src).sections
|
64
|
+
section[0][:modifiers][:font_size].should == 30
|
65
|
+
section[0][:text].should == 'Hello '
|
66
|
+
|
67
|
+
section[1][:modifiers][:font_size].should == 15
|
68
|
+
section[1][:text].should == 'World'
|
69
|
+
|
70
|
+
section[2][:modifiers][:font_size].should == 6
|
71
|
+
section[2][:text].should == 'Goodbye, cruel world.'
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'inherits properly over {} groups' do
|
75
|
+
src = '{\rtf1 \b\fs60 Hello {\i\fs30 World}\ul Goodbye, cruel world.}'
|
76
|
+
|
77
|
+
section = parser.parse(src).sections
|
78
|
+
section[0][:modifiers][:font_size].should == 30
|
79
|
+
section[0][:modifiers][:bold].should be_true
|
80
|
+
section[0][:modifiers].has_key?(:italic).should be_false
|
81
|
+
section[0][:modifiers].has_key?(:underline).should be_false
|
82
|
+
section[0][:text].should == 'Hello '
|
83
|
+
|
84
|
+
section[1][:modifiers][:font_size].should == 15
|
85
|
+
section[1][:modifiers][:italic].should be_true
|
86
|
+
section[1][:modifiers][:bold].should be_true
|
87
|
+
section[1][:modifiers].has_key?(:underline).should be_false
|
88
|
+
section[1][:text].should == 'World'
|
89
|
+
|
90
|
+
section[2][:modifiers][:font_size].should == 30
|
91
|
+
section[2][:modifiers][:bold].should be_true
|
92
|
+
section[2][:modifiers][:underline].should be_true
|
93
|
+
section[2][:modifiers].has_key?(:italic).should be_false
|
94
|
+
section[2][:text].should == 'Goodbye, cruel world.'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context '#parse_control' do
|
99
|
+
it 'parses a normal control' do
|
100
|
+
parser.parse_control("rtf")[0, 2].should == [:rtf, nil]
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'parses a control with a value' do
|
104
|
+
parser.parse_control("f2")[0, 2].should == [:f, 2]
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'unicode' do
|
108
|
+
%w(u21487* u21487).each do |code|
|
109
|
+
it "parses #{code}" do
|
110
|
+
parser.parse_control(code)[0, 2].should == [:u, 21487]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
%w(u-21487* u-21487).each do |code|
|
115
|
+
it "parses #{code}" do
|
116
|
+
parser.parse_control(code)[0, 2].should == [:u, -21487]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'parses a hex control' do
|
122
|
+
parser.parse_control("'7e")[0, 2].should == [:hex, '~']
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'parses a hex control with a string after it' do
|
126
|
+
ctrl, val, current_pos = parser.parse_control("'7e25")
|
127
|
+
ctrl.should == :hex
|
128
|
+
val.should == '~'
|
129
|
+
current_pos.should == 3
|
130
|
+
end
|
131
|
+
|
132
|
+
[' ', '{', '}', '\\', "\r", "\n"].each do |stop|
|
133
|
+
it "stops at a #{stop}" do
|
134
|
+
parser.parse_control("rtf#{stop}test")[0, 2].should == [:rtf, nil]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'handles a non-zero current position' do
|
139
|
+
parser.parse_control('Test ansi test', 5)[0, 2].should == [:ansi, nil]
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'advances the current positon' do
|
143
|
+
parser.parse_control('Test ansi{test', 5).last.should == 9
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'advances the current positon past the optional space' do
|
147
|
+
parser.parse_control('Test ansi test', 5).last.should == 10
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'character set' do
|
152
|
+
%w(ansi mac pc pca).each do |type|
|
153
|
+
it "accepts #{type}" do
|
154
|
+
src = "{\\rtf1\\#{type}\\deff0 {\\fonttbl {\\f0 Times New Roman;}}\\f0 \\fs60 Hello, World!}"
|
155
|
+
doc = parser.parse(src)
|
156
|
+
doc.character_set.should == type.to_sym
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'font table' do
|
162
|
+
it 'sets the font table into the document' do
|
163
|
+
src = '{\rtf1{\fonttbl{\f0\froman Times;}{\f1\fnil Arial;}}}'
|
164
|
+
doc = parser.parse(src)
|
165
|
+
|
166
|
+
font = doc.font_table[0]
|
167
|
+
font.family_command.should == :roman
|
168
|
+
font.name.should == 'Times'
|
169
|
+
end
|
170
|
+
|
171
|
+
context '#parse_font_table' do
|
172
|
+
it 'parses a font table' do
|
173
|
+
src = '{\f0\froman Times New Roman;}{\f1\fnil Arial;}}}'
|
174
|
+
parser.parse_font_table(src, 0)
|
175
|
+
tbl = doc.font_table
|
176
|
+
|
177
|
+
tbl.length.should == 2
|
178
|
+
tbl[0].family_command.should == :roman
|
179
|
+
tbl[0].name.should == 'Times New Roman'
|
180
|
+
|
181
|
+
tbl[1].family_command.should == :nil
|
182
|
+
tbl[1].name.should == 'Arial'
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'parses a font table without braces' do
|
186
|
+
src = '\f0\froman\fcharset0 TimesNewRomanPSMT;}}'
|
187
|
+
parser.parse_font_table(src, 0)
|
188
|
+
tbl = doc.font_table
|
189
|
+
tbl[0].name.should == 'TimesNewRomanPSMT'
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'handles \r and \n in the font table' do
|
193
|
+
src = "{\\f0\\froman Times New Roman;}\r{\\f1\\fnil Arial;}\n}}"
|
194
|
+
parser.parse_font_table(src, 0)
|
195
|
+
tbl = doc.font_table
|
196
|
+
|
197
|
+
tbl.length.should == 2
|
198
|
+
tbl[0].family_command.should == :roman
|
199
|
+
tbl[0].name.should == 'Times New Roman'
|
200
|
+
|
201
|
+
tbl[1].family_command.should == :nil
|
202
|
+
tbl[1].name.should == 'Arial'
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'the family command is optional' do
|
206
|
+
src = '{\f0 Times New Roman;}}}'
|
207
|
+
parser.parse_font_table(src, 0)
|
208
|
+
tbl = doc.font_table
|
209
|
+
tbl[0].family_command.should == :nil
|
210
|
+
tbl[0].name.should == 'Times New Roman'
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'does not require the numbering to be incremental' do
|
214
|
+
src = '{\f77\froman Times New Roman;}{\f3\fnil Arial;}}}'
|
215
|
+
parser.parse_font_table(src, 0)
|
216
|
+
tbl = doc.font_table
|
217
|
+
|
218
|
+
tbl[77].family_command.should == :roman
|
219
|
+
tbl[77].name.should == 'Times New Roman'
|
220
|
+
|
221
|
+
tbl[3].family_command.should == :nil
|
222
|
+
tbl[3].name.should == 'Arial'
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'accepts the \falt command' do
|
226
|
+
src = '{\f0\froman Times New Roman{\*\falt Courier New};}}'
|
227
|
+
parser.parse_font_table(src, 0)
|
228
|
+
tbl = doc.font_table
|
229
|
+
tbl[0].name.should == 'Times New Roman'
|
230
|
+
tbl[0].alternate_name.should == 'Courier New'
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'sets current pos to the closing }' do
|
234
|
+
src = '{\f0\froman Times New Roman{\*\falt Courier New};}}'
|
235
|
+
parser.parse_font_table(src, 0).should == (src.length - 1)
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'accepts the panose command' do
|
239
|
+
src = '{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman{\*\falt Courier New};}}'
|
240
|
+
parser.parse_font_table(src, 0)
|
241
|
+
tbl = doc.font_table
|
242
|
+
tbl[0].panose.should == '02020603050405020304'
|
243
|
+
tbl[0].name.should == 'Times New Roman'
|
244
|
+
tbl[0].alternate_name.should == 'Courier New'
|
245
|
+
end
|
246
|
+
|
247
|
+
%w(flomajor fhimajor fdbmajor fbimajor flominor fhiminor fdbminor fbiminor).each do |type|
|
248
|
+
it "handles theme font type: #{type}" do
|
249
|
+
src = "{\\f0\\#{type} Times New Roman;}}"
|
250
|
+
parser.parse_font_table(src, 0)
|
251
|
+
tbl = doc.font_table
|
252
|
+
tbl[0].name.should == 'Times New Roman'
|
253
|
+
tbl[0].theme.should == type[1..-1].to_sym
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
[[0, :default], [1, :fixed], [2, :variable]].each do |pitch|
|
258
|
+
it 'parses pitch information' do
|
259
|
+
src = "{\\f0\\fprq#{pitch.first} Times New Roman;}}"
|
260
|
+
parser.parse_font_table(src, 0)
|
261
|
+
tbl = doc.font_table
|
262
|
+
tbl[0].name.should == 'Times New Roman'
|
263
|
+
tbl[0].pitch.should == pitch.last
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'parses the non-tagged font name' do
|
268
|
+
src = '{\f0{\*\fname Arial;}Times New Roman;}}'
|
269
|
+
parser.parse_font_table(src, 0)
|
270
|
+
tbl = doc.font_table
|
271
|
+
tbl[0].name.should == 'Times New Roman'
|
272
|
+
tbl[0].non_tagged_name.should == 'Arial'
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'parses the charset' do
|
276
|
+
src = '{\f0\fcharset87 Times New Roman;}}'
|
277
|
+
parser.parse_font_table(src, 0)
|
278
|
+
tbl = doc.font_table
|
279
|
+
tbl[0].name.should == 'Times New Roman'
|
280
|
+
tbl[0].character_set.should == 87
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
context 'colour table' do
|
286
|
+
it 'sets the colour table into the document' do
|
287
|
+
src = '{\rtf1{\colortbl\red0\green0\blue0;\red127\green2\blue255;}}'
|
288
|
+
doc = parser.parse(src)
|
289
|
+
|
290
|
+
clr = doc.colour_table[0]
|
291
|
+
clr.red.should == 0
|
292
|
+
clr.green.should == 0
|
293
|
+
clr.blue.should == 0
|
294
|
+
|
295
|
+
clr = doc.colour_table[1]
|
296
|
+
clr.red.should == 127
|
297
|
+
clr.green.should == 2
|
298
|
+
clr.blue.should == 255
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'sets the first colour if missing' do
|
302
|
+
src = '{\rtf1{\colortbl;\red255\green0\blue0;\red0\green0\blue255;}}'
|
303
|
+
doc = parser.parse(src)
|
304
|
+
|
305
|
+
clr = doc.colour_table[0]
|
306
|
+
clr.use_default?.should be_true
|
307
|
+
|
308
|
+
clr = doc.colour_table[1]
|
309
|
+
clr.red.should == 255
|
310
|
+
clr.green.should == 0
|
311
|
+
clr.blue.should == 0
|
312
|
+
end
|
313
|
+
|
314
|
+
context '#parse_colour_table' do
|
315
|
+
it 'parses \red \green \blue' do
|
316
|
+
src = '\red2\green55\blue23;}'
|
317
|
+
parser.parse_colour_table(src, 0)
|
318
|
+
tbl = doc.colour_table
|
319
|
+
tbl[0].red.should == 2
|
320
|
+
tbl[0].green.should == 55
|
321
|
+
tbl[0].blue.should == 23
|
322
|
+
end
|
323
|
+
|
324
|
+
it 'handles ctintN' do
|
325
|
+
src = '\ctint22\red2\green55\blue23;}'
|
326
|
+
parser.parse_colour_table(src, 0)
|
327
|
+
tbl = doc.colour_table
|
328
|
+
tbl[0].tint.should == 22
|
329
|
+
end
|
330
|
+
|
331
|
+
it 'handles cshadeN' do
|
332
|
+
src = '\cshade11\red2\green55\blue23;}'
|
333
|
+
parser.parse_colour_table(src, 0)
|
334
|
+
tbl = doc.colour_table
|
335
|
+
tbl[0].shade.should == 11
|
336
|
+
end
|
337
|
+
|
338
|
+
%w(cmaindarkone cmainlightone cmaindarktwo cmainlighttwo caccentone
|
339
|
+
caccenttwo caccentthree caccentfour caccentfive caccentsix
|
340
|
+
chyperlink cfollowedhyperlink cbackgroundone ctextone
|
341
|
+
cbackgroundtwo ctexttwo).each do |theme|
|
342
|
+
it "it allows theme item #{theme}" do
|
343
|
+
src = "\\#{theme}\\red11\\green22\\blue33;}"
|
344
|
+
parser.parse_colour_table(src, 0)
|
345
|
+
tbl = doc.colour_table
|
346
|
+
tbl[0].theme.should == theme[1..-1].to_sym
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
it 'handles \r and \n' do
|
351
|
+
src = "\\cshade11\\red2\\green55\r\n\\blue23;}"
|
352
|
+
parser.parse_colour_table(src, 0)
|
353
|
+
tbl = doc.colour_table
|
354
|
+
tbl[0].shade.should == 11
|
355
|
+
tbl[0].red.should == 2
|
356
|
+
tbl[0].green.should == 55
|
357
|
+
tbl[0].blue.should == 23
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
context 'stylesheet' do
|
363
|
+
it 'parses a stylesheet'
|
364
|
+
end
|
365
|
+
|
366
|
+
context 'document info' do
|
367
|
+
it 'parse the doocument info'
|
368
|
+
end
|
369
|
+
|
370
|
+
context '#handle_control' do
|
371
|
+
it 'sets the font' do
|
372
|
+
font = RubyRTF::Font.new('Times New Roman')
|
373
|
+
doc.font_table[0] = font
|
374
|
+
|
375
|
+
parser.handle_control(:f, 0, nil, 0)
|
376
|
+
parser.current_section[:modifiers][:font].should == font
|
377
|
+
end
|
378
|
+
|
379
|
+
it 'sets the font size' do
|
380
|
+
parser.handle_control(:fs, 61, nil, 0)
|
381
|
+
parser.current_section[:modifiers][:font_size].should == 30.5
|
382
|
+
end
|
383
|
+
|
384
|
+
it 'sets bold' do
|
385
|
+
parser.handle_control(:b, nil, nil, 0)
|
386
|
+
parser.current_section[:modifiers][:bold].should be_true
|
387
|
+
end
|
388
|
+
|
389
|
+
it 'sets underline' do
|
390
|
+
parser.handle_control(:ul, nil, nil, 0)
|
391
|
+
parser.current_section[:modifiers][:underline].should be_true
|
392
|
+
end
|
393
|
+
|
394
|
+
it 'sets italic' do
|
395
|
+
parser.handle_control(:i, nil, nil, 0)
|
396
|
+
parser.current_section[:modifiers][:italic].should be_true
|
397
|
+
end
|
398
|
+
|
399
|
+
%w(rquote lquote).each do |quote|
|
400
|
+
it "sets a #{quote}" do
|
401
|
+
parser.current_section[:text] = 'My code'
|
402
|
+
parser.handle_control(quote.to_sym, nil, nil, 0)
|
403
|
+
doc.sections.last[:text].should == "'"
|
404
|
+
doc.sections.last[:modifiers][quote.to_sym].should be_true
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
%w(rdblquote ldblquote).each do |quote|
|
409
|
+
it "sets a #{quote}" do
|
410
|
+
parser.current_section[:text] = 'My code'
|
411
|
+
parser.handle_control(quote.to_sym, nil, nil, 0)
|
412
|
+
doc.sections.last[:text].should == '"'
|
413
|
+
doc.sections.last[:modifiers][quote.to_sym].should be_true
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
it 'sets a hex character' do
|
418
|
+
parser.current_section[:text] = 'My code'
|
419
|
+
parser.handle_control(:hex, '~', nil, 0)
|
420
|
+
parser.current_section[:text].should == 'My code~'
|
421
|
+
end
|
422
|
+
|
423
|
+
it 'sets a unicode character < 1000 (char 643)' do
|
424
|
+
parser.current_section[:text] = 'My code'
|
425
|
+
parser.handle_control(:u, 643, nil, 0)
|
426
|
+
parser.current_section[:text].should == 'My codeك'
|
427
|
+
end
|
428
|
+
|
429
|
+
it 'sets a unicode character < 32768 (char 2603)' do
|
430
|
+
parser.current_section[:text] = 'My code'
|
431
|
+
parser.handle_control(:u, 2603, nil, 0)
|
432
|
+
parser.current_section[:text].should == 'My code☃'
|
433
|
+
end
|
434
|
+
|
435
|
+
it 'sets a unicode character < 32768 (char 21340)' do
|
436
|
+
parser.current_section[:text] = 'My code'
|
437
|
+
parser.handle_control(:u, 21340, nil, 0)
|
438
|
+
parser.current_section[:text].should == 'My code卜'
|
439
|
+
end
|
440
|
+
|
441
|
+
|
442
|
+
it 'sets a unicode character > 32767 (char 36,947)' do
|
443
|
+
parser.current_section[:text] = 'My code'
|
444
|
+
parser.handle_control(:u, -28589, nil, 0)
|
445
|
+
parser.current_section[:text].should == 'My code道'
|
446
|
+
end
|
447
|
+
|
448
|
+
context 'new line' do
|
449
|
+
['line', "\n"].each do |type|
|
450
|
+
it "sets from #{type}" do
|
451
|
+
parser.current_section[:text] = "end."
|
452
|
+
parser.handle_control(type.to_sym, nil, nil, 0)
|
453
|
+
doc.sections.last[:modifiers][:newline].should be_true
|
454
|
+
doc.sections.last[:text].should == "\n"
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
it 'ignores \r' do
|
459
|
+
parser.current_section[:text] = "end."
|
460
|
+
parser.handle_control(:"\r", nil, nil, 0)
|
461
|
+
parser.current_section[:text].should == "end."
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
it 'inserts a \tab' do
|
466
|
+
parser.current_section[:text] = "end."
|
467
|
+
parser.handle_control(:tab, nil, nil, 0)
|
468
|
+
doc.sections.last[:modifiers][:tab].should be_true
|
469
|
+
doc.sections.last[:text].should == "\t"
|
470
|
+
end
|
471
|
+
|
472
|
+
it 'inserts a \super' do
|
473
|
+
parser.current_section[:text] = "end."
|
474
|
+
parser.handle_control(:super, nil, nil, 0)
|
475
|
+
|
476
|
+
parser.current_section[:modifiers][:superscript].should be_true
|
477
|
+
parser.current_section[:text].should == ""
|
478
|
+
end
|
479
|
+
|
480
|
+
it 'inserts a \sub' do
|
481
|
+
parser.current_section[:text] = "end."
|
482
|
+
parser.handle_control(:sub, nil, nil, 0)
|
483
|
+
|
484
|
+
parser.current_section[:modifiers][:subscript].should be_true
|
485
|
+
parser.current_section[:text].should == ""
|
486
|
+
end
|
487
|
+
|
488
|
+
it 'inserts a \strike' do
|
489
|
+
parser.current_section[:text] = "end."
|
490
|
+
parser.handle_control(:strike, nil, nil, 0)
|
491
|
+
|
492
|
+
parser.current_section[:modifiers][:strikethrough].should be_true
|
493
|
+
parser.current_section[:text].should == ""
|
494
|
+
end
|
495
|
+
|
496
|
+
it 'inserts a \scaps' do
|
497
|
+
parser.current_section[:text] = "end."
|
498
|
+
parser.handle_control(:scaps, nil, nil, 0)
|
499
|
+
|
500
|
+
parser.current_section[:modifiers][:smallcaps].should be_true
|
501
|
+
parser.current_section[:text].should == ""
|
502
|
+
end
|
503
|
+
|
504
|
+
it 'inserts an \emdash' do
|
505
|
+
parser.current_section[:text] = "end."
|
506
|
+
parser.handle_control(:emdash, nil, nil, 0)
|
507
|
+
doc.sections.last[:modifiers][:emdash].should be_true
|
508
|
+
doc.sections.last[:text].should == "--"
|
509
|
+
end
|
510
|
+
|
511
|
+
it 'inserts an \endash' do
|
512
|
+
parser.current_section[:text] = "end."
|
513
|
+
parser.handle_control(:endash, nil, nil, 0)
|
514
|
+
doc.sections.last[:modifiers][:endash].should be_true
|
515
|
+
doc.sections.last[:text].should == "-"
|
516
|
+
end
|
517
|
+
|
518
|
+
context 'escapes' do
|
519
|
+
['{', '}', '\\'].each do |escape|
|
520
|
+
it "inserts an escaped #{escape}" do
|
521
|
+
parser.current_section[:text] = "end."
|
522
|
+
parser.handle_control(escape.to_sym, nil, nil, 0)
|
523
|
+
parser.current_section[:text].should == "end.#{escape}"
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
it 'adds a new section for a par command' do
|
529
|
+
parser.current_section[:text] = 'end.'
|
530
|
+
parser.handle_control(:par, nil, nil, 0)
|
531
|
+
parser.current_section[:text].should == ""
|
532
|
+
end
|
533
|
+
|
534
|
+
%w(pard plain).each do |type|
|
535
|
+
it "resets the current sections information to default for #{type}" do
|
536
|
+
parser.current_section[:modifiers][:bold] = true
|
537
|
+
parser.current_section[:modifiers][:italic] = true
|
538
|
+
parser.handle_control(type.to_sym, nil, nil, 0)
|
539
|
+
|
540
|
+
parser.current_section[:modifiers].has_key?(:bold).should be_false
|
541
|
+
parser.current_section[:modifiers].has_key?(:italic).should be_false
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
context 'colour' do
|
546
|
+
it 'sets the foreground colour' do
|
547
|
+
doc.colour_table << RubyRTF::Colour.new(255, 0, 255)
|
548
|
+
parser.handle_control(:cf, 0, nil, 0)
|
549
|
+
parser.current_section[:modifiers][:foreground_colour].to_s.should == "[255, 0, 255]"
|
550
|
+
end
|
551
|
+
|
552
|
+
it 'sets the background colour' do
|
553
|
+
doc.colour_table << RubyRTF::Colour.new(255, 0, 255)
|
554
|
+
parser.handle_control(:cb, 0, nil, 0)
|
555
|
+
parser.current_section[:modifiers][:background_colour].to_s.should == "[255, 0, 255]"
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
context 'justification' do
|
560
|
+
it 'handles left justify' do
|
561
|
+
parser.handle_control(:ql, nil, nil, 0)
|
562
|
+
parser.current_section[:modifiers][:justification].should == :left
|
563
|
+
end
|
564
|
+
|
565
|
+
it 'handles right justify' do
|
566
|
+
parser.handle_control(:qr, nil, nil, 0)
|
567
|
+
parser.current_section[:modifiers][:justification].should == :right
|
568
|
+
end
|
569
|
+
|
570
|
+
it 'handles full justify' do
|
571
|
+
parser.handle_control(:qj, nil, nil, 0)
|
572
|
+
parser.current_section[:modifiers][:justification].should == :full
|
573
|
+
end
|
574
|
+
|
575
|
+
it 'handles centered' do
|
576
|
+
parser.handle_control(:qc, nil, nil, 0)
|
577
|
+
parser.current_section[:modifiers][:justification].should == :center
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
context 'indenting' do
|
582
|
+
it 'handles first line indent' do
|
583
|
+
parser.handle_control(:fi, 1000, nil, 0)
|
584
|
+
parser.current_section[:modifiers][:first_line_indent].should == 50
|
585
|
+
end
|
586
|
+
|
587
|
+
it 'handles left indent' do
|
588
|
+
parser.handle_control(:li, 1000, nil, 0)
|
589
|
+
parser.current_section[:modifiers][:left_indent].should == 50
|
590
|
+
end
|
591
|
+
|
592
|
+
it 'handles right indent' do
|
593
|
+
parser.handle_control(:ri, 1000, nil, 0)
|
594
|
+
parser.current_section[:modifiers][:right_indent].should == 50
|
595
|
+
end
|
596
|
+
end
|
597
|
+
|
598
|
+
context 'margins' do
|
599
|
+
it 'handles left margin' do
|
600
|
+
parser.handle_control(:margl, 1000, nil, 0)
|
601
|
+
parser.current_section[:modifiers][:left_margin].should == 50
|
602
|
+
end
|
603
|
+
|
604
|
+
it 'handles right margin' do
|
605
|
+
parser.handle_control(:margr, 1000, nil, 0)
|
606
|
+
parser.current_section[:modifiers][:right_margin].should == 50
|
607
|
+
end
|
608
|
+
|
609
|
+
it 'handles top margin' do
|
610
|
+
parser.handle_control(:margt, 1000, nil, 0)
|
611
|
+
parser.current_section[:modifiers][:top_margin].should == 50
|
612
|
+
end
|
613
|
+
|
614
|
+
it 'handles bottom margin' do
|
615
|
+
parser.handle_control(:margb, 1000, nil, 0)
|
616
|
+
parser.current_section[:modifiers][:bottom_margin].should == 50
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
context 'paragraph spacing' do
|
621
|
+
it 'handles space before' do
|
622
|
+
parser.handle_control(:sb, 1000, nil, 0)
|
623
|
+
parser.current_section[:modifiers][:space_before].should == 50
|
624
|
+
end
|
625
|
+
|
626
|
+
it 'handles space after' do
|
627
|
+
parser.handle_control(:sa, 1000, nil, 0)
|
628
|
+
parser.current_section[:modifiers][:space_after].should == 50
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
632
|
+
context 'non breaking space' do
|
633
|
+
it 'handles :~' do
|
634
|
+
parser.current_section[:text] = "end."
|
635
|
+
parser.handle_control(:~, nil, nil, 0)
|
636
|
+
doc.sections.last[:modifiers][:nbsp].should be_true
|
637
|
+
doc.sections.last[:text].should == " "
|
638
|
+
end
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
context 'sections' do
|
643
|
+
it 'has sections' do
|
644
|
+
doc.sections.should_not be_nil
|
645
|
+
end
|
646
|
+
|
647
|
+
it 'sets an initial section' do
|
648
|
+
parser.current_section.should_not be_nil
|
649
|
+
end
|
650
|
+
|
651
|
+
context '#add_section!' do
|
652
|
+
it 'does not add a section if the current :text is empty' do
|
653
|
+
d = parser
|
654
|
+
d.add_section!
|
655
|
+
doc.sections.length.should == 0
|
656
|
+
end
|
657
|
+
|
658
|
+
it 'adds a section of the current section has text' do
|
659
|
+
d = parser
|
660
|
+
d.current_section[:text] = "Test"
|
661
|
+
d.add_section!
|
662
|
+
doc.sections.length.should == 1
|
663
|
+
end
|
664
|
+
|
665
|
+
it 'inherits the modifiers from the parent section' do
|
666
|
+
d = parser
|
667
|
+
d.current_section[:modifiers][:bold] = true
|
668
|
+
d.current_section[:modifiers][:italics] = true
|
669
|
+
d.current_section[:text] = "New text"
|
670
|
+
|
671
|
+
d.add_section!
|
672
|
+
|
673
|
+
d.current_section[:modifiers][:underline] = true
|
674
|
+
|
675
|
+
sections = doc.sections
|
676
|
+
sections.first[:modifiers].should == {:bold => true, :italics => true}
|
677
|
+
d.current_section[:modifiers].should == {:bold => true, :italics => true, :underline => true}
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
context '#reset_current_section!' do
|
682
|
+
it 'resets the current sections modifiers' do
|
683
|
+
d = parser
|
684
|
+
d.current_section[:modifiers] = {:bold => true, :italics => true}
|
685
|
+
d.current_section[:text] = "New text"
|
686
|
+
|
687
|
+
d.add_section!
|
688
|
+
d.reset_current_section!
|
689
|
+
d.current_section[:modifiers][:underline] = true
|
690
|
+
|
691
|
+
sections = doc.sections
|
692
|
+
sections.first[:modifiers].should == {:bold => true, :italics => true}
|
693
|
+
d.current_section[:modifiers].should == {:underline => true}
|
694
|
+
end
|
695
|
+
end
|
696
|
+
|
697
|
+
context '#remove_last_section!' do
|
698
|
+
it 'removes the last section' do
|
699
|
+
d = parser
|
700
|
+
d.current_section[:modifiers] = {:bold => true, :italics => true}
|
701
|
+
d.current_section[:text] = "New text"
|
702
|
+
|
703
|
+
d.add_section!
|
704
|
+
|
705
|
+
d.current_section[:modifiers][:underline] = true
|
706
|
+
|
707
|
+
doc.sections.length.should == 1
|
708
|
+
doc.sections.first[:text].should == 'New text'
|
709
|
+
end
|
710
|
+
end
|
711
|
+
|
712
|
+
context 'tables' do
|
713
|
+
def compare_table_results(table, data)
|
714
|
+
table.rows.length.should == data.length
|
715
|
+
|
716
|
+
data.each_with_index do |row, idx|
|
717
|
+
end_positions = table.rows[idx].end_positions
|
718
|
+
row[:end_positions].each_with_index do |size, cidx|
|
719
|
+
end_positions[cidx].should == size
|
720
|
+
end
|
721
|
+
|
722
|
+
cells = table.rows[idx].cells
|
723
|
+
cells.length.should == row[:values].length
|
724
|
+
|
725
|
+
row[:values].each_with_index do |items, vidx|
|
726
|
+
sects = cells[vidx].sections
|
727
|
+
items.each_with_index do |val, iidx|
|
728
|
+
sects[iidx][:text].should == val
|
729
|
+
end
|
730
|
+
end
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
it 'parses a single row/column table' do
|
735
|
+
src = '{\rtf1 Before Table' +
|
736
|
+
'\trowd\trgaph180\cellx1440' +
|
737
|
+
'\pard\intbl fee.\cell\row ' +
|
738
|
+
'After table}'
|
739
|
+
d = parser.parse(src)
|
740
|
+
|
741
|
+
sect = d.sections
|
742
|
+
sect.length.should == 3
|
743
|
+
sect[0][:text].should == 'Before Table'
|
744
|
+
sect[2][:text].should == 'After table'
|
745
|
+
|
746
|
+
sect[1][:modifiers][:table].should_not be_nil
|
747
|
+
table = sect[1][:modifiers][:table]
|
748
|
+
|
749
|
+
compare_table_results(table, [{:end_positions => [72], :values => [['fee.']]}])
|
750
|
+
end
|
751
|
+
|
752
|
+
it 'parses a \trgaph180' do
|
753
|
+
src = '{\rtf1 Before Table' +
|
754
|
+
'\trowd\trgaph180\cellx1440' +
|
755
|
+
'\pard\intbl fee.\cell\row ' +
|
756
|
+
'After table}'
|
757
|
+
d = parser.parse(src)
|
758
|
+
|
759
|
+
table = d.sections[1][:modifiers][:table]
|
760
|
+
table.half_gap.should == 9
|
761
|
+
end
|
762
|
+
|
763
|
+
it 'parses a \trleft240' do
|
764
|
+
src = '{\rtf1 Before Table' +
|
765
|
+
'\trowd\trgaph180\trleft240\cellx1440' +
|
766
|
+
'\pard\intbl fee.\cell\row ' +
|
767
|
+
'After table}'
|
768
|
+
d = parser.parse(src)
|
769
|
+
|
770
|
+
table = d.sections[1][:modifiers][:table]
|
771
|
+
table.left_margin.should == 12
|
772
|
+
end
|
773
|
+
|
774
|
+
it 'parses a single row with multiple columns' do
|
775
|
+
src = '{\rtf1 Before Table' +
|
776
|
+
'\trowd\trgaph180\cellx1440\cellx2880\cellx1000' +
|
777
|
+
'\pard\intbl fee.\cell' +
|
778
|
+
'\pard\intbl fie.\cell' +
|
779
|
+
'\pard\intbl foe.\cell\row ' +
|
780
|
+
'After table}'
|
781
|
+
d = parser.parse(src)
|
782
|
+
|
783
|
+
sect = d.sections
|
784
|
+
|
785
|
+
sect.length.should == 3
|
786
|
+
sect[0][:text].should == 'Before Table'
|
787
|
+
sect[2][:text].should == 'After table'
|
788
|
+
|
789
|
+
sect[1][:modifiers][:table].should_not be_nil
|
790
|
+
table = sect[1][:modifiers][:table]
|
791
|
+
|
792
|
+
compare_table_results(table, [{:end_positions => [72, 144, 50], :values => [['fee.'], ['fie.'], ['foe.']]}])
|
793
|
+
end
|
794
|
+
|
795
|
+
it 'parses multiple rows and multiple columns' do
|
796
|
+
src = '{\rtf1 \strike Before Table' +
|
797
|
+
'\trowd\trgaph180\cellx1440\cellx2880\cellx1000' +
|
798
|
+
'\pard\intbl\ul fee.\cell' +
|
799
|
+
'\pard\intbl\i fie.\cell' +
|
800
|
+
'\pard\intbl\b foe.\cell\row ' +
|
801
|
+
'\trowd\trgaph180\cellx1000\cellx1440\cellx2880' +
|
802
|
+
'\pard\intbl\i foo.\cell' +
|
803
|
+
'\pard\intbl\b bar.\cell' +
|
804
|
+
'\pard\intbl\ul baz.\cell\row ' +
|
805
|
+
'After table}'
|
806
|
+
d = parser.parse(src)
|
807
|
+
|
808
|
+
sect = d.sections
|
809
|
+
sect.length.should == 3
|
810
|
+
sect[0][:text].should == 'Before Table'
|
811
|
+
sect[2][:text].should == 'After table'
|
812
|
+
|
813
|
+
sect[1][:modifiers][:table].should_not be_nil
|
814
|
+
table = sect[1][:modifiers][:table]
|
815
|
+
|
816
|
+
compare_table_results(table, [{:end_positions => [72, 144, 50], :values => [['fee.'], ['fie.'], ['foe.']]},
|
817
|
+
{:end_positions => [50, 72, 144], :values => [['foo.'], ['bar.'], ['baz.']]}])
|
818
|
+
end
|
819
|
+
|
820
|
+
it 'parses a grouped table' do
|
821
|
+
src = '{\rtf1 \strike Before Table' +
|
822
|
+
'{\trowd\trgaph180\cellx1440\cellx2880\cellx1000' +
|
823
|
+
'\pard\intbl\ul fee.\cell' +
|
824
|
+
'\pard\intbl\i fie.\cell' +
|
825
|
+
'\pard\intbl\b foe.\cell\row}' +
|
826
|
+
'{\trowd\trgaph180\cellx1000\cellx1440\cellx2880' +
|
827
|
+
'\pard\intbl\i foo.\cell' +
|
828
|
+
'\pard\intbl\b bar.\cell' +
|
829
|
+
'\pard\intbl\ul baz.\cell\row}' +
|
830
|
+
'After table}'
|
831
|
+
d = parser.parse(src)
|
832
|
+
|
833
|
+
sect = d.sections
|
834
|
+
sect.length.should == 3
|
835
|
+
sect[0][:text].should == 'Before Table'
|
836
|
+
sect[2][:text].should == 'After table'
|
837
|
+
|
838
|
+
sect[1][:modifiers][:table].should_not be_nil
|
839
|
+
table = sect[1][:modifiers][:table]
|
840
|
+
|
841
|
+
compare_table_results(table, [{:end_positions => [72, 144, 50], :values => [['fee.'], ['fie.'], ['foe.']]},
|
842
|
+
{:end_positions => [50, 72, 144], :values => [['foo.'], ['bar.'], ['baz.']]}])
|
843
|
+
end
|
844
|
+
|
845
|
+
it 'parses a new line inside a table cell' do
|
846
|
+
src = '{\rtf1 Before Table' +
|
847
|
+
'\trowd\trgaph180\cellx1440' +
|
848
|
+
'\pard\intbl fee.\line fie.\cell\row ' +
|
849
|
+
'After table}'
|
850
|
+
d = parser.parse(src)
|
851
|
+
|
852
|
+
sect = d.sections
|
853
|
+
sect.length.should == 3
|
854
|
+
sect[0][:text].should == 'Before Table'
|
855
|
+
sect[2][:text].should == 'After table'
|
856
|
+
table = sect[1][:modifiers][:table]
|
857
|
+
|
858
|
+
compare_table_results(table, [{:end_positions => [72], :values => [["fee.", "\n", "fie."]]}])
|
859
|
+
end
|
860
|
+
|
861
|
+
it 'parses a new line inside a table cell' do
|
862
|
+
src = '{\rtf1 Before Table' +
|
863
|
+
'\trowd\trgaph180\cellx1440\cellx2880\cellx1000' +
|
864
|
+
'\pard\intbl fee.\cell' +
|
865
|
+
'\pard\intbl\cell' +
|
866
|
+
'\pard\intbl fie.\cell\row ' +
|
867
|
+
'After table}'
|
868
|
+
d = parser.parse(src)
|
869
|
+
|
870
|
+
sect = d.sections
|
871
|
+
sect.length.should == 3
|
872
|
+
sect[0][:text].should == 'Before Table'
|
873
|
+
sect[2][:text].should == 'After table'
|
874
|
+
table = sect[1][:modifiers][:table]
|
875
|
+
|
876
|
+
compare_table_results(table, [{:end_positions => [72, 144, 50], :values => [["fee."], [""], ["fie."]]}])
|
877
|
+
end
|
878
|
+
|
879
|
+
it 'parses a grouped cell' do
|
880
|
+
src = '{\rtf1 Before Table\trowd\cellx1440\cellx2880\cellx1000 \pard ' +
|
881
|
+
'{\fs20 Familiar }{\cell }' +
|
882
|
+
'{\fs20 Alignment }{\cell }' +
|
883
|
+
'\pard \intbl {\fs20 Arcane Spellcaster Level}{\cell }' +
|
884
|
+
'\pard {\b\fs18 \trowd \trgaph108\trleft-108\cellx1000\row }After table}'
|
885
|
+
d = parser.parse(src)
|
886
|
+
|
887
|
+
sect = d.sections
|
888
|
+
|
889
|
+
sect.length.should == 3
|
890
|
+
sect[0][:text].should == 'Before Table'
|
891
|
+
sect[2][:text].should == 'After table'
|
892
|
+
table = sect[1][:modifiers][:table]
|
893
|
+
|
894
|
+
compare_table_results(table, [{:end_positions => [72, 144, 50],
|
895
|
+
:values => [["Familiar "], ["Alignment "], ['Arcane Spellcaster Level']]}])
|
896
|
+
end
|
897
|
+
|
898
|
+
it 'parses cells' do
|
899
|
+
src = '{\rtf1\trowd\trgaph108\trleft-108\cellx1440\cellx2880' +
|
900
|
+
'\intbl{\fs20 Familiar }{\cell }' +
|
901
|
+
'{\fs20 Alignment }{\cell }}'
|
902
|
+
|
903
|
+
d = parser.parse(src)
|
904
|
+
table = d.sections[0][:modifiers][:table]
|
905
|
+
|
906
|
+
compare_table_results(table, [{:end_positions => [72, 144], :values => [['Familiar '], ['Alignment ']]}])
|
907
|
+
end
|
908
|
+
|
909
|
+
it 'parses blank rows' do
|
910
|
+
src = '{\rtf1\trowd \trgaph108\trleft-108\cellx1440' +
|
911
|
+
'\intbl{\fs20 Familiar }{\cell }' +
|
912
|
+
'\pard\plain \intbl {\trowd \trgaph108\trleft-108\cellx1440\row } ' +
|
913
|
+
'Improved animal}'
|
914
|
+
d = parser.parse(src)
|
915
|
+
|
916
|
+
sect = d.sections
|
917
|
+
sect.length.should == 2
|
918
|
+
sect[1][:text].should == ' Improved animal'
|
919
|
+
sect[1][:modifiers].should == {}
|
920
|
+
|
921
|
+
table = sect[0][:modifiers][:table]
|
922
|
+
compare_table_results(table, [{:end_positions => [72], :values => [['Familiar ']]}])
|
923
|
+
end
|
924
|
+
end
|
925
|
+
end
|
926
|
+
end
|