rods 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest +5 -0
- data/README +269 -0
- data/Rakefile +11 -0
- data/lib/example.rb +88 -0
- data/lib/rods.rb +2487 -0
- data/rods.gemspec +30 -0
- metadata +80 -0
data/Manifest
ADDED
data/README
ADDED
@@ -0,0 +1,269 @@
|
|
1
|
+
= RODS - Ruby Open Document Spreadsheet
|
2
|
+
This class provides a convenient interface for reading and writing
|
3
|
+
spreadsheets conforming to Open Document Format v1.1. with Ruby 1.9.1 and 1.8.7..
|
4
|
+
Installiation of an office-application (LibreOffice, OpenOffice.org) is not required as the code directly
|
5
|
+
manipulates the XML-files in the zipped *.ods-container.
|
6
|
+
|
7
|
+
= Copyright
|
8
|
+
Copyright (c) <em>Dr. Heinz Breinlinger</em> (2011).
|
9
|
+
Licensed under the same terms as Ruby. No warranty is provided.
|
10
|
+
|
11
|
+
= Disclaimer
|
12
|
+
At the time of this publishing and stage of development RODS just suits my personal needs
|
13
|
+
* to provide an intuitively to use, purpose oriented interface for
|
14
|
+
* automating most of the tasks I personally encountered so far.
|
15
|
+
|
16
|
+
The code has not been tested on a wide variety of systems, languages or use cases yet.
|
17
|
+
At the time of this writing the script 'example.rb' provided in commented version below proved to work for
|
18
|
+
* Linux
|
19
|
+
* Ubuntu 10.10 64-Bit (German)
|
20
|
+
* OpenOffice.org 3.2 (German)
|
21
|
+
* Ruby 1.9.1 and 1.8.7
|
22
|
+
* Mac
|
23
|
+
* OS X 10.6 (German)
|
24
|
+
* OpenOffice.org 3.2 (German)
|
25
|
+
* Ruby 1.8.7 (1.9.1 not tried)
|
26
|
+
* Windows
|
27
|
+
* WinXP 32-Bit (German)
|
28
|
+
* OpenOffice.org 3.0 (German)
|
29
|
+
* Ruby 1.9.1 and 1.8.7
|
30
|
+
|
31
|
+
= Howto
|
32
|
+
|
33
|
+
$ sudo gem install rods (root-privileges necessary)
|
34
|
+
|
35
|
+
# coding: UTF-8
|
36
|
+
#
|
37
|
+
# Author: Dr. Heinz Breinlinger
|
38
|
+
#
|
39
|
+
require 'rubygems'
|
40
|
+
require 'rods'
|
41
|
+
#
|
42
|
+
#-----------------------------------------------------------------
|
43
|
+
# Open a spreadsheet in the working directory.
|
44
|
+
# If the file is elsewhere, prepend the path according to your
|
45
|
+
# operating systems notation, e.g. "/home/heinz/myfile.ods".
|
46
|
+
#-----------------------------------------------------------------
|
47
|
+
mySheet=Rods.new("Template.ods") # empty or prefilled sheet
|
48
|
+
#-----------------------------------------------------------------
|
49
|
+
# Rename, insert and delete some tables
|
50
|
+
#-----------------------------------------------------------------
|
51
|
+
mySheet.renameTable("Tabelle1","not needed")
|
52
|
+
mySheet.insertTable("example")
|
53
|
+
#-----------------------------------------------------------------
|
54
|
+
# Very important: tell RODS, what table you want to work with subsequently,
|
55
|
+
# if the table is not the first in the spreadsheet.
|
56
|
+
#-----------------------------------------------------------------
|
57
|
+
mySheet.setCurrentTable("example")
|
58
|
+
mySheet.deleteTable("Tabelle2")
|
59
|
+
mySheet.deleteTable("Tabelle3")
|
60
|
+
#-----------------------------------------------------------------
|
61
|
+
# Fill the sheet with values. All values are strings at the
|
62
|
+
# user-level and are accompanied by a type-token.
|
63
|
+
# The type affects the internal representation and the style of
|
64
|
+
# the corresponding cell.
|
65
|
+
# When the type is a formula, the resulting type is appended.
|
66
|
+
# Valid types are
|
67
|
+
# "string","time","date","float","percent","currency","formula",
|
68
|
+
# "formula:float","formula:time","formula:date","formula:currency".
|
69
|
+
#-----------------------------------------------------------------
|
70
|
+
mySheet.writeCell(1,1,"date","31.12.2010")
|
71
|
+
mySheet.writeCell(2,1,"formula:date","=A1+1")
|
72
|
+
#-----------------------------------------------------------------
|
73
|
+
# Alter the data-style for the following 2 date-values.
|
74
|
+
#
|
75
|
+
# Date-styles affect, how values are displayed, i.e. their "meaning".
|
76
|
+
# Within that "meaning" styles (not data-styles !) affect the look
|
77
|
+
# (color, indentation, 'italic', etc.)
|
78
|
+
# Data-styles are predefined by RODS and (so far) unique for every type.
|
79
|
+
# Only date-values have to different data-styles implemented.
|
80
|
+
#-----------------------------------------------------------------
|
81
|
+
mySheet.setDateFormat("myDateDay") # "05.01.2011" -> "Mi" (if German)
|
82
|
+
#-----------------------------------------------------------------
|
83
|
+
# Add 2 formulas with date-values as results.
|
84
|
+
#-----------------------------------------------------------------
|
85
|
+
mySheet.writeCell(1,2,"formula:date","=A1")
|
86
|
+
mySheet.writeCell(2,2,"formula:date","=A2")
|
87
|
+
#-----------------------------------------------------------------
|
88
|
+
# Reset the data-style for date-values
|
89
|
+
#-----------------------------------------------------------------
|
90
|
+
mySheet.setDateFormat("myDate") # back to "DD.MM.YYYY"
|
91
|
+
#-----------------------------------------------------------------
|
92
|
+
# Continue with 2 time-values
|
93
|
+
#-----------------------------------------------------------------
|
94
|
+
mySheet.writeCell(1,3,"time","13:37")
|
95
|
+
mySheet.writeCell(2,3,"time","20:15")
|
96
|
+
#-----------------------------------------------------------------
|
97
|
+
# Write 2 currency-values
|
98
|
+
#-----------------------------------------------------------------
|
99
|
+
mySheet.writeCell(1,4,"currency","19,99")
|
100
|
+
mySheet.writeCell(2,4,"currency","-7,78")
|
101
|
+
#-----------------------------------------------------------------
|
102
|
+
# Insert a formula for the time-difference
|
103
|
+
#-----------------------------------------------------------------
|
104
|
+
cell=mySheet.writeGetCell(3,3,"formula:time","=C2-C1")
|
105
|
+
#-----------------------------------------------------------------
|
106
|
+
# Set a border, center the text, change background- and font-color.
|
107
|
+
#-----------------------------------------------------------------
|
108
|
+
mySheet.setAttributes(cell,{ "border" => "0.01cm solid turquoise",
|
109
|
+
"text-align" => "center",
|
110
|
+
"background-color" => "yellow2",
|
111
|
+
"color" => "blue"})
|
112
|
+
#-----------------------------------------------------------------
|
113
|
+
# Inset a formula for the sum of the above currency-values
|
114
|
+
#-----------------------------------------------------------------
|
115
|
+
cell=mySheet.writeGetCell(3,4,"formula:currency","=D2+D1")
|
116
|
+
#-----------------------------------------------------------------
|
117
|
+
# Apply different borders and display the font italic and bold.
|
118
|
+
#-----------------------------------------------------------------
|
119
|
+
mySheet.setAttributes(cell,{ "border-right" => "0.05cm solid magenta4",
|
120
|
+
"border-bottom" => "0.03cm solid lightgreen",
|
121
|
+
"border-top" => "0.08cm solid salmon",
|
122
|
+
"font-style" => "italic",
|
123
|
+
"font-weight" => "bold"})
|
124
|
+
#-----------------------------------------------------------------
|
125
|
+
# Create a new style for percent-values and apply it to a new
|
126
|
+
# percent-value.
|
127
|
+
# Be aware that a valid data-style is chosen ("myPercentFormat"
|
128
|
+
# is RODS' default-data-style for percent-values.)
|
129
|
+
#-----------------------------------------------------------------
|
130
|
+
mySheet.writeStyleAbbr({"name" => "myNewPercentStyle",
|
131
|
+
"margin-left" => "0.3cm",
|
132
|
+
"text-align" => "start",
|
133
|
+
"color" => "blue",
|
134
|
+
"border" => "0.01cm solid black",
|
135
|
+
"font-style" => "italic",
|
136
|
+
"data-style-name" => "myPercentFormat", # <- data-style !
|
137
|
+
"font-weight" => "bold"})
|
138
|
+
cell=mySheet.writeGetCell(4,2,"percent","4,71")
|
139
|
+
mySheet.setStyle(cell,"myNewPercentStyle")
|
140
|
+
#-----------------------------------------------------------------
|
141
|
+
# Add a comment-field, change the font-color and font-style.
|
142
|
+
#-----------------------------------------------------------------
|
143
|
+
mySheet.writeComment(cell,"by Dr. Heinz Breinlinger")
|
144
|
+
cell=mySheet.writeGetCell(4,3,"formula:time","=B4*C3")
|
145
|
+
mySheet.setAttributes(cell,{ "color" => "lightmagenta",
|
146
|
+
"font-style" => "italic"})
|
147
|
+
#-----------------------------------------------------------------
|
148
|
+
# Insert a formula for the percentage of a currency-value.
|
149
|
+
#-----------------------------------------------------------------
|
150
|
+
cell=mySheet.writeGetCell(4,4,"formula:currency","=B4*D3")
|
151
|
+
#-----------------------------------------------------------------
|
152
|
+
# Change some attributes of the cell.
|
153
|
+
#-----------------------------------------------------------------
|
154
|
+
mySheet.setAttributes(cell,{ "color" => "turquoise7",
|
155
|
+
"text-align" => "center",
|
156
|
+
"font-style" => "bold"})
|
157
|
+
#-----------------------------------------------------------------
|
158
|
+
# Create a new style and apply it to 2 new text-values.
|
159
|
+
#-----------------------------------------------------------------
|
160
|
+
mySheet.writeStyleAbbr({"name" => "myBold",
|
161
|
+
"text-align" => "end",
|
162
|
+
"font-weight" => "bold",
|
163
|
+
"background-color" => "purple"})
|
164
|
+
cell=mySheet.writeGetCell(3,1,"string","Diff/Sum")
|
165
|
+
mySheet.setStyle(cell,"myBold")
|
166
|
+
cell=mySheet.writeGetCell(4,1,"string","Percent")
|
167
|
+
mySheet.setStyle(cell,"myBold")
|
168
|
+
#-----------------------------------------------------------------
|
169
|
+
# Insert a text with an annotation.
|
170
|
+
#-----------------------------------------------------------------
|
171
|
+
cell=mySheet.writeGetCell(6,1,"string","annotation")
|
172
|
+
mySheet.writeComment(cell,"C3,C4,D3,D4 are formulas")
|
173
|
+
#-----------------------------------------------------------------
|
174
|
+
# Draw a long green vertical line to frame what we created.
|
175
|
+
#-----------------------------------------------------------------
|
176
|
+
1.upto(7){ |row|
|
177
|
+
cell=mySheet.getCell(row,5)
|
178
|
+
mySheet.setAttributes(cell,{ "border-right" => "0.07cm solid green6" })
|
179
|
+
}
|
180
|
+
#-----------------------------------------------------------------
|
181
|
+
# Complete it with a red horicontal line right across.
|
182
|
+
#-----------------------------------------------------------------
|
183
|
+
1.upto(5){ |col|
|
184
|
+
cell=mySheet.getCell(7,col)
|
185
|
+
mySheet.setAttributes(cell,{ "border-bottom" => "0.085cm solid red5" }) #
|
186
|
+
}
|
187
|
+
#-----------------------------------------------------------------
|
188
|
+
# Read and calculate 2 currency-values.
|
189
|
+
#-----------------------------------------------------------------
|
190
|
+
amount=0.0
|
191
|
+
1.upto(2){ |i|
|
192
|
+
row=mySheet.getRow(i)
|
193
|
+
text,type=mySheet.readCellFromRow(row,4)
|
194
|
+
if(type == "currency")
|
195
|
+
amount+=text.to_f
|
196
|
+
end
|
197
|
+
}
|
198
|
+
#-----------------------------------------------------------------
|
199
|
+
# Delete the table we do not need.
|
200
|
+
#-----------------------------------------------------------------
|
201
|
+
mySheet.deleteTable("not needed")
|
202
|
+
#-----------------------------------------------------------------
|
203
|
+
# Save the sheet under a different name (if you don not want to
|
204
|
+
# override the original).
|
205
|
+
#-----------------------------------------------------------------
|
206
|
+
mySheet.saveAs("Example.ods")
|
207
|
+
#-----------------------------------------------------------------
|
208
|
+
# Print the result of the former computation.
|
209
|
+
#-----------------------------------------------------------------
|
210
|
+
puts("Sums up to: #{amount}") # -> "Sums up to: 12.21"
|
211
|
+
#-----------------------------------------------------------------
|
212
|
+
# Open the file we wrote in the previous step again.
|
213
|
+
#-----------------------------------------------------------------
|
214
|
+
mySheet=Rods.new("Example.ods")
|
215
|
+
#-----------------------------------------------------------------
|
216
|
+
# Set the current table for subsequent operations.
|
217
|
+
# This is not necessary here as the table of interest is the
|
218
|
+
# first in the spreadsheet and automatically becomes the default-table.
|
219
|
+
#-----------------------------------------------------------------
|
220
|
+
mySheet.setCurrentTable("example")
|
221
|
+
#-----------------------------------------------------------------
|
222
|
+
# Read a currency-value from the sheet and print it.
|
223
|
+
# Remember that all values are passed to and from the spreadsheet
|
224
|
+
# as strings which get their meaning by the accompanying types.
|
225
|
+
#-----------------------------------------------------------------
|
226
|
+
text,type=mySheet.readCell(2,4)
|
227
|
+
if(text && type)
|
228
|
+
if(type == "currency")
|
229
|
+
puts("not so much: #{text} bucks") # -> "not so much: -7.78 bucks"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
= Caveat
|
234
|
+
|
235
|
+
The XML-structure of a <file>.ods is
|
236
|
+
* first rows
|
237
|
+
* then cells
|
238
|
+
|
239
|
+
As a result
|
240
|
+
|
241
|
+
1.upto(500){ |i|
|
242
|
+
text1,type1=readCell(i,3) # XML-Parser starts from the top-node
|
243
|
+
text2,type2=readCell(i,4) # XML-Parser starts form the top-node
|
244
|
+
puts("Read #{text1} of #{type1} and #{text2} of #{type2}")
|
245
|
+
}
|
246
|
+
|
247
|
+
is significantly slower than the slight variation
|
248
|
+
|
249
|
+
1.upto(500){ |i|
|
250
|
+
row=getRow(i) # XML-Parser starts from top-node
|
251
|
+
text1,type1=readCellFromRow(row,3) # XML-Parser continues from row-node
|
252
|
+
text2,type2=readCellFromRow(row,4) # XML-Parser continues from row-node
|
253
|
+
puts("Read #{text1} of #{type1} and #{text2} of #{type2}
|
254
|
+
}
|
255
|
+
|
256
|
+
This difference hardly matters while dealing with small documents, but degrades performance significantly when you
|
257
|
+
move up or down a sheet with a lot of rows and process several cells on the same row !
|
258
|
+
|
259
|
+
On the ATOM-Nettop I developed the gem the script above took 2-3 seconds and on my Core-i7-Notebook it was finished so quickly
|
260
|
+
that I supposed it had halted on an immediate error, so: don't be concerned, just be smart and try :-)
|
261
|
+
|
262
|
+
= Standards
|
263
|
+
* Open Document Format for Office Applications (Open Document) v1.1 based on OpenOffice.org (OOo)
|
264
|
+
* http://www.oasis-open.org/specs/#opendocumentv1.1
|
265
|
+
* Hint: Download the pdf-version. Reading the html-version online was to slow at least
|
266
|
+
on my attempts.
|
267
|
+
* W3C Extensible Stylesheet Language (XSL) Version 1.0 (W3C Recommendation 15 October 2001)
|
268
|
+
* http://www.w3.org/TR/2001/REC-xsl-20011015/
|
269
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('rods', '0.0.1') do |p|
|
6
|
+
p.description = "This class provides a convenient interface for fast reading and writing spreadsheets conforming to Open Document Format v1.1. with Ruby 1.9.1 and 1.8.7. This format is e.g. used by OpenOffice.org and LibreOffice."
|
7
|
+
p.summary = "Automation of OpenOffice/LibreOffice by batch-processing of spreadsheets conforming to Open Document v1.1"
|
8
|
+
p.url = ""
|
9
|
+
p.author = "Dr. Heinz Breinlinger"
|
10
|
+
p.email = "rods.ruby@online.de"
|
11
|
+
end
|
data/lib/example.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rods'
|
4
|
+
mySheet=Rods.new("/home/heinz/Work/Template.ods",["de","DE","€","EUR"])
|
5
|
+
# aternative values for United States would be
|
6
|
+
# mySheet=Rods.new("/home/heinz/Work/Template.ods",["us","US","$","DOLLAR"])
|
7
|
+
mySheet.renameTable("Tabelle1","not needed")
|
8
|
+
mySheet.insertTable("example")
|
9
|
+
mySheet.setCurrentTable("example")
|
10
|
+
mySheet.deleteTable("Tabelle2")
|
11
|
+
mySheet.deleteTable("Tabelle3")
|
12
|
+
mySheet.writeCell(1,1,"date","31.12.2010")
|
13
|
+
mySheet.writeCell(2,1,"formula:date","=A1+1")
|
14
|
+
mySheet.setDateFormat("myDateDay")
|
15
|
+
mySheet.writeCell(1,2,"formula:date","=A1")
|
16
|
+
mySheet.writeCell(2,2,"formula:date","=A2")
|
17
|
+
mySheet.setDateFormat("myDate")
|
18
|
+
mySheet.writeCell(1,3,"time","13:37")
|
19
|
+
mySheet.writeCell(2,3,"time","20:15")
|
20
|
+
mySheet.writeCell(1,4,"currency","19,99")
|
21
|
+
mySheet.writeCell(2,4,"currency","-7,78")
|
22
|
+
cell=mySheet.writeGetCell(3,3,"formula:time","=C2-C1")
|
23
|
+
mySheet.setAttributes(cell,{ "border" => "0.01cm solid turquoise",
|
24
|
+
"text-align" => "center",
|
25
|
+
"background-color" => "yellow2",
|
26
|
+
"color" => "blue"})
|
27
|
+
cell=mySheet.writeGetCell(3,4,"formula:currency","=D2+D1")
|
28
|
+
mySheet.setAttributes(cell,{ "border-right" => "0.05cm solid magenta4",
|
29
|
+
"border-bottom" => "0.03cm solid lightgreen",
|
30
|
+
"border-top" => "0.08cm solid salmon",
|
31
|
+
"font-style" => "italic",
|
32
|
+
"font-weight" => "bold"})
|
33
|
+
mySheet.writeStyleAbbr({"name" => "myNewPercentStyle",
|
34
|
+
"margin-left" => "0.3cm",
|
35
|
+
"text-align" => "start",
|
36
|
+
"color" => "blue",
|
37
|
+
"border" => "0.01cm solid black",
|
38
|
+
"font-style" => "italic",
|
39
|
+
"data-style-name" => "myPercentFormat",
|
40
|
+
"font-weight" => "bold"})
|
41
|
+
cell=mySheet.writeGetCell(4,2,"percent","4,71")
|
42
|
+
mySheet.setStyle(cell,"myNewPercentStyle")
|
43
|
+
mySheet.writeComment(cell,"by Dr. Heinz Breinlinger")
|
44
|
+
cell=mySheet.writeGetCell(4,3,"formula:time","=B4*C3")
|
45
|
+
mySheet.setAttributes(cell,{ "color" => "lightmagenta",
|
46
|
+
"font-style" => "italic"})
|
47
|
+
cell=mySheet.writeGetCell(4,4,"formula:currency","=B4*D3")
|
48
|
+
mySheet.setAttributes(cell,{ "color" => "turquoise7",
|
49
|
+
"text-align" => "center",
|
50
|
+
"font-style" => "bold"})
|
51
|
+
mySheet.writeStyleAbbr({"name" => "myBold",
|
52
|
+
"text-align" => "end",
|
53
|
+
"font-weight" => "bold",
|
54
|
+
"background-color" => "purple"})
|
55
|
+
cell=mySheet.writeGetCell(3,1,"string","Diff/Sum")
|
56
|
+
mySheet.setStyle(cell,"myBold")
|
57
|
+
cell=mySheet.writeGetCell(4,1,"string","Percent")
|
58
|
+
mySheet.setStyle(cell,"myBold")
|
59
|
+
cell=mySheet.writeGetCell(6,1,"string","annotation")
|
60
|
+
mySheet.writeComment(cell,"C3,C4,D3,D4 are formulas")
|
61
|
+
1.upto(7){ |row|
|
62
|
+
cell=mySheet.getCell(row,5)
|
63
|
+
mySheet.setAttributes(cell,{ "border-right" => "0.07cm solid green6" })
|
64
|
+
}
|
65
|
+
1.upto(5){ |col|
|
66
|
+
cell=mySheet.getCell(7,col)
|
67
|
+
mySheet.setAttributes(cell,{ "border-bottom" => "0.085cm solid red5" }) #
|
68
|
+
}
|
69
|
+
amount=0.0
|
70
|
+
1.upto(2){ |i|
|
71
|
+
row=mySheet.getRow(i)
|
72
|
+
text,type=mySheet.readCellFromRow(row,4)
|
73
|
+
if(type == "currency")
|
74
|
+
amount+=text.to_f
|
75
|
+
end
|
76
|
+
}
|
77
|
+
mySheet.deleteTable("not needed")
|
78
|
+
mySheet.saveAs("/home/heinz/Work/Example.ods")
|
79
|
+
puts("Sums up to: #{amount}") # -> "Sums up to: 12.21"
|
80
|
+
#----------Open Example.ods again---------------------------
|
81
|
+
mySheet=Rods.new("/home/heinz/Work/Example.ods")
|
82
|
+
mySheet.setCurrentTable("example") # not essential, as 'example' is the 1st and only table
|
83
|
+
text,type=mySheet.readCell(2,4)
|
84
|
+
if(text && type)
|
85
|
+
if(type == "currency")
|
86
|
+
puts("not so much: #{text} bucks") # -> "not so much: -7.78 bucks"
|
87
|
+
end
|
88
|
+
end
|
data/lib/rods.rb
ADDED
@@ -0,0 +1,2487 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
#
|
3
|
+
# = RODS - Ruby Open Document Spreadsheet
|
4
|
+
# This class provides a convenient interface for fast reading and writing
|
5
|
+
# spreadsheets conforming to Open Document Format v1.1. with Ruby 1.9.1 and 1.8.7..
|
6
|
+
# Installiation of an office-application (LibreOffice, OpenOffice.org) is not required as the code directly
|
7
|
+
# manipulates the XML-files in the zipped *.ods-container.
|
8
|
+
#
|
9
|
+
# = Copyright
|
10
|
+
# Copyright (c) <em>Dr. Heinz Breinlinger</em> (2011).
|
11
|
+
# Licensed under the same terms as Ruby. No warranty is provided.
|
12
|
+
#
|
13
|
+
# = Prerequisites
|
14
|
+
# As *.ods-files are zipped archives you need to
|
15
|
+
# $ sudo gem install zip
|
16
|
+
#
|
17
|
+
# = What ist does
|
18
|
+
# * open and save a spreadsheet
|
19
|
+
# * save the spreadsheet under a different location/name
|
20
|
+
# * add, delete and rename tables
|
21
|
+
# * tables are always inserted at the end of the spreadsheet
|
22
|
+
# * read cells with content of type
|
23
|
+
# * string
|
24
|
+
# * formula
|
25
|
+
# * percent
|
26
|
+
# * time
|
27
|
+
# * date
|
28
|
+
# * currency
|
29
|
+
# * write cells with content of type
|
30
|
+
# * string
|
31
|
+
# * formula
|
32
|
+
# * formula:float
|
33
|
+
# * formula:date
|
34
|
+
# * formula:time
|
35
|
+
# * formula:currency
|
36
|
+
# * percent
|
37
|
+
# * time
|
38
|
+
# * date
|
39
|
+
# * 2 different date formats (German so far)
|
40
|
+
# * currency
|
41
|
+
# * German
|
42
|
+
# * English
|
43
|
+
# * insert annotations
|
44
|
+
# * create styles and apply styles as well as features to cells
|
45
|
+
# * style-name
|
46
|
+
# * font-color
|
47
|
+
# * background-color
|
48
|
+
# * border
|
49
|
+
# * all four sides or
|
50
|
+
# * top,bottom,left,right
|
51
|
+
# * indentation
|
52
|
+
# * alignment
|
53
|
+
# * font-weight ('bold')
|
54
|
+
# * font-style ('italic')
|
55
|
+
# * data-style-name (most important for language specific formattings)
|
56
|
+
#
|
57
|
+
# = Tutorial
|
58
|
+
# Please refer to README for how to use the interface.
|
59
|
+
#
|
60
|
+
require 'rubygems'
|
61
|
+
require 'zip/zipfilesystem'
|
62
|
+
require 'rexml/document'
|
63
|
+
|
64
|
+
|
65
|
+
class Rods
|
66
|
+
ROW="row"
|
67
|
+
CELL="cell"
|
68
|
+
TAG="tag"
|
69
|
+
TEXT="text"
|
70
|
+
CHILD="child"
|
71
|
+
STYLES="styles"
|
72
|
+
CONTENT="content"
|
73
|
+
DUMMY="dummy"
|
74
|
+
WIDTH="width"
|
75
|
+
NODE="node"
|
76
|
+
WIDTHEXCEEDED="exceeded"
|
77
|
+
##########################################################################
|
78
|
+
# Convenience-function to switch the default-style for the display of
|
79
|
+
# date-values. The switch is valid for all subsequently created cells with
|
80
|
+
# date-values.
|
81
|
+
# Builtin valid values are
|
82
|
+
# * 'myDate'
|
83
|
+
# * -> "02.01.2011" (German formatting)
|
84
|
+
# * 'myDateDay'
|
85
|
+
# * -> "Su"
|
86
|
+
# Example
|
87
|
+
# mySheet.setDateFormat("myDateDay") # RODS' default format for display of weekday
|
88
|
+
# mySheet.setDateFormat("myDate") # RODS' default format for date ("12.01.2011" German format)
|
89
|
+
#-------------------------------------------------------------------------
|
90
|
+
def setDateFormat(formatName)
|
91
|
+
case formatName
|
92
|
+
when "myDate" then @dateStyle="myDate"
|
93
|
+
when "myDateDay" then @dateStyle="myDateDay"
|
94
|
+
else die("setDateFormat: invalid format-name #{format}")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
##########################################################################
|
98
|
+
# internal: Wrapper around 'puts' to display "all or nothing" according to debug-switch
|
99
|
+
#-------------------------------------------------------------------------
|
100
|
+
def tell(message)
|
101
|
+
# puts("INFO: #{message}")
|
102
|
+
end
|
103
|
+
##########################################################################
|
104
|
+
# internal: Error-routine for displaying fatal error-message and exiting
|
105
|
+
#-------------------------------------------------------------------------
|
106
|
+
def die(message)
|
107
|
+
puts("\nERROR:\n#{message}\nEXIT\n\n")
|
108
|
+
exit
|
109
|
+
end
|
110
|
+
##########################################################################
|
111
|
+
# internal: Returns a new REXML::Element of type 'cell' with repetition-attribute set to 'n'
|
112
|
+
#-------------------------------------------------------------------------
|
113
|
+
def createCell(repetition)
|
114
|
+
return createElement(CELL,repetition)
|
115
|
+
end
|
116
|
+
##########################################################################
|
117
|
+
# internal: Returns a new REXML::Element of type 'row' with repetition-attribute set to 'n'
|
118
|
+
#-------------------------------------------------------------------------
|
119
|
+
def createRow(repetition)
|
120
|
+
return createElement(ROW,repetition)
|
121
|
+
end
|
122
|
+
##########################################################################
|
123
|
+
# internal: Returns a new REXML::Element of type 'row' or 'cell' with repetition-attribute set to 'n'
|
124
|
+
#-------------------------------------------------------------------------
|
125
|
+
def createElement(type,repetition)
|
126
|
+
if(repetition < 1)
|
127
|
+
die("createElement: invalid value for repetition #{repetition}")
|
128
|
+
end
|
129
|
+
if(type == ROW)
|
130
|
+
row=REXML::Element.new("table:table-row")
|
131
|
+
if(repetition > 1)
|
132
|
+
row.attributes["table:number-rows-repeated"]=repetition.to_s
|
133
|
+
end
|
134
|
+
return row
|
135
|
+
elsif(type == CELL)
|
136
|
+
cell=REXML::Element.new("table:table-cell")
|
137
|
+
if(repetition > 1)
|
138
|
+
cell.attributes["table:number-columns-repeated"]=repetition.to_s
|
139
|
+
end
|
140
|
+
return cell
|
141
|
+
else
|
142
|
+
die("createElement: Invalid Type: #{type}")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
##########################################################################
|
146
|
+
# internal: Sets repeption-attribute of REXML::Element of type 'row' or 'cell'
|
147
|
+
#------------------------------------------------------------------------
|
148
|
+
def setRepetition(element,type,repetition)
|
149
|
+
#----------------------------------------------------------------------
|
150
|
+
if((type != ROW) && (type != CELL))
|
151
|
+
die("setRepetition: wrong type #{type}")
|
152
|
+
end
|
153
|
+
if(repetition < 1)
|
154
|
+
die("setRepetition: invalid value for repetition #{repetition}")
|
155
|
+
end
|
156
|
+
if(! element)
|
157
|
+
die("setRepetition: element is nil")
|
158
|
+
end
|
159
|
+
#----------------------------------------------------------------------
|
160
|
+
if(type == ROW)
|
161
|
+
kindOfRepetition="table:number-rows-repeated"
|
162
|
+
elsif(type == CELL)
|
163
|
+
kindOfRepetition="table:number-columns-repeated"
|
164
|
+
else
|
165
|
+
die("setRepetition: wrong type #{type}")
|
166
|
+
end
|
167
|
+
#----------------------------------------------------------------------
|
168
|
+
if(repetition.to_i == 1)
|
169
|
+
element.attributes.delete(kindOfRepetition)
|
170
|
+
else
|
171
|
+
element.attributes[kindOfRepetition]=repetition.to_s
|
172
|
+
end
|
173
|
+
end
|
174
|
+
##########################################################################
|
175
|
+
# Writes the given text to the cell with the given indices.
|
176
|
+
# Creates the cell if not existing.
|
177
|
+
# Formats the cell according to type and returns the cell.
|
178
|
+
# cell=mySheet.writeGetCell(3,3,"formula:time","=C2-C1")
|
179
|
+
# This is useful for a subsequent call to
|
180
|
+
# mySheet.setAttributes(cell,{ "background-color" => "yellow3"})
|
181
|
+
#-------------------------------------------------------------------------
|
182
|
+
def writeGetCell(rowInd,colInd,type,text)
|
183
|
+
cell=getCell(rowInd,colInd)
|
184
|
+
writeText(cell,type,text)
|
185
|
+
return cell
|
186
|
+
end
|
187
|
+
##########################################################################
|
188
|
+
# Writes the given text to the cell with the given indices.
|
189
|
+
# Creates the cell if not existing.
|
190
|
+
# Formats the cell according to type.
|
191
|
+
# mySheet.writeCell(1,1,"date","31.12.2010") # 1st row, 1st column
|
192
|
+
# mySheet.writeCell(2,1,"formula:date","=A1+1")
|
193
|
+
# mySheet.writeCell(1,3,"time","13:37") # German time-format
|
194
|
+
# mySheet.writeCell(1,4,"currency","19,99") # you could also use '.' as a decimal separator
|
195
|
+
#-------------------------------------------------------------------------
|
196
|
+
def writeCell(rowInd,colInd,type,text)
|
197
|
+
cell=getCell(rowInd,colInd)
|
198
|
+
writeText(cell,type,text)
|
199
|
+
end
|
200
|
+
##########################################################################
|
201
|
+
# Writes the given text to the cell with the given index in the given row.
|
202
|
+
# Row is a REXML::Element.
|
203
|
+
# Creates the cell if not existing.
|
204
|
+
# Formats the cell according to type and returns the cell.
|
205
|
+
# row=mySheet.getRow(17)
|
206
|
+
# cell=mySheet.writeGetCellFromRow(row,4,"formula:currency","=B5*1,19")
|
207
|
+
#-------------------------------------------------------------------------
|
208
|
+
def writeGetCellFromRow(row,colInd,type,text)
|
209
|
+
cell=getCellFromRow(row,colInd)
|
210
|
+
writeText(cell,type,text)
|
211
|
+
return cell
|
212
|
+
end
|
213
|
+
##########################################################################
|
214
|
+
# Writes the given text to the cell with the given index in the given row.
|
215
|
+
# Row is a REXML::Element.
|
216
|
+
# Creates the cell if it does not exist.
|
217
|
+
# Formats the cell according to type.
|
218
|
+
# row=mySheet.getRow(3)
|
219
|
+
# mySheet.writeCellFromRow(row,1,"date","28.12.2010")
|
220
|
+
# mySheet.writeCellFromRow(row,2,"formula:date","=A1+3")
|
221
|
+
#-------------------------------------------------------------------------
|
222
|
+
def writeCellFromRow(row,colInd,type,text)
|
223
|
+
cell=getCellFromRow(row,colInd)
|
224
|
+
writeText(cell,type,text)
|
225
|
+
end
|
226
|
+
##########################################################################
|
227
|
+
# Returns the cell at the given index in the given row.
|
228
|
+
# Cell and row are REXML::Elements.
|
229
|
+
# The cell is created if it does not exist.
|
230
|
+
# row=mySheet.getRow(15)
|
231
|
+
# cell=mySheet.getCellFromRow(row,17) # 17th cell of 15th row
|
232
|
+
# Looks a bit strange compared to
|
233
|
+
# cell=mySheet.getCell(15,17)
|
234
|
+
# but is considerably faster if you are operating on several cells of the
|
235
|
+
# same row as after locating the first cell of the row the XML-Parser can start
|
236
|
+
# from the node of the already found row instead of having to locate the
|
237
|
+
# row over and over again.
|
238
|
+
#-------------------------------------------------------------------------
|
239
|
+
def getCellFromRow(row,colInd)
|
240
|
+
return getChildByIndex(row,CELL,colInd)
|
241
|
+
end
|
242
|
+
##########################################################################
|
243
|
+
# Returns the cell at the given indices.
|
244
|
+
# Cell is a REXML::Element.
|
245
|
+
# The cell is created if it does not exist.
|
246
|
+
# cell=mySheet.getCell(14,37)
|
247
|
+
#-------------------------------------------------------------------------
|
248
|
+
def getCell(rowInd,colInd)
|
249
|
+
row=getRow(rowInd)
|
250
|
+
return getChildByIndex(row,CELL,colInd)
|
251
|
+
end
|
252
|
+
##########################################################################
|
253
|
+
# Returns the row at the given index.
|
254
|
+
# Row is a REXML::Element.
|
255
|
+
# The row is created if it does not exist.
|
256
|
+
# row=getRow(1)
|
257
|
+
# 1.upto(500){ |i|
|
258
|
+
# row=getRow(i)
|
259
|
+
# text1,type1=readCellFromRow(row,3)
|
260
|
+
# text2,type2=readCellFromRow(row,4) # XML-Parser can start from row-node instead of root-node !
|
261
|
+
# puts("Read #{text1} of #{type1} and #{text2} of #{type2}
|
262
|
+
# }
|
263
|
+
#-------------------------------------------------------------------------
|
264
|
+
def getRow(rowInd)
|
265
|
+
currentTable=@tables[@currentTableName][NODE]
|
266
|
+
return getChildByIndex(currentTable,ROW,rowInd)
|
267
|
+
end
|
268
|
+
##########################################################################
|
269
|
+
# internal: returns the child REXML::Element of the given parent, type
|
270
|
+
# ('row' or 'cell') and index within the parent-element.
|
271
|
+
# The child is created if it does not exist.
|
272
|
+
#------------------------------------------------------------------------
|
273
|
+
def getChildByIndex(parent,type,index)
|
274
|
+
i=0
|
275
|
+
lastElement=nil
|
276
|
+
#----------------------------------------------------------------------
|
277
|
+
# Validierung
|
278
|
+
#----------------------------------------------------------------------
|
279
|
+
if((type != ROW) && (type != CELL))
|
280
|
+
die("getChildByIndex: wrong type #{type}")
|
281
|
+
end
|
282
|
+
if(index < 1)
|
283
|
+
die("getChildByIndex: invalid index #{index}")
|
284
|
+
end
|
285
|
+
if(! parent)
|
286
|
+
die("getChildByIndex: parent-element is nil")
|
287
|
+
end
|
288
|
+
#----------------------------------------------------------------------
|
289
|
+
# Typabhaengige Vorbelegungen
|
290
|
+
#----------------------------------------------------------------------
|
291
|
+
if(type == ROW)
|
292
|
+
kindOfElement="table:table-row"
|
293
|
+
kindOfRepetition="table:number-rows-repeated"
|
294
|
+
elsif(type == CELL)
|
295
|
+
kindOfElement="table:table-cell"
|
296
|
+
kindOfRepetition="table:number-columns-repeated"
|
297
|
+
if(index > @tables[@currentTableName][WIDTH])
|
298
|
+
@tables[@currentTableName][WIDTH]=index
|
299
|
+
@tables[@currentTableName][WIDTHEXCEEDED]=true
|
300
|
+
end
|
301
|
+
else
|
302
|
+
die("getChildByIndex: wrong type #{type}")
|
303
|
+
end
|
304
|
+
#----------------------------------------------------------------------
|
305
|
+
# Durchlauf
|
306
|
+
# 'i' hat stets den zum aktuellen Element inkl. Wiederholungen gehoerigen
|
307
|
+
# Index
|
308
|
+
#----------------------------------------------------------------------
|
309
|
+
parent.elements.each(kindOfElement){ |element|
|
310
|
+
i+=1
|
311
|
+
lastElement=element
|
312
|
+
#--------------------------------------------------------------------
|
313
|
+
# Suchindex erreicht ?
|
314
|
+
#--------------------------------------------------------------------
|
315
|
+
if(i == index)
|
316
|
+
#------------------------------------------------------------------
|
317
|
+
# Element mit Wiederholungen ?
|
318
|
+
# => Wiederholungsattribut loeschen, Element mit verbleibenden Leerelementen
|
319
|
+
# anhaengen, Rueckgabe
|
320
|
+
#------------------------------------------------------------------
|
321
|
+
if(repetition=element.attributes[kindOfRepetition])
|
322
|
+
numEmptyElementsAfter=repetition.to_i-1
|
323
|
+
if(numEmptyElementsAfter < 1)
|
324
|
+
die("getChildByIndex: new repetition < 1")
|
325
|
+
end
|
326
|
+
setRepetition(element,type,1)
|
327
|
+
element.next_sibling=createElement(type,numEmptyElementsAfter)
|
328
|
+
end
|
329
|
+
return element
|
330
|
+
#--------------------------------------------------------------------
|
331
|
+
# Suchindex noch nicht erreicht ?
|
332
|
+
#--------------------------------------------------------------------
|
333
|
+
elsif(i < index)
|
334
|
+
#------------------------------------------------------------------
|
335
|
+
# Wiederholungsattribut ?
|
336
|
+
#------------------------------------------------------------------
|
337
|
+
if(repetition=element.attributes[kindOfRepetition])
|
338
|
+
indexOfLastEmptyElement=i+repetition.to_i-1
|
339
|
+
#----------------------------------------------------------------
|
340
|
+
# Liegt letzte Wiederholung noch vor dem Suchindex ?
|
341
|
+
#----------------------------------------------------------------
|
342
|
+
if(indexOfLastEmptyElement < index)
|
343
|
+
i=indexOfLastEmptyElement
|
344
|
+
#----------------------------------------------------------------
|
345
|
+
# ... nein => Aufteilung des wiederholten Bereiches
|
346
|
+
#----------------------------------------------------------------
|
347
|
+
else
|
348
|
+
numEmptyElementsBefore=index-i
|
349
|
+
numEmptyElementsAfter=indexOfLastEmptyElement-index
|
350
|
+
#-------------------------------------------------
|
351
|
+
# Wiederholungszahl des aktuellen Elementes reduzieren
|
352
|
+
#-------------------------------------------------
|
353
|
+
setRepetition(element,type,numEmptyElementsBefore)
|
354
|
+
#-------------------------------------------------
|
355
|
+
# Neues, zurueckzugebendes Element einfuegen
|
356
|
+
#-------------------------------------------------
|
357
|
+
element.next_sibling=createElement(type,1)
|
358
|
+
#-------------------------------------------------
|
359
|
+
# ggf. weitere Leerelemente anhaengen
|
360
|
+
#-------------------------------------------------
|
361
|
+
if(numEmptyElementsAfter > 0)
|
362
|
+
element.next_sibling.next_sibling=createElement(type,numEmptyElementsAfter)
|
363
|
+
end
|
364
|
+
#-------------------------------------------------
|
365
|
+
# => Rueckgabe des Elementes mit Suchindex
|
366
|
+
#-------------------------------------------------
|
367
|
+
return element.next_sibling
|
368
|
+
end # letzte Leerzelle < Index
|
369
|
+
end # falls Wiederholung
|
370
|
+
end # i =|< index
|
371
|
+
}
|
372
|
+
#-----------------------------------------------------------------------
|
373
|
+
# Index ausserhalb bisheriger Elemente inkl. wiederholter Leerelemente
|
374
|
+
#-----------------------------------------------------------------------
|
375
|
+
numEmptyElementsBefore=index-i-1
|
376
|
+
#----------------------------------------------------------------------
|
377
|
+
# Hatte Vater bereits vor dem gesuchten Kind liegende Kinder ?
|
378
|
+
#----------------------------------------------------------------------
|
379
|
+
if(i > 0) # => lastElement != nil
|
380
|
+
if(numEmptyElementsBefore > 0)
|
381
|
+
lastElement.next_sibling=createElement(type,numEmptyElementsBefore)
|
382
|
+
return (lastElement.next_sibling.next_sibling=createElement(type,1))
|
383
|
+
else
|
384
|
+
return(lastElement.next_sibling=createElement(type,1))
|
385
|
+
end
|
386
|
+
#----------------------------------------------------------------------
|
387
|
+
# Nein, neues Kind ist erstes Kind
|
388
|
+
#----------------------------------------------------------------------
|
389
|
+
else
|
390
|
+
#-----------------------------------------------
|
391
|
+
# Hat das neue Kind Index 1 ?
|
392
|
+
#-----------------------------------------------
|
393
|
+
if(index == 1)
|
394
|
+
newElement=createElement(type,1)
|
395
|
+
parent.add(newElement)
|
396
|
+
return newElement
|
397
|
+
#-----------------------------------------------
|
398
|
+
# Nein, Kind benoetigt "Leergeschwister" vorneweg
|
399
|
+
#-----------------------------------------------
|
400
|
+
else
|
401
|
+
newElement=createElement(type,numEmptyElementsBefore)
|
402
|
+
parent.add(newElement)
|
403
|
+
newElement.next_sibling=createElement(type,1)
|
404
|
+
return newElement.next_sibling
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
##########################################################################
|
409
|
+
# internal: Determines the number of tables, initializes the internal
|
410
|
+
# table-administration via Hashes and sets the current default-table for
|
411
|
+
# all subsequent operations (first table of spreadsheet).
|
412
|
+
#-------------------------------------------------------------------------
|
413
|
+
def initHousekeeping()
|
414
|
+
@spreadSheet=@contentText.elements["/office:document-content/office:body/office:spreadsheet"]
|
415
|
+
die("initHousekeeping: Could not extract office:spreadsheet") unless (@spreadSheet)
|
416
|
+
#------------------------------------------------------------
|
417
|
+
# Fuer alle Tabelleneintraege
|
418
|
+
#------------------------------------------------------------
|
419
|
+
@numTables=0
|
420
|
+
@spreadSheet.elements.each("table:table"){ |table|
|
421
|
+
tableName=table.attributes["table:name"]
|
422
|
+
die("initHouskeeping: Could not extract tableName") if (tableName.empty?())
|
423
|
+
@tables[tableName]=Hash.new()
|
424
|
+
@tables[tableName][NODE]=table
|
425
|
+
@tables[tableName][WIDTH]=getTableWidth(table)
|
426
|
+
@tables[tableName][WIDTHEXCEEDED]=false
|
427
|
+
@numTables+=1
|
428
|
+
}
|
429
|
+
#----------------------------------------------------------------
|
430
|
+
# Nun noch aktuelle, i.e. Default-Tabelle setzen
|
431
|
+
#----------------------------------------------------------------
|
432
|
+
firstTable=@spreadSheet.elements["table:table[1]"]
|
433
|
+
@currentTableName=firstTable.attributes["table:name"]
|
434
|
+
tell("initHousekeeping: number of tables: #{@numTables} ... defaulting to '#{@currentTableName}'")
|
435
|
+
end
|
436
|
+
##########################################################################
|
437
|
+
# Renames the table of the given name and updates the internal table-administration.
|
438
|
+
# mySheet.renameTable("Tabelle1","not needed") # 'Tabelle1' is the default in a German environment
|
439
|
+
#-------------------------------------------------------------------------
|
440
|
+
def renameTable(oldName,newName)
|
441
|
+
die("renameTable: table '#{oldName}' does not exist") unless (@tables.has_key?(oldName))
|
442
|
+
# die("renameTable: table '#{oldName}' cannot be renamed as it is the current table !") if (oldName == @currentTableName)
|
443
|
+
#------------------------------------------------------
|
444
|
+
# XML-Tree anpassen
|
445
|
+
#------------------------------------------------------
|
446
|
+
node=@tables[oldName][NODE]
|
447
|
+
node.attributes["table:name"]=newName
|
448
|
+
#------------------------------------------------------
|
449
|
+
# Tabellen-Hash anpassen
|
450
|
+
#------------------------------------------------------
|
451
|
+
@tables[newName]=@tables[oldName]
|
452
|
+
@tables.delete(oldName)
|
453
|
+
if(oldName == @currentTableName)
|
454
|
+
@currentTableName=newName
|
455
|
+
tell("renameTable: renaming table (which is current table !) '#{oldName}' to '#{newName}'")
|
456
|
+
else
|
457
|
+
tell("renameTable: renaming table '#{oldName}' to '#{newName}'")
|
458
|
+
end
|
459
|
+
end
|
460
|
+
##########################################################################
|
461
|
+
# Sets the table of the given name as the default-table for all subsequent
|
462
|
+
# operations.
|
463
|
+
# mySheet.setCurrentTable("example")
|
464
|
+
#-------------------------------------------------------------------------
|
465
|
+
def setCurrentTable(tableName)
|
466
|
+
die("setCurrentTable: table '#{tableName}' does not exist") unless (@tables.has_key?(tableName))
|
467
|
+
@currentTableName=tableName
|
468
|
+
tell("setCurrentTable: setting #{tableName} as current table")
|
469
|
+
end
|
470
|
+
##########################################################################
|
471
|
+
# Inserts a table of the given name at the end of the spreadsheet and updates
|
472
|
+
# the internal table-administration.
|
473
|
+
# mySheet.insertTable("example")
|
474
|
+
#-------------------------------------------------------------------------
|
475
|
+
def insertTable(tableName)
|
476
|
+
die("insertTable: table '#{tableName}' already exists") if (@tables.has_key?(tableName))
|
477
|
+
#---------------------------------------------------------------------------
|
478
|
+
# XML-Tree schreiben
|
479
|
+
#---------------------------------------------------------------------------
|
480
|
+
newTable=writeXml(@spreadSheet,{TAG => "table:table",
|
481
|
+
"table:name" => tableName,
|
482
|
+
"table:print" => "false",
|
483
|
+
"table:style-name" => "myTable",
|
484
|
+
"child1" => {TAG => "table:table-column",
|
485
|
+
"table:style" => "myColumn",
|
486
|
+
"table:default-cell-style-name" => "Default"},
|
487
|
+
"child2" => {TAG => "table:table-row",
|
488
|
+
"table:style-name" => "myRow",
|
489
|
+
"child3" => {TAG => "table:table-cell"}}})
|
490
|
+
#---------------------------------------------------------------------------
|
491
|
+
# Tabellen-Hash aktualisieren
|
492
|
+
#---------------------------------------------------------------------------
|
493
|
+
@tables[tableName]=Hash.new()
|
494
|
+
@tables[tableName][NODE]=newTable
|
495
|
+
@tables[tableName][WIDTH]=getTableWidth(newTable)
|
496
|
+
@tables[tableName][WIDTHEXCEEDED]=false
|
497
|
+
@numTables+=1
|
498
|
+
end
|
499
|
+
##########################################################################
|
500
|
+
# Deletes the table of the given name and updates the internal
|
501
|
+
# table-administration.
|
502
|
+
# mySheet.deleteTable("Tabelle2")
|
503
|
+
#-------------------------------------------------------------------------
|
504
|
+
def deleteTable(tableName)
|
505
|
+
die("deleteTable: table '#{tableName}' cannot be deleted as it is the current table !") if (tableName == @currentTableName)
|
506
|
+
#----------------------------------------------------
|
507
|
+
# Tabellenname gueltig ?
|
508
|
+
#----------------------------------------------------
|
509
|
+
if(@tables.has_key?(tableName))
|
510
|
+
#--------------------------------------------------
|
511
|
+
# Loeschung in XML-Tree
|
512
|
+
#--------------------------------------------------
|
513
|
+
node=@tables[tableName][NODE]
|
514
|
+
@spreadSheet.elements.delete(node)
|
515
|
+
#--------------------------------------------------
|
516
|
+
# Loeschung in Tabellen-Hash
|
517
|
+
#--------------------------------------------------
|
518
|
+
@tables.delete(tableName)
|
519
|
+
@numTables-=1
|
520
|
+
tell("deleteTable: deleting table #{tableName}")
|
521
|
+
else
|
522
|
+
die("deleteTable: invalid table-name/not existing table: '#{tableName}'")
|
523
|
+
end
|
524
|
+
end
|
525
|
+
##########################################################################
|
526
|
+
# internal: Calculates the current width of the current table.
|
527
|
+
#-------------------------------------------------------------------------
|
528
|
+
def getTableWidth(table)
|
529
|
+
die("getTableWidth: table #{table} is not a REXML::Element") unless (table.class.to_s == "REXML::Element")
|
530
|
+
die("getTableWidth: current table does not contain table:table-column") unless(table.elements["table:table-column"])
|
531
|
+
tableName=table.attributes["table:name"]
|
532
|
+
die("getTableWidth: Could not extract tableName") if (tableName.empty?())
|
533
|
+
numColumnsOfTable=0
|
534
|
+
#--------------------------------------------------------------
|
535
|
+
# Vorhandene Spalteneintraege zaehlen
|
536
|
+
#--------------------------------------------------------------
|
537
|
+
table.elements.each("table:table-column"){ |tableColumn|
|
538
|
+
numColumnsOfTable+=1
|
539
|
+
numRepetitions=tableColumn.attributes["table:number-columns-repeated"]
|
540
|
+
if(numRepetitions)
|
541
|
+
numColumnsOfTable+=numRepetitions.to_i
|
542
|
+
end
|
543
|
+
}
|
544
|
+
tell("getTableWidth: width of '#{tableName}': #{numColumnsOfTable}")
|
545
|
+
return numColumnsOfTable
|
546
|
+
end
|
547
|
+
##########################################################################
|
548
|
+
# internal: Adapts the number of columns in the headers of all tables
|
549
|
+
# according to the right-most valid column. This routine is called when
|
550
|
+
# the spreadsheet is saved.
|
551
|
+
#------------------------------------------------------------------------
|
552
|
+
def padTable
|
553
|
+
#---------------------------------------------------------------
|
554
|
+
# Ggf. geaenderte Tabellenbreite setzen und
|
555
|
+
# alle Zeilen auf neue Tabellenbreite auffuellen
|
556
|
+
#---------------------------------------------------------------
|
557
|
+
@tables.each{ |tableName,tableHash|
|
558
|
+
table=tableHash[NODE]
|
559
|
+
width=tableHash[WIDTH]
|
560
|
+
numColumnsOfTable=getTableWidth(table)
|
561
|
+
if(tableHash[WIDTHEXCEEDED])
|
562
|
+
die("padTable: current table does not contain table:table-column") unless(table.elements["table:table-column"])
|
563
|
+
#--------------------------------------------------------------
|
564
|
+
# Differenz zu Sollbreite ermitteln und Wiederholungszahl des
|
565
|
+
# letzten Spalteneintrages aktualisieren/setzen
|
566
|
+
#--------------------------------------------------------------
|
567
|
+
lastTableColumn=table.elements["table:table-column[last()]"]
|
568
|
+
if(lastTableColumn.attributes["table:number-columns-repeated"])
|
569
|
+
numRepetitions=(lastTableColumn.attributes["table:number-columns-repeated"]).to_i+width-numColumnsOfTable
|
570
|
+
else
|
571
|
+
numRepetitions=width-numColumnsOfTable
|
572
|
+
end
|
573
|
+
lastTableColumn.attributes["table:number-columns-repeated"]=numRepetitions.to_s
|
574
|
+
tableHash[WIDTHEXCEEDED]=false
|
575
|
+
tell("padTable: adjusted columns: #{numColumnsOfTable} -> #{width}")
|
576
|
+
else
|
577
|
+
tell("padTable: equal: #{numColumnsOfTable} <-> #{width}")
|
578
|
+
end
|
579
|
+
}
|
580
|
+
end
|
581
|
+
##########################################################################
|
582
|
+
# internal: This routine pads the given row with newly created cells and/or
|
583
|
+
# adapts their repetition-attributes. It was formerly called by 'padTable' and is obsolete.
|
584
|
+
#----------------------------------------------------------------------
|
585
|
+
def padRow(row,width)
|
586
|
+
j=0
|
587
|
+
#-----------------------------------------------------
|
588
|
+
# Falls ueberhaupt Spaltenobjekte vorhanden sind
|
589
|
+
#-----------------------------------------------------
|
590
|
+
if(row.has_elements?())
|
591
|
+
#--------------------------
|
592
|
+
# Spalten zaehlen
|
593
|
+
#--------------------------
|
594
|
+
row.elements.each("table:table-cell"){ |cell|
|
595
|
+
j=j+1
|
596
|
+
#-------------------------------------------
|
597
|
+
# Spaltenwiederholungen addieren
|
598
|
+
#-------------------------------------------
|
599
|
+
repetition=cell.attributes["table:number-columns-repeated"]
|
600
|
+
if(repetition)
|
601
|
+
j=j+(repetition.to_i-1)
|
602
|
+
end
|
603
|
+
}
|
604
|
+
#-------------------------------
|
605
|
+
# Fuellmenge bestimmen
|
606
|
+
#-------------------------------
|
607
|
+
numPaddings=width-j
|
608
|
+
#------------------------------
|
609
|
+
# Fuellbedarf ?
|
610
|
+
#------------------------------
|
611
|
+
if(numPaddings > 0)
|
612
|
+
#-------------------------------
|
613
|
+
# Letztes Element der Zeile holen
|
614
|
+
#-------------------------------
|
615
|
+
cell=row.elements["table:table-cell[last()]"]
|
616
|
+
#-------------------------------
|
617
|
+
# Leerzelle ?
|
618
|
+
#-------------------------------
|
619
|
+
if(! cell.elements["text:p"])
|
620
|
+
#-----------------------------
|
621
|
+
# Leerzelle mit Wiederholung ?
|
622
|
+
#-----------------------------
|
623
|
+
if(repetition=cell.attributes["table:number-columns-repeated"])
|
624
|
+
newRepetition=(repetition.to_i+numPaddings)
|
625
|
+
#----------------------------
|
626
|
+
# nein, einzelne Leerzelle -> Wiederholungszahl setzen
|
627
|
+
#----------------------------
|
628
|
+
else
|
629
|
+
newRepetition=numPaddings
|
630
|
+
end
|
631
|
+
setRepetition(cell,CELL,newRepetition)
|
632
|
+
#-------------------------------
|
633
|
+
# keine Leerzelle -> Leerzelle(n) anhaengen
|
634
|
+
#-------------------------------
|
635
|
+
else
|
636
|
+
cell.next_sibling=createElement(CELL,numPaddings)
|
637
|
+
end
|
638
|
+
#------------------------------------------------------
|
639
|
+
# bei negativem Wert -> Fehler
|
640
|
+
#------------------------------------------------------
|
641
|
+
elsif(numPaddings < 0)
|
642
|
+
die("padRow: cellWidth #{j} exceeds width of table #{width}")
|
643
|
+
end
|
644
|
+
#--------------------------------------------------------
|
645
|
+
# Falls keine Spaltenobjekte vorhanden sind
|
646
|
+
#--------------------------------------------------------
|
647
|
+
else
|
648
|
+
row.add_element(createElement(CELL,width))
|
649
|
+
end
|
650
|
+
end
|
651
|
+
##########################################################################
|
652
|
+
# internal: Verifies the format of a given time-string and converts it into
|
653
|
+
# a proper internal representation.
|
654
|
+
#-------------------------------------------------------------------------
|
655
|
+
def time2TimeVal(text)
|
656
|
+
#----------------------------------------
|
657
|
+
# Format- und Range-Pruefung
|
658
|
+
#----------------------------------------
|
659
|
+
#------------------------
|
660
|
+
# Format
|
661
|
+
#------------------------
|
662
|
+
unless(text.match(/^\d{2}:\d{2}$/))
|
663
|
+
die("time2TimeVal: wrong time-format '#{text}' -> expected: 'hh:mm'")
|
664
|
+
end
|
665
|
+
#------------------------
|
666
|
+
# Range
|
667
|
+
#------------------------
|
668
|
+
unless(text.match(/^[0-1][0-9]:[0-5][0-9]$/) || text.match(/^[2][0-3]:[0-5][0-9]$/))
|
669
|
+
die("time2TimeVal: time '#{text}' not in valid range")
|
670
|
+
end
|
671
|
+
time=text.match(/(\d{2}):(\d{2})/)
|
672
|
+
hour=time[1]
|
673
|
+
minute=time[2]
|
674
|
+
internalValue="PT"+hour+"H"+minute+"M00S"
|
675
|
+
tell("time2TimeVal: mapping: #{text} -> #{internalValue}")
|
676
|
+
return internalValue
|
677
|
+
exit
|
678
|
+
end
|
679
|
+
|
680
|
+
##########################################################################
|
681
|
+
# internal: Converts a given percentage-string to English-format ('.' instead
|
682
|
+
# of ',' as decimal separator, divides by 100 and returns a string with this
|
683
|
+
# format. For instance: 3,49 becomes 0.0349.
|
684
|
+
#----------------------------------------------------------------------
|
685
|
+
def percent2PercentVal(text)
|
686
|
+
return (text.sub(/,/,".").to_f/100.0).to_s
|
687
|
+
end
|
688
|
+
##########################################################################
|
689
|
+
# internal: Converts a date-string of the form '01.01.2010' into the internal
|
690
|
+
# representation '2010-01-01'.
|
691
|
+
#----------------------------------------------------------------------
|
692
|
+
def date2DateVal(text)
|
693
|
+
if(! text.match(/^\d{2}\.\d{2}\.\d{4}$/))
|
694
|
+
die("date2DateVal: Date #{text} does not comply with format dd.mm.yyyy")
|
695
|
+
else
|
696
|
+
text.match(/(^\d{2})\.(\d{2})\.(\d{4})$/)
|
697
|
+
return $3+"-"+$2+"-"+$1
|
698
|
+
end
|
699
|
+
end
|
700
|
+
##########################################################################
|
701
|
+
# Returns the content and type of the cell at the index in the given row
|
702
|
+
# as strings. Row is a REXML::Element.
|
703
|
+
# If the cell does not exist, nil is returned for text and type.
|
704
|
+
# Type is one of the following office:value-types
|
705
|
+
# * string, float, currency, time, date, percent, formula
|
706
|
+
# The content of a formula is it's last calculated result or 0 in case of a
|
707
|
+
# newly created cell ! The text is internally cleaned from currency-symbols and
|
708
|
+
# converted to a valid (English) float representation (but remains a string)
|
709
|
+
# in case of type "currency" or "float".
|
710
|
+
# amount=0.0
|
711
|
+
# 5.upto(8){ |i|
|
712
|
+
# row=mySheet.getRow(i)
|
713
|
+
# text,type=mySheet.readCellFromRow(row,i)
|
714
|
+
# mySheet.writeCellFromRow(row,9,type,(-1.0*text.to_f).to_s)
|
715
|
+
# if(type == "currency")
|
716
|
+
# amount+=text.to_f
|
717
|
+
# end
|
718
|
+
# }
|
719
|
+
# puts("Earned #{amount} bucks")
|
720
|
+
#---------------------------------------------------------------
|
721
|
+
def readCellFromRow(row,colInd)
|
722
|
+
j=0
|
723
|
+
#------------------------------------------------------------------
|
724
|
+
# Fuer alle Spalten
|
725
|
+
#------------------------------------------------------------------
|
726
|
+
row.elements.each("table:table-cell"){ |cell|
|
727
|
+
j=j+1
|
728
|
+
#-------------------------------------------
|
729
|
+
# Spaltenwiederholungen addieren
|
730
|
+
#-------------------------------------------
|
731
|
+
repetition=cell.attributes["table:number-columns-repeated"]
|
732
|
+
if(repetition)
|
733
|
+
j=j+(repetition.to_i-1)
|
734
|
+
end
|
735
|
+
#-------------------------------------------
|
736
|
+
# Falls Spaltenindex schon uebersprungen
|
737
|
+
#-------------------------------------------
|
738
|
+
if(j > colInd)
|
739
|
+
return nil, nil
|
740
|
+
#-------------------------------------------
|
741
|
+
# Falls Spaltenindex erreicht
|
742
|
+
#-------------------------------------------
|
743
|
+
elsif(j == colInd)
|
744
|
+
#-------------------------------------------
|
745
|
+
# Zelltext und Datentyp zurueckgeben
|
746
|
+
# ggf. Waehrungssymbol abschneiden
|
747
|
+
#-------------------------------------------
|
748
|
+
textElement=cell.elements["text:p"]
|
749
|
+
if(! textElement)
|
750
|
+
return nil,nil
|
751
|
+
else
|
752
|
+
text=textElement.text
|
753
|
+
if(! text)
|
754
|
+
die("readCellFromRow: Could not extract text at coordinate #{rowInd},#{colInd}")
|
755
|
+
end
|
756
|
+
type=cell.attributes["office:value-type"]
|
757
|
+
if(! type)
|
758
|
+
die("readCellFromRow: Could not extract data type at coordinate #{rowInd},#{colInd}")
|
759
|
+
end
|
760
|
+
text=normalizeText(text,type)
|
761
|
+
return text,type
|
762
|
+
end
|
763
|
+
end
|
764
|
+
}
|
765
|
+
#----------------------------------------------
|
766
|
+
# ausserhalb bisheriger Spalten
|
767
|
+
#----------------------------------------------
|
768
|
+
return nil,nil
|
769
|
+
end
|
770
|
+
##########################################################################
|
771
|
+
# Returns the content and type of the cell at the given indices
|
772
|
+
# as strings.
|
773
|
+
# If the cell does not exist, nil is returned for text and type.
|
774
|
+
# Type is one of the following office:value-types
|
775
|
+
# * string, float, currency, time, date, percent, formula
|
776
|
+
# The content of a formula is it's last calculated result or 0 in case of a
|
777
|
+
# newly created cell. See annotations at 'readCellFromRow'.
|
778
|
+
# 1.upto(10){ |i|
|
779
|
+
# text,type=readCell(i,i)
|
780
|
+
# writeCell(i,10-i,type,text)
|
781
|
+
# }
|
782
|
+
#-------------------------------------------------------------------------
|
783
|
+
def readCell(rowInd,colInd)
|
784
|
+
#------------------------------------------------------------------
|
785
|
+
# Fuer alle Zeilen
|
786
|
+
#------------------------------------------------------------------
|
787
|
+
i=0
|
788
|
+
j=0
|
789
|
+
#------------------------------------------------------------------
|
790
|
+
# Zelle mit Indizes suchen
|
791
|
+
#------------------------------------------------------------------
|
792
|
+
currentTable=@tables[@currentTableName][NODE]
|
793
|
+
currentTable.elements.each("table:table-row"){ |row|
|
794
|
+
i=i+1
|
795
|
+
j=0
|
796
|
+
repetition=row.attributes["table:number-rows-repeated"]
|
797
|
+
#-------------------------------------------
|
798
|
+
# Zeilenwiederholungen addieren
|
799
|
+
#-------------------------------------------
|
800
|
+
if(repetition)
|
801
|
+
i=i+(repetition.to_i-1)
|
802
|
+
end
|
803
|
+
#-------------------------------------------
|
804
|
+
# Falls Zeilenindex schon uebersprungen
|
805
|
+
#-------------------------------------------
|
806
|
+
if(i > rowInd)
|
807
|
+
return nil, nil
|
808
|
+
#-------------------------------------------
|
809
|
+
# Falls Zeilenindex erreicht
|
810
|
+
#-------------------------------------------
|
811
|
+
elsif(i == rowInd)
|
812
|
+
return readCellFromRow(row,colInd)
|
813
|
+
end
|
814
|
+
}
|
815
|
+
#--------------------------------------------
|
816
|
+
# ausserhalb bisheriger Zeilen
|
817
|
+
#--------------------------------------------
|
818
|
+
return nil,nil
|
819
|
+
end
|
820
|
+
##########################################################################
|
821
|
+
# internal: Composes everything necessary for writing all the contents of
|
822
|
+
# the resulting *.ods zip-file upon call of 'save' or 'saveAs'.
|
823
|
+
# Saves and zips all contents.
|
824
|
+
#---------------------------------------
|
825
|
+
def finalize(zipfile)
|
826
|
+
#------------------------
|
827
|
+
# meta.xml
|
828
|
+
#------------------------
|
829
|
+
#-------------------------------------
|
830
|
+
# Autor (ich :-)
|
831
|
+
#-------------------------------------
|
832
|
+
initialCreator=@officeMeta.elements["meta:initial-creator"]
|
833
|
+
die("finalize: Could not extract meta:initial-creator") unless (initialCreator)
|
834
|
+
initialCreator.text="Dr. Heinz Breinlinger"
|
835
|
+
tell("finalize: automator: Dr. Heinz Breinlinger")
|
836
|
+
#-------------------------------------
|
837
|
+
# Datum/Zeit
|
838
|
+
#-------------------------------------
|
839
|
+
metaCreationDate=@officeMeta.elements["meta:creation-date"]
|
840
|
+
die("finalize: could not extract meta:creation-date") unless (metaCreationDate)
|
841
|
+
now=Time.new()
|
842
|
+
time=now.year.to_s+"-"+now.month.to_s+"-"+now.day.to_s+"T"+now.hour.to_s+":"+now.min.to_s+":"+now.sec.to_s
|
843
|
+
metaCreationDate.text=time
|
844
|
+
tell("finalize: time: #{time}")
|
845
|
+
#-------------------------------------
|
846
|
+
# Anzahl der Tabellen
|
847
|
+
#-------------------------------------
|
848
|
+
metaDocumentStatistic=@officeMeta.elements["meta:document-statistic"]
|
849
|
+
die("finalize: Could not extract meta:document-statistic") unless (metaDocumentStatistic)
|
850
|
+
metaDocumentStatistic.attributes["meta:table-count"]=@numTables.to_s
|
851
|
+
tell("finalize: num of tables: #{@numTables}")
|
852
|
+
#-------------------------------------
|
853
|
+
tell("finalize: writing meta.xml ...")
|
854
|
+
zipfile.file.open("meta.xml","w") { |outfile|
|
855
|
+
outfile.puts @metaText.to_s
|
856
|
+
}
|
857
|
+
#------------------------
|
858
|
+
# manifest.xml
|
859
|
+
#------------------------
|
860
|
+
tell("finalize: writing manifest.xml ...")
|
861
|
+
zipfile.file.open("META-INF/manifest.xml","w") { |outfile|
|
862
|
+
outfile.puts @manifestText.to_s
|
863
|
+
}
|
864
|
+
#------------------------
|
865
|
+
# mimetype
|
866
|
+
# Cave: Darf KEIN Newline am Ende beinhalten -> print anstelle puts !!!
|
867
|
+
#------------------------
|
868
|
+
tell("finalize: writing mimetype ...")
|
869
|
+
zipfile.file.open("mimetype","w") { |outfile|
|
870
|
+
outfile.print("application/vnd.oasis.opendocument.spreadsheet")
|
871
|
+
}
|
872
|
+
#------------------------
|
873
|
+
# settings.xml
|
874
|
+
#------------------------
|
875
|
+
tell("finalize: writing settings.xml ...")
|
876
|
+
zipfile.file.open("settings.xml","w") { |outfile|
|
877
|
+
outfile.puts @settingsText.to_s
|
878
|
+
}
|
879
|
+
#------------------------
|
880
|
+
# styles.xml
|
881
|
+
#------------------------
|
882
|
+
tell("finalize: writing styles.xml ...")
|
883
|
+
zipfile.file.open("styles.xml","w") { |outfile|
|
884
|
+
outfile.puts @stylesText.to_s
|
885
|
+
}
|
886
|
+
#------------------------
|
887
|
+
# content.xml
|
888
|
+
#------------------------
|
889
|
+
padTable()
|
890
|
+
tell("finalize: writing content.xml ...")
|
891
|
+
zipfile.file.open("content.xml","w") { |outfile|
|
892
|
+
outfile.puts @contentText.to_s
|
893
|
+
}
|
894
|
+
end
|
895
|
+
##########################################################################
|
896
|
+
# internal: Called by constructor upon creation of Open Document-object.
|
897
|
+
# Reads given zip-archive. Parses XML-files in archive. Initializes
|
898
|
+
# internal variables according to XML-trees. Calculates initial width of
|
899
|
+
# all tables and creates default-styles and default-data-styles for all
|
900
|
+
# data-types.
|
901
|
+
#-------------------------------------------------------------------
|
902
|
+
def init(zipfile)
|
903
|
+
#-------------------------------------------------------
|
904
|
+
# meta.xml
|
905
|
+
#-------------------------------------------------------
|
906
|
+
tell("init: parsing meta.xml ...")
|
907
|
+
@metaText=REXML::Document.new zipfile.file.read("meta.xml")
|
908
|
+
@officeMeta=@metaText.elements["/office:document-meta/office:meta"]
|
909
|
+
die("init: Could not extract office:document-meta") unless (@officeMeta)
|
910
|
+
#-------------------------------------------------------
|
911
|
+
# manifest.xml
|
912
|
+
#-------------------------------------------------------
|
913
|
+
tell("init: parsing manifest.xml ...")
|
914
|
+
@manifestText=REXML::Document.new zipfile.file.read("META-INF/manifest.xml")
|
915
|
+
@manifestRoot=@manifestText.elements["/manifest:manifest"]
|
916
|
+
die("init: Could not extract manifest:manifest") unless (@manifestRoot)
|
917
|
+
#-------------------------------------------------------
|
918
|
+
# settings.xml
|
919
|
+
#-------------------------------------------------------
|
920
|
+
tell("init: parsing settings.xml ...")
|
921
|
+
@settingsText=REXML::Document.new zipfile.file.read("settings.xml")
|
922
|
+
@officeSettings=@settingsText.elements["/office:document-settings/office:settings"]
|
923
|
+
die("init: Could not extract office:-settings") unless (@officeSettings)
|
924
|
+
#-------------------------------------------------------
|
925
|
+
# styles.xml
|
926
|
+
#-------------------------------------------------------
|
927
|
+
tell("init: parsing styles.xml ...")
|
928
|
+
@stylesText=REXML::Document.new zipfile.file.read("styles.xml")
|
929
|
+
@officeStyles=@stylesText.elements["/office:document-styles/office:styles"]
|
930
|
+
die("init: Could not extract office:document-styles") unless (@officeStyles)
|
931
|
+
#--------------------------------------------------------------
|
932
|
+
# content.xml
|
933
|
+
#--------------------------------------------------------------
|
934
|
+
tell("init: parsing content.xml ...")
|
935
|
+
@contentText=REXML::Document.new zipfile.file.read("content.xml")
|
936
|
+
@autoStyles=@contentText.elements["/office:document-content/office:automatic-styles"]
|
937
|
+
die("init: Could not extract office:automatic-styles") unless (@autoStyles)
|
938
|
+
#--------------------------------------------------------
|
939
|
+
# Tabellendaten ermitteln und Initialwerte setzen
|
940
|
+
#--------------------------------------------------------
|
941
|
+
initHousekeeping()
|
942
|
+
#------------------------------------------------------------
|
943
|
+
# Default-Styles und Default-Data-Styles anlegen
|
944
|
+
#------------------------------------------------------------
|
945
|
+
writeDefaultStyles
|
946
|
+
end
|
947
|
+
##########################################################################
|
948
|
+
# internal: Converts the given string (of type 'float' or 'currency') to
|
949
|
+
# the internal arithmetic represenation.
|
950
|
+
# This changes the thousands-separator, the decimal-separator and prunes
|
951
|
+
# the currency-symbol
|
952
|
+
#----------------------------------------------------------
|
953
|
+
def normalizeText(text,type)
|
954
|
+
if((type == "currency") || (type == "float"))
|
955
|
+
newText=String.new(text)
|
956
|
+
#--------------------------------------
|
957
|
+
# Tausendertrennzeichen beseitigen
|
958
|
+
#--------------------------------------
|
959
|
+
newText.sub!(/\./,"")
|
960
|
+
#--------------------------------------
|
961
|
+
# Dezimaltrenner umwandeln
|
962
|
+
#--------------------------------------
|
963
|
+
newText.sub!(/,/,".")
|
964
|
+
if(type == "currency")
|
965
|
+
#--------------------------------------
|
966
|
+
# Waehrungssymbol am Ende abschneiden
|
967
|
+
#--------------------------------------
|
968
|
+
newText.sub!(/\s*\S+$/,"")
|
969
|
+
end
|
970
|
+
end
|
971
|
+
return newText
|
972
|
+
end
|
973
|
+
##########################################################################
|
974
|
+
# Writes the given text-string to given cell and sets style of
|
975
|
+
# cell to corresponding type. Keep in mind: All values of tables are
|
976
|
+
# passed and retrieved as strings !
|
977
|
+
# mySheet.writeText(getCell(17,39),"currency","14,37")
|
978
|
+
# The example can of course be simplified by
|
979
|
+
# mySheet.writeCell(17,39,"currency","14,37")
|
980
|
+
#-----------------------------------------------------------
|
981
|
+
def writeText(cell,type,text)
|
982
|
+
#------------------------------------------
|
983
|
+
# Zunaechst ggf. stoerende Attribute löschen
|
984
|
+
#------------------------------------------
|
985
|
+
cell.attributes.each{ |attribute,value|
|
986
|
+
cell.attributes.delete(attribute)
|
987
|
+
}
|
988
|
+
#-------------------------------------------
|
989
|
+
# Typabhaengig diverse Attribute der Zelle setzen
|
990
|
+
#-------------------------------------------
|
991
|
+
# String
|
992
|
+
#-------------------------------------------
|
993
|
+
if(type == "string")
|
994
|
+
cell.attributes["office:value-type"]="string"
|
995
|
+
cell.attributes["table:style-name"]=@stringStyle
|
996
|
+
#-------------------------------------------
|
997
|
+
# Float
|
998
|
+
#-------------------------------------------
|
999
|
+
elsif(type == "float")
|
1000
|
+
cell.attributes["office:value-type"]="float"
|
1001
|
+
#-----------------------------------------------------
|
1002
|
+
# Dezimaltrenner von "," in "." aendern
|
1003
|
+
#-----------------------------------------------------
|
1004
|
+
internalText=text.sub(/,/,".")
|
1005
|
+
cell.attributes["office:value"]=internalText
|
1006
|
+
cell.attributes["table:style-name"]=@floatStyle
|
1007
|
+
#-------------------------------------------
|
1008
|
+
# Formula
|
1009
|
+
# Cave: Zahlformat 1,25 muss geaendert werden in 1.25
|
1010
|
+
# In der reinen Textdarstellung der Zellenformel verwendet
|
1011
|
+
# OpenOffice das laenderspezifische Trennzeichen; im Attributwert
|
1012
|
+
# der Formel muss jedoch das englische Format mit '.' stehen !
|
1013
|
+
# Waehrend dies bei interaktiver Eingabe der Formel transparent
|
1014
|
+
# gewandelt (jedoch stets mit laenderspezifischem Trennzeichen angezeigt) wird,
|
1015
|
+
# muss hier explizit "Hand angelegt" werden. Der Unterschied ist dann lediglich
|
1016
|
+
# in der XML-Darstellung (des Attributwertes) zu sehen, NICHT in der interaktiven
|
1017
|
+
# Anzeige unter OpenOffice.
|
1018
|
+
# Als Fuellwert wird stehts "0" gesetzt; beim Oeffnen der Datei mit OpenOffice
|
1019
|
+
# wird dann der richtige Wert errechnet und geschrieben.
|
1020
|
+
#-------------------------------------------
|
1021
|
+
elsif(type.match(/^formula/))
|
1022
|
+
#---------------------------------------------
|
1023
|
+
# Formel fuer interne Darstellung aufbereiten
|
1024
|
+
#---------------------------------------------
|
1025
|
+
cell.attributes["table:formula"]=internalizeFormula(text)
|
1026
|
+
#---------------------------------------------
|
1027
|
+
# Zellformatierung bestimmen
|
1028
|
+
#---------------------------------------------
|
1029
|
+
case type
|
1030
|
+
when "formula","formula:float"
|
1031
|
+
cell.attributes["office:value-type"]="float"
|
1032
|
+
cell.attributes["office:value"]=0
|
1033
|
+
cell.attributes["table:style-name"]=@floatStyle
|
1034
|
+
when "formula:time"
|
1035
|
+
cell.attributes["office:value-type"]="time"
|
1036
|
+
cell.attributes["office:time-value"]="PT00H00M00S"
|
1037
|
+
cell.attributes["table:style-name"]=@timeStyle
|
1038
|
+
# cell.attributes["table:style-name"]=""
|
1039
|
+
when "formula:date"
|
1040
|
+
cell.attributes["office:value-type"]="date"
|
1041
|
+
cell.attributes["office:date-value"]="0"
|
1042
|
+
cell.attributes["table:style-name"]=@dateStyle
|
1043
|
+
when "formula:currency"
|
1044
|
+
cell.attributes["office:value-type"]="currency"
|
1045
|
+
#-----------------------------------------------------
|
1046
|
+
# Dezimaltrenner von "," in "." aendern
|
1047
|
+
#-----------------------------------------------------
|
1048
|
+
internalText="0.0"
|
1049
|
+
cell.attributes["office:value"]=internalText
|
1050
|
+
cell.attributes["office:currency"]=@currencySymbolInternal
|
1051
|
+
cell.attributes["table:style-name"]=@currencyStyle
|
1052
|
+
else die("writeText: invalid type of formula #{type}")
|
1053
|
+
end
|
1054
|
+
text="0"
|
1055
|
+
#-------------------------------------------
|
1056
|
+
# Percent
|
1057
|
+
#-------------------------------------------
|
1058
|
+
elsif(type == "percent")
|
1059
|
+
cell.attributes["office:value-type"]="percentage"
|
1060
|
+
cell.attributes["office:value"]=percent2PercentVal(text)
|
1061
|
+
cell.attributes["table:style-name"]=@percentStyle
|
1062
|
+
text=text+" %"
|
1063
|
+
#-------------------------------------------
|
1064
|
+
# Currency
|
1065
|
+
#-------------------------------------------
|
1066
|
+
elsif(type == "currency")
|
1067
|
+
cell.attributes["office:value-type"]="currency"
|
1068
|
+
#-----------------------------------------------------
|
1069
|
+
# Dezimaltrenner von "," in "." aendern und
|
1070
|
+
# Waehrungs-Symbol hintanstellen
|
1071
|
+
#-----------------------------------------------------
|
1072
|
+
internalText=text.sub(/,/,".")
|
1073
|
+
text=text+" "+@currencySymbol
|
1074
|
+
cell.attributes["office:value"]=internalText
|
1075
|
+
cell.attributes["office:currency"]=@currencySymbolInternal
|
1076
|
+
cell.attributes["table:style-name"]=@currencyStyle
|
1077
|
+
#-------------------------------------------
|
1078
|
+
# Date
|
1079
|
+
#-------------------------------------------
|
1080
|
+
elsif(type == "date")
|
1081
|
+
cell.attributes["office:value-type"]="date"
|
1082
|
+
cell.attributes["table:style-name"]=@dateStyle
|
1083
|
+
cell.attributes["office:date-value"]=date2DateVal(text)
|
1084
|
+
#-------------------------------------------
|
1085
|
+
# Time (im Format 13:37)
|
1086
|
+
#-------------------------------------------
|
1087
|
+
elsif(type == "time")
|
1088
|
+
cell.attributes["office:value-type"]="time"
|
1089
|
+
cell.attributes["table:style-name"]=@timeStyle
|
1090
|
+
cell.attributes["office:time-value"]=time2TimeVal(text)
|
1091
|
+
else
|
1092
|
+
puts("Wrong type #{type}: Doing nothing")
|
1093
|
+
end
|
1094
|
+
#-------------------------------------------
|
1095
|
+
# Text setzen
|
1096
|
+
#-------------------------------------------
|
1097
|
+
# Textelement bereits vorhanden ?
|
1098
|
+
#-------------------------------------------
|
1099
|
+
if(cell.elements["text:p"])
|
1100
|
+
cell.elements["text:p"].text=text
|
1101
|
+
#-------------------------------------------
|
1102
|
+
# nicht vorhanden (Leerzelle) -> neu anlegen
|
1103
|
+
#-------------------------------------------
|
1104
|
+
else
|
1105
|
+
newElement=cell.add_element("text:p")
|
1106
|
+
newElement.text=text
|
1107
|
+
end
|
1108
|
+
end
|
1109
|
+
##########################################################################
|
1110
|
+
# internal: Maps a convenience color-value into a hex-value
|
1111
|
+
# example: "turquoise" => "#008080"
|
1112
|
+
#-------------------------------------------------------------------------
|
1113
|
+
def getColor(color)
|
1114
|
+
hexColor=@palette[color]
|
1115
|
+
die("getColor: color \'#{color}\' is not known in existing palette") unless (hexColor)
|
1116
|
+
tell("getColor: Mapping #{color} to #{hexColor}")
|
1117
|
+
return hexColor
|
1118
|
+
end
|
1119
|
+
##########################################################################
|
1120
|
+
# internal: Norms and maps a known set of attributes of the given style-Hash to
|
1121
|
+
# valid long forms of OASIS-style-attributes and replaces color-values with
|
1122
|
+
# their hex-representations.
|
1123
|
+
# Unknown hash-keys are copied as is.
|
1124
|
+
#-------------------------------------------------------------------------
|
1125
|
+
def normStyleHash(inHash)
|
1126
|
+
outHash=Hash.new()
|
1127
|
+
inHash.each{ |key,value|
|
1128
|
+
#---------------------------------------------------------------------
|
1129
|
+
# Ersetzung von Farbwerten
|
1130
|
+
#---------------------------------------------------------------------
|
1131
|
+
if((key == "color") || (key == "fo:color") || (key == "background-color") || (key == "fo:background-color"))
|
1132
|
+
#-------------------------------------------------------
|
1133
|
+
# Falls Farbwert nicht hexadezimal angegeben (i.e. '#' zu Beginn),
|
1134
|
+
# => in Farbpalette nachschlagen, ggf. Fehlermeldung
|
1135
|
+
#-------------------------------------------------------
|
1136
|
+
if(!value.match(/^#/)) then value=getColor(value) end
|
1137
|
+
#--------------------------------------------------------
|
1138
|
+
# dito bei Farben fuer den Rand
|
1139
|
+
#--------------------------------------------------------
|
1140
|
+
elsif(key.match(/^(fo:)?border/))
|
1141
|
+
die("normStyleHash: wrong format for border '#{value}'") unless (value.match(/^\S+\s+\S+\s+\S+$/))
|
1142
|
+
#---------------------------------------------
|
1143
|
+
# Cave: Matcht auf Audruecke der Art
|
1144
|
+
# "0.1cm solid red7" und berueksichtigt auch, dass
|
1145
|
+
# zwischen 0.1 und cm Leerzeichen sein koennen, da nur
|
1146
|
+
# auf die letzten 3 Ausdrucke gematcht wird !
|
1147
|
+
#---------------------------------------------
|
1148
|
+
match=value.match(/\S+\s\S+\s(\S+)\s*$/)
|
1149
|
+
color=match[1]
|
1150
|
+
#-------------------------------------------------
|
1151
|
+
# Falls Farbwert nicht hexadezimal -> Ersetzen
|
1152
|
+
#-------------------------------------------------
|
1153
|
+
unless(color.match(/#[a-fA-F0-9]{6}/))
|
1154
|
+
hexColor=getColor(color)
|
1155
|
+
value.sub!(color,hexColor)
|
1156
|
+
end
|
1157
|
+
end
|
1158
|
+
case key
|
1159
|
+
when "name" then outHash["style:name"] = value
|
1160
|
+
when "family" then outHash["style:family"] = value
|
1161
|
+
when "parent-style-name" then outHash["style:parent-style-name"] = value
|
1162
|
+
when "background-color" then outHash["fo:background-color"] = value
|
1163
|
+
when "text-align-source" then outHash["style:text-align-source"] = value
|
1164
|
+
when "text-align" then outHash["fo:text-align"] = value
|
1165
|
+
when "margin-left" then outHash["fo:margin-left"] = value
|
1166
|
+
when "color" then outHash["fo:color"] = value
|
1167
|
+
when "border" then outHash["fo:border"] = value
|
1168
|
+
when "border-bottom" then outHash["fo:border-bottom"] = value
|
1169
|
+
when "border-top" then outHash["fo:border-top"] = value
|
1170
|
+
when "border-left" then outHash["fo:border-left"] = value
|
1171
|
+
when "border-right" then outHash["fo:border-right"] = value
|
1172
|
+
when "font-style" then outHash["fo:font-style"] = value
|
1173
|
+
when "font-weight" then outHash["fo:font-weight"] = value
|
1174
|
+
when "data-style-name" then outHash["style:data-style-name"] = value
|
1175
|
+
#-------------------------------------
|
1176
|
+
# andernfalls Key und Value kopieren
|
1177
|
+
#-------------------------------------
|
1178
|
+
else outHash[key]=value
|
1179
|
+
end
|
1180
|
+
}
|
1181
|
+
return outHash
|
1182
|
+
end
|
1183
|
+
##########################################################################
|
1184
|
+
# internal: Retrieves and returns the node of the style with the given name from content.xml or
|
1185
|
+
# styles.xml along with the indicator of the corresponding file.
|
1186
|
+
#-------------------------------------------------------------------------
|
1187
|
+
def getStyle(styleName)
|
1188
|
+
style=@autoStyles.elements["*[@style:name = '#{styleName}']"]
|
1189
|
+
if(style)
|
1190
|
+
file=CONTENT
|
1191
|
+
else
|
1192
|
+
style=@officeStyles.elements["*[@style:name = '#{styleName}']"]
|
1193
|
+
die("getStyle: Could not find style \'#{styleName}\' in content.xml or styles.xml") unless (style)
|
1194
|
+
file=STYLES
|
1195
|
+
end
|
1196
|
+
tell("getStyle: found style '#{styleName}'")
|
1197
|
+
return file,style
|
1198
|
+
end
|
1199
|
+
##########################################################################
|
1200
|
+
# Merges style-attributes of given attribute-hash with current style
|
1201
|
+
# of given cell. Checks, whether the resulting style already exists in the
|
1202
|
+
# archive of created styles or creates and archives a new style. Applies the
|
1203
|
+
# found or created style to cell. Cell is a REXML::Element.
|
1204
|
+
# mySheet.setAttributes(cell,{ "border-right" => "0.05cm solid magenta4",
|
1205
|
+
# "border-bottom" => "0.03cm solid lightgreen",
|
1206
|
+
# "border-top" => "0.08cm solid salmon",
|
1207
|
+
# "font-style" => "italic",
|
1208
|
+
# "font-weight" => "bold"})
|
1209
|
+
# mySheet.setAttributes(cell,{ "border" => "0.01cm solid turquoise", # turquoise frame
|
1210
|
+
# "text-align" => "center", # center alignment
|
1211
|
+
# "background-color" => "yellow2", # background-color
|
1212
|
+
# "color" => "blue"}) # font-color
|
1213
|
+
# 1.upto(7){ |row|
|
1214
|
+
# cell=mySheet.getCell(row,5)
|
1215
|
+
# mySheet.setAttributes(cell,{ "border-right" => "0.07cm solid green6" })
|
1216
|
+
# }
|
1217
|
+
#-------------------------------------------------------------------------
|
1218
|
+
def setAttributes(cell,attributes)
|
1219
|
+
die("setAttributes: cell #{cell} is not a REXML::Element") unless (cell.class.to_s == "REXML::Element")
|
1220
|
+
die("setAttributes: hash #{attributes} is not a hash") unless (attributes.class.to_s == "Hash")
|
1221
|
+
#----------------------------------------------------------------------
|
1222
|
+
# Flag, ob neue Attribute und deren Auspraegungen bereits im aktuellen
|
1223
|
+
# style vorhanden sind
|
1224
|
+
#----------------------------------------------------------------------
|
1225
|
+
containsMatchingAttributes=TRUE
|
1226
|
+
#-----------------------------------------------------------------------
|
1227
|
+
# Attribut-Hash, welcher "convenience"-Werte enthalten kann (und wird ;-)
|
1228
|
+
# zunaechst normieren
|
1229
|
+
#-----------------------------------------------------------------------
|
1230
|
+
attributes=normStyleHash(attributes)
|
1231
|
+
die("setAttributes: attribute style:name not allowed in attribute-list as automatically generated") if (attributes.has_key?("style:name"))
|
1232
|
+
#------------------------------------------------------------------
|
1233
|
+
# Falls Zelle bereits style zugewiesen hat
|
1234
|
+
#------------------------------------------------------------------
|
1235
|
+
currentStyleName=cell.attributes["table:style-name"]
|
1236
|
+
if(currentStyleName)
|
1237
|
+
#---------------------------------------------------------------
|
1238
|
+
# style suchen (lassen)
|
1239
|
+
#---------------------------------------------------------------
|
1240
|
+
file,currentStyle=getStyle(currentStyleName)
|
1241
|
+
#-----------------------------------------------------------------------
|
1242
|
+
# Pruefung, ob oben gefundener style die neuen Attribute und deren Werte
|
1243
|
+
# bereits enthaelt.
|
1244
|
+
# Falls auch nur ein Attribut nicht oder nicht mit dem richtigen Wert
|
1245
|
+
# vorhanden ist, muss ein neuer style erstellt werden.
|
1246
|
+
# Grundannahme: Ein Open-Document-Style-Attribut kann per se immer nur in einem bestimmten Typ
|
1247
|
+
# Knoten vorkommen und muss daher nicht naeher qualifiziert werden !
|
1248
|
+
#-----------------------------------------------------------------------
|
1249
|
+
attributes.each{ |attribute,value|
|
1250
|
+
currentValue=currentStyle.attributes[attribute]
|
1251
|
+
#-------------------------------------------------
|
1252
|
+
# Attribut in Context-Node nicht gefunden ?
|
1253
|
+
#-------------------------------------------------
|
1254
|
+
if(! currentValue) # nilClass
|
1255
|
+
tell("setAttributes: #{currentStyleName}: #{attribute} not in Top-Node")
|
1256
|
+
#-----------------------------------------------------------
|
1257
|
+
# Attribut mit passendem Wert dann in Kind-Element vorhanden ?
|
1258
|
+
#-----------------------------------------------------------
|
1259
|
+
if(currentStyle.elements["*[@#{attribute} = '#{value}']"])
|
1260
|
+
tell("setAttributes: #{currentStyleName}: #{attribute}/#{value} matching in Sub-Node")
|
1261
|
+
#-----------------------------------------------------------
|
1262
|
+
# andernfalls Komplettabbruch der Pruefschleife aller Attribute und Flag setzen
|
1263
|
+
# => neuer style muss erzeugt werden
|
1264
|
+
#-----------------------------------------------------------
|
1265
|
+
else
|
1266
|
+
tell("setAttributes: #{currentStyleName}: #{attribute}/#{value} not matching in Sub-Node")
|
1267
|
+
containsMatchingAttributes=FALSE
|
1268
|
+
break
|
1269
|
+
end
|
1270
|
+
#--------------------------------------------------
|
1271
|
+
# Attribut in Context-Node gefunden
|
1272
|
+
#--------------------------------------------------
|
1273
|
+
else
|
1274
|
+
#--------------------------------------------------
|
1275
|
+
# Passt der Wert des gefundenen Attributes bereits ?
|
1276
|
+
#--------------------------------------------------
|
1277
|
+
if (currentValue == value)
|
1278
|
+
tell("setAttributes: #{currentStyleName}: #{attribute}/#{value} matching in Top-Node")
|
1279
|
+
#-------------------------------------------------
|
1280
|
+
# bei unpassendem Wert Flag setzen
|
1281
|
+
#-------------------------------------------------
|
1282
|
+
else
|
1283
|
+
tell("setAttributes: #{currentStyleName}: #{attribute}/#{value} not matching with #{currentValue} in Top-Node")
|
1284
|
+
containsMatchingAttributes=FALSE
|
1285
|
+
end
|
1286
|
+
end
|
1287
|
+
}
|
1288
|
+
#--------------------------------------------------------
|
1289
|
+
# Wurden alle Attribut-Wertepaare gefunden, d.h. kann
|
1290
|
+
# bisheriger style weiterverwendet werden ?
|
1291
|
+
#--------------------------------------------------------
|
1292
|
+
if(containsMatchingAttributes)
|
1293
|
+
tell("setAttributes: #{currentStyleName}: all attributes/values matching -> keeping current style")
|
1294
|
+
#-------------------------------------------------------
|
1295
|
+
# nein => passenden Style in Archiv suchen oder klonen und anpassen
|
1296
|
+
#-------------------------------------------------------
|
1297
|
+
else
|
1298
|
+
getAppropriateStyle(cell,currentStyle,attributes)
|
1299
|
+
end
|
1300
|
+
#------------------------------------------------------------------------
|
1301
|
+
# Zelle hatte noch gar keinen style zugewiesen
|
1302
|
+
#------------------------------------------------------------------------
|
1303
|
+
else
|
1304
|
+
#----------------------------------------------------------------------
|
1305
|
+
# Da style fehlt, ggf. aus office:value-type bestmoeglichen style ermitteln
|
1306
|
+
#----------------------------------------------------------------------
|
1307
|
+
valueType=cell.attributes["office:value-type"]
|
1308
|
+
if(valueType)
|
1309
|
+
case valueType
|
1310
|
+
when "string" then currentStyleName="myString"
|
1311
|
+
when "percentage" then currentStyleName="myPercentage"
|
1312
|
+
when "currency" then currentStyleName="myCurrency"
|
1313
|
+
when "float" then currentStyleName="myFloat"
|
1314
|
+
when "date" then currentStyleName="myDate"
|
1315
|
+
when "time" then currentStyleName="myTime"
|
1316
|
+
else
|
1317
|
+
die("setAttributes: unknown office:value-type #{valueType} found in #{cell}")
|
1318
|
+
end
|
1319
|
+
else
|
1320
|
+
#-----------------------------------------
|
1321
|
+
# 'myString' als Default
|
1322
|
+
#-----------------------------------------
|
1323
|
+
currentStyleName="myString"
|
1324
|
+
end
|
1325
|
+
#-------------------------------------------------------
|
1326
|
+
# passenden Style in Archiv suchen oder klonen und anpassen
|
1327
|
+
#-------------------------------------------------------
|
1328
|
+
file,currentStyle=getStyle(currentStyleName)
|
1329
|
+
getAppropriateStyle(cell,currentStyle,attributes)
|
1330
|
+
end
|
1331
|
+
end
|
1332
|
+
##########################################################################
|
1333
|
+
# internal: Function is called, when 'setAttributes' detected, that the current style
|
1334
|
+
# of a cell and a given attribute-list don't match. The function clones the current
|
1335
|
+
# style of the cell, generates a virtual new style, merges it with the attribute-list,
|
1336
|
+
# calculates a hash-value of the resulting style, checks whether the latter is already
|
1337
|
+
# in the pool of archived styles, retrieves an archived style or
|
1338
|
+
# writes the resulting new style, archives the latter and applies the effective style to cell.
|
1339
|
+
#-------------------------------------------------------------------------
|
1340
|
+
def getAppropriateStyle(cell,currentStyle,attributes)
|
1341
|
+
die("getAppropriateStyle: cell #{cell} is not a REXML::Element") unless (cell.class.to_s == "REXML::Element")
|
1342
|
+
die("getAppropriateStyle: style #{currentStyle} is not a REXML::Element") unless (currentStyle.class.to_s == "REXML::Element")
|
1343
|
+
die("getAppropriateStyle: hash #{attributes} is not a hash") unless (attributes.class.to_s == "Hash")
|
1344
|
+
die("getAppropriateStyle: attribute style:name not allowed in attribute-list as automatically generated") if (attributes.has_key?("style:name"))
|
1345
|
+
#------------------------------------------------------
|
1346
|
+
# Klonen
|
1347
|
+
#------------------------------------------------------
|
1348
|
+
newStyle=cloneNode(currentStyle)
|
1349
|
+
#------------------------------------------------------
|
1350
|
+
# Neuen style-Namen generieren und in Attributliste einfuegen
|
1351
|
+
# (oben wurde bereits geprueft, dass selbige keinen style-Namen enthaelt)
|
1352
|
+
# Cave: Wird neuer style spaeter verworfen (da in Archiv vorhanden), wird
|
1353
|
+
# @styleCounter wieder dekrementiert
|
1354
|
+
#------------------------------------------------------
|
1355
|
+
newStyleName="myAutoStyle"+(@styleCounter+=1).to_s
|
1356
|
+
attributes["style:name"]=newStyleName
|
1357
|
+
#------------------------------------------------------
|
1358
|
+
# Attributliste in neuen style einfuegen
|
1359
|
+
#------------------------------------------------------
|
1360
|
+
insertStyleAttributes(newStyle,attributes)
|
1361
|
+
#-----------------------------------------------------------
|
1362
|
+
# noch nicht geschriebenen style verhashen
|
1363
|
+
# (dabei wird auch style:name auf Dummy-Wert gesetzt)
|
1364
|
+
#-----------------------------------------------------------
|
1365
|
+
hashKey=style2Hash(newStyle)
|
1366
|
+
#----------------------------------------------------------
|
1367
|
+
# Neuer style bereits in Archiv vorhanden ?
|
1368
|
+
#----------------------------------------------------------
|
1369
|
+
if(@styleArchive.has_key?(hashKey))
|
1370
|
+
#-------------------------------------------------------
|
1371
|
+
# Zelle style aus Archiv zuweisen
|
1372
|
+
# @styleCounter dekrementieren und neuen style verwerfen
|
1373
|
+
#-------------------------------------------------------
|
1374
|
+
archiveStyleName=@styleArchive[hashKey]
|
1375
|
+
cell.attributes["table:style-name"]=archiveStyleName
|
1376
|
+
@styleCounter-=1
|
1377
|
+
newStyle=nil
|
1378
|
+
tell("getAppropriateStyle: archived style #{archiveStyleName} matches new attributes")
|
1379
|
+
else
|
1380
|
+
#-------------------------------------------------------
|
1381
|
+
# Neuen style in Hash aufnehmen, Zelle zuweisen und schreiben (!)
|
1382
|
+
#-------------------------------------------------------
|
1383
|
+
@styleArchive[hashKey]=newStyleName # archivieren
|
1384
|
+
cell.attributes["table:style-name"]=newStyleName # Zelle zuweisen
|
1385
|
+
@autoStyles.elements << newStyle # in content.xml schreiben
|
1386
|
+
tell("getAppropriateStyle: adding/archiving style '#{newStyleName}' (hash: #{hashKey})")
|
1387
|
+
end
|
1388
|
+
end
|
1389
|
+
##########################################################################
|
1390
|
+
# internal: verifies the validity of a hash of style-attributes.
|
1391
|
+
# The attributes have to be normed already.
|
1392
|
+
#-------------------------------------------------------------------------
|
1393
|
+
def checkStyleAttributes(attributes)
|
1394
|
+
die("checkStyleAttributes: hash #{attributes} is not a hash") unless (attributes.class.to_s == "Hash")
|
1395
|
+
#-------------------------------------------------
|
1396
|
+
# Normierungs-Check
|
1397
|
+
#-------------------------------------------------
|
1398
|
+
attributes.each{ |key,value|
|
1399
|
+
die("checkStyleAttributes: internal error: found unnormed or invalid attribute #{key}") unless (key.match(/:/))
|
1400
|
+
}
|
1401
|
+
#--------------------------------------------------------
|
1402
|
+
# fo:font-style und fo:font-weight vereinheitlichen (asiatisch/komplex)
|
1403
|
+
#--------------------------------------------------------
|
1404
|
+
fontStyle=attributes["fo:font-style"]
|
1405
|
+
if(fontStyle)
|
1406
|
+
if(attributes.has_key?("fo:font-style-asian") || attributes.has_key?("fo:font-style-complex"))
|
1407
|
+
tell("checkStyleAttributes: automatically overwritten fo:font-style-asian/complex with value of fo:font-style")
|
1408
|
+
end
|
1409
|
+
attributes["fo:font-style-asian"]=attributes["fo:font-style-complex"]=fontStyle
|
1410
|
+
end
|
1411
|
+
#--------------------------------------------------------
|
1412
|
+
fontWeight=attributes["fo:font-weight"]
|
1413
|
+
if(fontWeight)
|
1414
|
+
if(attributes.has_key?("fo:font-weight-asian") || attributes.has_key?("fo:font-weight-complex"))
|
1415
|
+
tell("checkStyleAttributes: automatically overwritten fo:font-weight-asian/complex with value of fo:font-weight")
|
1416
|
+
end
|
1417
|
+
attributes["fo:font-weight-asian"]=attributes["fo:font-weight-complex"]=fontWeight
|
1418
|
+
end
|
1419
|
+
#-----------------------------------------------------------------------
|
1420
|
+
# Sind nur entweder fo:border fo:border-... enthalten ?
|
1421
|
+
#-----------------------------------------------------------------------
|
1422
|
+
if(attributes.has_key?("fo:border") \
|
1423
|
+
&& (attributes.has_key?("fo:border-bottom") \
|
1424
|
+
|| attributes.has_key?("fo:border-top") \
|
1425
|
+
|| attributes.has_key?("fo:border-left") \
|
1426
|
+
|| attributes.has_key?("fo:border-right")))
|
1427
|
+
tell("checkStyleAttributes: automatically deleted fo:border as one or more sides were specified'")
|
1428
|
+
attributes.delete("fo:border")
|
1429
|
+
end
|
1430
|
+
#-----------------------------------------------------------------------
|
1431
|
+
# Sind fo:margin-left und fo:text-align kompatibel ?
|
1432
|
+
# Rules of precedence (hier willkuerlich): Alignment schlaegt Einruecktiefe ;-)
|
1433
|
+
#-----------------------------------------------------------------------
|
1434
|
+
leftMargin=attributes["fo:margin-left"]
|
1435
|
+
textAlign=attributes["fo:text-align"]
|
1436
|
+
#----------------------------------------------------------------------
|
1437
|
+
# Mittig oder rechtsbuendig impliziert aeusserst linken Rand
|
1438
|
+
#----------------------------------------------------------------------
|
1439
|
+
if(leftMargin && textAlign && (textAlign != "start") && (leftMargin != "0"))
|
1440
|
+
tell("checkStyleAttributes: automatically corrected: fo:text-align \'#{attributes['fo:text-align']}\' does not match fo:margin-left \'#{attributes['fo:margin-left']}\'")
|
1441
|
+
attributes["fo:margin-left"]="0"
|
1442
|
+
#----------------------------------------------------------------------
|
1443
|
+
# Einrueckung bedingt Linksbuendigkeit
|
1444
|
+
#----------------------------------------------------------------------
|
1445
|
+
elsif(leftMargin && (leftMargin != "0") && !textAlign)
|
1446
|
+
tell("checkStyleAttributes: automatically corrected: fo:margin-left \'#{attributes['fo:margin-left']}\' needs fo:text-align \'start\' to work")
|
1447
|
+
attributes["fo:text-align"]="start"
|
1448
|
+
end
|
1449
|
+
end
|
1450
|
+
##########################################################################
|
1451
|
+
# internal: Merges a hash of given style-attributes with those of
|
1452
|
+
# the given style-node. The attributes have to be normed already. Existing
|
1453
|
+
# attributes of the style-node are overwritten.
|
1454
|
+
#-------------------------------------------------------------------------
|
1455
|
+
def insertStyleAttributes(style,attributes)
|
1456
|
+
die("insertStyleAttributes: style #{style} is not a REXML::Element") unless (style.class.to_s == "REXML::Element")
|
1457
|
+
die("insertStyleAttributes: hash #{attributes} is not a hash") unless (attributes.class.to_s == "Hash")
|
1458
|
+
die("insertStyleAttributes: Missing attribute style:name in node #{style}") unless (style.attributes["style:name"])
|
1459
|
+
#-----------------------------------------------------------------
|
1460
|
+
# Cave: Sub-Nodes koennen, muessen aber nicht vorhanden sein
|
1461
|
+
# in diesem Fall werden sie spaeter angelegt
|
1462
|
+
#-----------------------------------------------------------------
|
1463
|
+
tableCellProperties=style.elements["style:table-cell-properties"]
|
1464
|
+
textProperties=style.elements["style:text-properties"]
|
1465
|
+
paragraphProperties=style.elements["style:paragraph-properties"]
|
1466
|
+
#-----------------------------------------------------------------
|
1467
|
+
# Vorverarbeitung
|
1468
|
+
#-----------------------------------------------------------------
|
1469
|
+
checkStyleAttributes(attributes)
|
1470
|
+
#-----------------------------------------------------------------
|
1471
|
+
# Attribute in entsprechende (Unter-)Knoten einfuegen
|
1472
|
+
#-----------------------------------------------------------------
|
1473
|
+
attributes.each{ |key,value|
|
1474
|
+
#------------------------------------------------------------------------
|
1475
|
+
# style:table-cell-properties
|
1476
|
+
#------------------------------------------------------------------------
|
1477
|
+
if(key.match(/^fo:border/) || (key == "style:text-align-source") || key == ("fo:background-color"))
|
1478
|
+
tableCellProperties=style.add_element("style:table-cell-properties") unless (tableCellProperties)
|
1479
|
+
#--------------------------------------------------------------------------
|
1480
|
+
# Cave: fo:border-(bottom|top|left|right) und fo:border duerfen NICHT
|
1481
|
+
# gleichzeitig vorhanden sein !
|
1482
|
+
# Zwar wurde fo:border in diesem Fall bereits durch checkStyleAttributes aus
|
1483
|
+
# Attributliste geloescht, das Attribut ist aber ggf. auch noch aus bestehendem style
|
1484
|
+
# zu loeschen !
|
1485
|
+
#--------------------------------------------------------------------------
|
1486
|
+
if(key.match(/^fo:border-/)) # Falls Border-Seitenangabe (bottom|top|left|right)
|
1487
|
+
tableCellProperties.attributes.delete("fo:border") # fo:border selbst loeschen
|
1488
|
+
end
|
1489
|
+
tableCellProperties.attributes[key]=value
|
1490
|
+
else
|
1491
|
+
case key
|
1492
|
+
#------------------------------------------------------------------------
|
1493
|
+
# style:style
|
1494
|
+
#------------------------------------------------------------------------
|
1495
|
+
when "style:name","style:family","style:parent-style-name","style:data-style-name"
|
1496
|
+
style.attributes[key]=value
|
1497
|
+
#------------------------------------------------------------------------
|
1498
|
+
# style:text-properties
|
1499
|
+
#------------------------------------------------------------------------
|
1500
|
+
when "fo:color","fo:font-style","fo:font-style-asian","fo:font-style-complex",
|
1501
|
+
"fo:font-weight","fo:font-weight-asian","fo:font-weight-complex"
|
1502
|
+
textProperties=style.add_element("style:text-properties") unless (textProperties)
|
1503
|
+
textProperties.attributes[key]=value
|
1504
|
+
#---------------------------------------------------------
|
1505
|
+
# asiatische und komplexe Varianten nachziehen
|
1506
|
+
#---------------------------------------------------------
|
1507
|
+
if(key == "fo:font-style")
|
1508
|
+
textProperties.attributes["fo:font-style-asian"]=textProperties.attributes["fo:font-style-complex"]=value
|
1509
|
+
elsif(key == "fo:font-weight")
|
1510
|
+
textProperties.attributes["fo:font-weight-asian"]=textProperties.attributes["fo:font-weight-complex"]=value
|
1511
|
+
end
|
1512
|
+
#------------------------------------------------------------------------
|
1513
|
+
# style:paragraph-properties
|
1514
|
+
#------------------------------------------------------------------------
|
1515
|
+
when "fo:margin-left","fo:text-align"
|
1516
|
+
paragraphProperties=style.add_element("style:paragraph-properties") unless (paragraphProperties)
|
1517
|
+
paragraphProperties.attributes[key]=value
|
1518
|
+
else
|
1519
|
+
die("insertStyleAttributes: invalid or not implemented attribute #{key}")
|
1520
|
+
end
|
1521
|
+
end
|
1522
|
+
}
|
1523
|
+
end
|
1524
|
+
##########################################################################
|
1525
|
+
# internal: Clones a given node recursively and returns the top-node as
|
1526
|
+
# REXML::Element
|
1527
|
+
#-------------------------------------------------------------------------
|
1528
|
+
def cloneNode(node)
|
1529
|
+
die("cloneNode: node #{node} is not a REXML::Element") unless (node.class.to_s == "REXML::Element")
|
1530
|
+
newNode=node.clone()
|
1531
|
+
#-----------------------------------------------
|
1532
|
+
# Rekursion fuer Kind-Elemente
|
1533
|
+
#-----------------------------------------------
|
1534
|
+
node.elements.each{ |child|
|
1535
|
+
newNode.elements << cloneNode(child)
|
1536
|
+
}
|
1537
|
+
return newNode
|
1538
|
+
end
|
1539
|
+
##########################################################################
|
1540
|
+
# Creates a new style out of the given attribute-hash with abbreviated and simplified syntax.
|
1541
|
+
# mySheet.writeStyleAbbr({"name" => "myNewPercentStyle", # <- style-name to be applied to a cell
|
1542
|
+
# "margin-left" => "0.3cm",
|
1543
|
+
# "text-align" => "start",
|
1544
|
+
# "color" => "blue",
|
1545
|
+
# "border" => "0.01cm solid black",
|
1546
|
+
# "font-style" => "italic",
|
1547
|
+
# "data-style-name" => "myPercentFormat", # <- predefined RODS data-style
|
1548
|
+
# "font-weight" => "bold"})
|
1549
|
+
#-------------------------------------------------------------------------
|
1550
|
+
def writeStyleAbbr(attributes)
|
1551
|
+
writeStyle(normStyleHash(attributes))
|
1552
|
+
end
|
1553
|
+
##########################################################################
|
1554
|
+
# internal: creates a style in content.xml out of the given attribute-hash, which has to be
|
1555
|
+
# supplied in fully qualified (normed) form. Missing attributes are replaced by default-values.
|
1556
|
+
#-------------------------------------------------------------------------
|
1557
|
+
def writeStyle(attributes)
|
1558
|
+
die("writeStyle: Style-Hash #{attributes} is not a Hash") unless (attributes.class.to_s == "Hash")
|
1559
|
+
die("writeStyle: Missing attribute style:name") unless (attributes.has_key?("style:name"))
|
1560
|
+
#-----------------------------------------------------------------------
|
1561
|
+
# Hashes potentieller Kind-Elemente und Tag-Vorbefuellung
|
1562
|
+
#-----------------------------------------------------------------------
|
1563
|
+
tableCellProperties=Hash.new(); tableCellProperties[TAG]="style:table-cell-properties"
|
1564
|
+
textProperties=Hash.new(); textProperties[TAG]="style:text-properties"
|
1565
|
+
paragraphProperties=Hash.new(); paragraphProperties[TAG]="style:paragraph-properties"
|
1566
|
+
#----------------------------------------------------------------------
|
1567
|
+
# Nur wenige Default-Werte
|
1568
|
+
#----------------------------------------------------------------------
|
1569
|
+
styleAttributes={TAG => "style:style",
|
1570
|
+
"style:name" => "noName", # eigentlich unnoetig, da Attribut zwingend und oben geprueft
|
1571
|
+
"style:family" => "table-cell",
|
1572
|
+
"style:parent-style-name" => "Default"}
|
1573
|
+
#--------------------------------------------------------------
|
1574
|
+
# Vorverarbeitung
|
1575
|
+
#--------------------------------------------------------------
|
1576
|
+
checkStyleAttributes(attributes)
|
1577
|
+
#--------------------------------------------------------------
|
1578
|
+
# Uebernahme der Werte in entsprechende (Sub-)Hashes
|
1579
|
+
#--------------------------------------------------------------
|
1580
|
+
attributes.each{ |key,value|
|
1581
|
+
die("writeStyle: value for key #{key} is not a String") unless (value.class.to_s == "String")
|
1582
|
+
#--------------------------------------------------------
|
1583
|
+
# Werte den Hashes zuordnen
|
1584
|
+
#--------------------------------------------------------
|
1585
|
+
case key
|
1586
|
+
when "style:name" then styleAttributes["style:name"]=value
|
1587
|
+
when "style:family" then styleAttributes["style:family"]=value
|
1588
|
+
when "style:parent-style-name" then styleAttributes["style:parent-style-name"]=value
|
1589
|
+
when "style:data-style-name" then styleAttributes["style:data-style-name"]=value
|
1590
|
+
#---------------------------------------------------------------------------------
|
1591
|
+
when "fo:background-color" then tableCellProperties["fo:background-color"]=value
|
1592
|
+
when "style:text-align-source" then tableCellProperties["style:text-align-source"]=value
|
1593
|
+
when "fo:border-bottom" then tableCellProperties["fo:border-bottom"]=value
|
1594
|
+
when "fo:border-top" then tableCellProperties["fo:border-top"]=value
|
1595
|
+
when "fo:border-left" then tableCellProperties["fo:border-left"]=value
|
1596
|
+
when "fo:border-right" then tableCellProperties["fo:border-right"]=value
|
1597
|
+
when "fo:border" then tableCellProperties["fo:border"]=value
|
1598
|
+
#---------------------------------------------------------------------------------
|
1599
|
+
when "fo:color" then textProperties["fo:color"]=value
|
1600
|
+
when "fo:font-style" then textProperties["fo:font-style"]=value
|
1601
|
+
when "fo:font-style-asian" then textProperties["fo:font-style-asian"]=value
|
1602
|
+
when "fo:font-style-complex" then textProperties["fo:font-style-complex"]=value
|
1603
|
+
when "fo:font-weight" then textProperties["fo:font-weight"]=value
|
1604
|
+
when "fo:font-weight-asian" then textProperties["fo:font-weight-asian"]=value
|
1605
|
+
when "fo:font-weight-complex" then textProperties["fo:font-weight-complex"]=value
|
1606
|
+
#---------------------------------------------------------------------------------
|
1607
|
+
when "fo:margin-left" then paragraphProperties["fo:margin-left"]=value
|
1608
|
+
when "fo:text-align" then paragraphProperties["fo:text-align"]=value
|
1609
|
+
else
|
1610
|
+
die("writeStyle: invalid or not implemented attribute #{key}")
|
1611
|
+
end
|
1612
|
+
}
|
1613
|
+
#------------------------------------------------------------
|
1614
|
+
# Belegte Kind-Hashes hinzufuegen
|
1615
|
+
# (Laenge > 1, da vordem bereits TAG in Kind-Hashes eingefuegt)
|
1616
|
+
#------------------------------------------------------------
|
1617
|
+
if (tableCellProperties.length > 1) then styleAttributes["child1"]=tableCellProperties end
|
1618
|
+
if (textProperties.length > 1) then styleAttributes["child2"]=textProperties end
|
1619
|
+
if (paragraphProperties.length > 1) then styleAttributes["child3"]=paragraphProperties end
|
1620
|
+
writeStyleXml(CONTENT,styleAttributes)
|
1621
|
+
end
|
1622
|
+
##########################################################################
|
1623
|
+
# internal: write a style-XML-tree to content.xml or styles.xml. The given hash
|
1624
|
+
# has to be provided in qualified form. The new
|
1625
|
+
# style is archived in a hash-pool of styles. Prior to that the 'style:name'
|
1626
|
+
# is replaced by a dummy-value to ensure comparability.
|
1627
|
+
#
|
1628
|
+
# Caveat: RODS' default-styles cannot be overwritten !
|
1629
|
+
#
|
1630
|
+
# Example (internal setting of default date-style upon object creation)
|
1631
|
+
# #------------------------------------------------------------------------
|
1632
|
+
# # date
|
1633
|
+
# #------------------------------------------------------------------------
|
1634
|
+
# # date-Style part 1 (format)
|
1635
|
+
# #--------------------------------------------------------
|
1636
|
+
# writeStyleXml(STYLES,{TAG => "number:date-style",
|
1637
|
+
# "style:name" => "myDateFormat",
|
1638
|
+
# "number:automatic-order" => "true",
|
1639
|
+
# "number:format-source" => "language",
|
1640
|
+
# "child1" => {TAG => "number:day"},
|
1641
|
+
# "child2" => {TAG => "number:text",
|
1642
|
+
# TEXT => "."},
|
1643
|
+
# "child3" => {TAG => "number:month"},
|
1644
|
+
# "child4" => {TAG => "number:text",
|
1645
|
+
# TEXT => "."},
|
1646
|
+
# "child5" => {TAG => "number:year"}})
|
1647
|
+
# #--------------------------------------------------------
|
1648
|
+
# # date-Style part 2 (referencing format above)
|
1649
|
+
# #--------------------------------------------------------
|
1650
|
+
# writeStyleXml(CONTENT,{TAG => "style:style",
|
1651
|
+
# "style:name" => "myDate",
|
1652
|
+
# "style:family" => "table-cell",
|
1653
|
+
# "style:parent-style-name" => "Default",
|
1654
|
+
# "style:data-style-name" => "myDateFormat"})
|
1655
|
+
#------------------------------------------------------------------------
|
1656
|
+
def writeStyleXml(file,styleHash)
|
1657
|
+
topNode=@autoStyles # Default
|
1658
|
+
#----------------------------------------------------------
|
1659
|
+
# In welche Ausgabedatei ?
|
1660
|
+
#----------------------------------------------------------
|
1661
|
+
case file
|
1662
|
+
when STYLES then topNode=@officeStyles
|
1663
|
+
when CONTENT then topNode=@autoStyles
|
1664
|
+
else die("writeStyleXml: wrong file-parameter #{file}")
|
1665
|
+
end
|
1666
|
+
die("writeStyleXml: Style-Hash #{styleHash} is not a Hash") unless (styleHash.class.to_s == "Hash")
|
1667
|
+
die("writeStyleXml: Missing attribute style:name") unless (styleHash.has_key?("style:name"))
|
1668
|
+
styleName=styleHash["style:name"]
|
1669
|
+
#-----------------------------------------------------------
|
1670
|
+
# Style dieses Namens bereits vorhanden ? -> Loeschen,
|
1671
|
+
# sofern kein Default-Style von RODS, und aus style-Archiv ggf. entfernen.
|
1672
|
+
# Cave: style wird nur in der angegebenen der beiden Dateien
|
1673
|
+
# content.xml ODER styles.xml gesucht !
|
1674
|
+
#-----------------------------------------------------------
|
1675
|
+
isFixedStyle=@fixedStyles.index(styleName)
|
1676
|
+
styleNode=topNode.elements["*[@style:name = '#{styleName}']"]
|
1677
|
+
if(styleNode && !isFixedStyle)
|
1678
|
+
tell("writeStyleXml: Deleting previous style with style:name '#{styleName}'")
|
1679
|
+
topNode.elements.delete(styleNode)
|
1680
|
+
#------------------------------------------
|
1681
|
+
# In Archiv loeschen
|
1682
|
+
#------------------------------------------
|
1683
|
+
@styleArchive.each{ |key,value|
|
1684
|
+
if(value == styleName)
|
1685
|
+
@styleArchive.delete(key)
|
1686
|
+
tell("writeStyleXml: deleting style #{value} from archive")
|
1687
|
+
break
|
1688
|
+
end
|
1689
|
+
}
|
1690
|
+
else
|
1691
|
+
tell("writeStyleXml: leaving existing default-style #{styleName} untouched")
|
1692
|
+
end
|
1693
|
+
#-----------------------------------------------------------
|
1694
|
+
# und schreiben, sofern nicht Default-Style
|
1695
|
+
#-----------------------------------------------------------
|
1696
|
+
unless(styleNode && isFixedStyle)
|
1697
|
+
nodeWritten=writeXml(topNode,styleHash)
|
1698
|
+
#-----------------------------------------------------------
|
1699
|
+
# geschriebenen Knoten verhashen
|
1700
|
+
#-----------------------------------------------------------
|
1701
|
+
hashKey=style2Hash(nodeWritten)
|
1702
|
+
if(@styleArchive.has_key?(hashKey))
|
1703
|
+
tell("writeStyleXml: style is already in archive")
|
1704
|
+
else
|
1705
|
+
@styleArchive[hashKey]=styleName
|
1706
|
+
end
|
1707
|
+
tell("writeStyleXml: adding/archiving style '#{styleName}' (hash: #{hashKey})")
|
1708
|
+
end
|
1709
|
+
end
|
1710
|
+
##########################################################################
|
1711
|
+
# internal: converts XML-node of a style into a hash-value and returns
|
1712
|
+
# the string-representation of the latter.
|
1713
|
+
##########################################################################
|
1714
|
+
def style2Hash(styleNode)
|
1715
|
+
#------------------------------------------------------------------
|
1716
|
+
# Fuer Verhashung
|
1717
|
+
# - Stringwandlung
|
1718
|
+
# - style:name auf Dummy-Wert setzen (da variabel)
|
1719
|
+
# - White-Space entfernen
|
1720
|
+
# - UND: Zeichen sortieren !!!
|
1721
|
+
# notwendig, da die Attributreihenfolge von XML-Knoten variiert !
|
1722
|
+
# (z.B. bei/nach Klonung)
|
1723
|
+
#------------------------------------------------------------------
|
1724
|
+
styleNodeString=styleNode.to_s
|
1725
|
+
styleNodeString.sub!(/style:name\s*=\s*('|")\S+('|")/,"style:name="+DUMMY)
|
1726
|
+
styleNodeString.gsub!(/\s+/,"")
|
1727
|
+
sortedString=styleNodeString.split(//).sort.join
|
1728
|
+
return sortedString.hash.to_s
|
1729
|
+
end
|
1730
|
+
##########################################################################
|
1731
|
+
# internal: write initial default styles into content.xml and styles.xml
|
1732
|
+
#------------------------------------------------------------------------
|
1733
|
+
def writeDefaultStyles()
|
1734
|
+
#------------------------------------------------------------------------
|
1735
|
+
# Formate fuer die Anlage von Tabellen
|
1736
|
+
#------------------------------------------------------------------------
|
1737
|
+
# Tabellenformat selbst
|
1738
|
+
#------------------------------------------------------------------------
|
1739
|
+
writeStyleXml(CONTENT,{TAG => "style:style",
|
1740
|
+
"style:name" => "myTable",
|
1741
|
+
"style:family" => "table",
|
1742
|
+
"style:master-page-name" => "Default",
|
1743
|
+
CHILD => {TAG => "style:table-properties",
|
1744
|
+
"style:writing-mode" => "lr-tb",
|
1745
|
+
"table:display" => "true"}})
|
1746
|
+
#------------------------------------------------------------------------
|
1747
|
+
# Zeilenformat
|
1748
|
+
#------------------------------------------------------------------------
|
1749
|
+
writeStyleXml(CONTENT,{TAG => "style:style",
|
1750
|
+
"style:name" => "myRow",
|
1751
|
+
"style:family" => "table-row",
|
1752
|
+
CHILD => {TAG => "style:table-row-properties",
|
1753
|
+
"style:use-optimal-row-height" => "true",
|
1754
|
+
"style:row-height" => "0.452cm",
|
1755
|
+
"fo:break-before" => "auto"}})
|
1756
|
+
#------------------------------------------------------------------------
|
1757
|
+
# Spaltenformat
|
1758
|
+
#------------------------------------------------------------------------
|
1759
|
+
writeStyleXml(CONTENT,{TAG => "style:style",
|
1760
|
+
"style:name" => "myColumn",
|
1761
|
+
"style:family" => "table-column",
|
1762
|
+
CHILD => {TAG => "style:table-column-properties",
|
1763
|
+
"style:column-width" => "2.267cm",
|
1764
|
+
"style:row-height" => "0.452cm",
|
1765
|
+
"fo:break-before" => "auto"}})
|
1766
|
+
#------------------------------------------------------------------------
|
1767
|
+
# Float/Formula
|
1768
|
+
#------------------------------------------------------------------------
|
1769
|
+
# Float-Style Teil 1 (Format)
|
1770
|
+
#--------------------------------------------------------
|
1771
|
+
writeStyleXml(STYLES,{TAG => "number:number-style",
|
1772
|
+
"style:name" => "myFloatFormat",
|
1773
|
+
CHILD => {TAG => "number:number",
|
1774
|
+
"number:decimal-places" => "2",
|
1775
|
+
"number:min-integer-digits" => "1"}})
|
1776
|
+
#--------------------------------------------------------
|
1777
|
+
# Float-Style Teil 2 (Referenz zu Format oben)
|
1778
|
+
#--------------------------------------------------------
|
1779
|
+
writeStyleXml(CONTENT,{TAG => "style:style",
|
1780
|
+
"style:name" => "myFloat",
|
1781
|
+
"style:family" => "table-cell",
|
1782
|
+
"style:parent-style-name" => "Default",
|
1783
|
+
"style:data-style-name" => "myFloatFormat"})
|
1784
|
+
#------------------------------------------------------------------------
|
1785
|
+
# Zeit
|
1786
|
+
#------------------------------------------------------------------------
|
1787
|
+
# Zeit-Style Teil 1 (Format)
|
1788
|
+
#--------------------------------------------------------
|
1789
|
+
writeStyleXml(STYLES,{TAG => "number:time-style",
|
1790
|
+
"style:name" => "myTimeFormat",
|
1791
|
+
"child1" => {TAG => "number:hours",
|
1792
|
+
"number:style" => "long"},
|
1793
|
+
"child2" => {TAG => "number:text",
|
1794
|
+
TEXT => ":"},
|
1795
|
+
"child3" => {TAG => "number:minutes",
|
1796
|
+
"number:style" => "long"}})
|
1797
|
+
#--------------------------------------------------------
|
1798
|
+
# Zeit-Style Teil 2 (Referenz zu Format oben)
|
1799
|
+
#--------------------------------------------------------
|
1800
|
+
writeStyleXml(CONTENT,{TAG => "style:style",
|
1801
|
+
"style:name" => "myTime",
|
1802
|
+
"style:family" => "table-cell",
|
1803
|
+
"style:parent-style-name" => "Default",
|
1804
|
+
"style:data-style-name" => "myTimeFormat"})
|
1805
|
+
#------------------------------------------------------------------------
|
1806
|
+
# Prozent
|
1807
|
+
#------------------------------------------------------------------------
|
1808
|
+
# Prozent-Style Teil 1 (Format)
|
1809
|
+
#--------------------------------------------------------
|
1810
|
+
writeStyleXml(STYLES,{TAG => "number:percent-style",
|
1811
|
+
"style:name" => "myPercentFormat",
|
1812
|
+
"child1" => {TAG => "number:number",
|
1813
|
+
"number:decimal-places" => "2",
|
1814
|
+
"number:min-integer-digits" => "1"},
|
1815
|
+
"child2" => {TAG => "number:text",
|
1816
|
+
TEXT => "%"}})
|
1817
|
+
#--------------------------------------------------------
|
1818
|
+
# Prozent-Style Teil 2 (Referenz zu Format oben)
|
1819
|
+
#--------------------------------------------------------
|
1820
|
+
writeStyleXml(CONTENT,{TAG => "style:style",
|
1821
|
+
"style:name" => "myPercent",
|
1822
|
+
"style:family" => "table-cell",
|
1823
|
+
"style:parent-style-name" => "Default",
|
1824
|
+
"style:data-style-name" => "myPercentFormat"})
|
1825
|
+
#------------------------------------------------------------------------
|
1826
|
+
# String
|
1827
|
+
#------------------------------------------------------------------------
|
1828
|
+
writeStyleXml(CONTENT,{TAG => "style:style",
|
1829
|
+
"style:name" => "myString",
|
1830
|
+
"style:family" => "table-cell",
|
1831
|
+
"style:parent-style-name" => "Default"})
|
1832
|
+
#------------------------------------------------------------------------
|
1833
|
+
# Datum
|
1834
|
+
#------------------------------------------------------------------------
|
1835
|
+
# Date-Style Teil 1 (Format)
|
1836
|
+
#--------------------------------------------------------
|
1837
|
+
writeStyleXml(STYLES,{TAG => "number:date-style",
|
1838
|
+
"style:name" => "myDateFormat",
|
1839
|
+
"number:automatic-order" => "true",
|
1840
|
+
"number:format-source" => "language",
|
1841
|
+
"child1" => {TAG => "number:day"},
|
1842
|
+
"child2" => {TAG => "number:text",
|
1843
|
+
TEXT => "."},
|
1844
|
+
"child3" => {TAG => "number:month"},
|
1845
|
+
"child4" => {TAG => "number:text",
|
1846
|
+
TEXT => "."},
|
1847
|
+
"child5" => {TAG => "number:year"}})
|
1848
|
+
#--------------------------------------------------------
|
1849
|
+
# Date-Style Teil 2 (Referenz zu Format oben)
|
1850
|
+
#--------------------------------------------------------
|
1851
|
+
writeStyleXml(CONTENT,{TAG => "style:style",
|
1852
|
+
"style:name" => "myDate",
|
1853
|
+
"style:family" => "table-cell",
|
1854
|
+
"style:parent-style-name" => "Default",
|
1855
|
+
"style:data-style-name" => "myDateFormat"})
|
1856
|
+
#------------------------------------------------------------------------
|
1857
|
+
# Datum als Wochentag
|
1858
|
+
#------------------------------------------------------------------------
|
1859
|
+
# Date-Style Teil 1 (Format)
|
1860
|
+
#--------------------------------------------------------
|
1861
|
+
writeStyleXml(STYLES,{TAG => "number:date-style",
|
1862
|
+
"style:name" => "myDateFormatDay",
|
1863
|
+
CHILD => {TAG => "number:day-of-week"}})
|
1864
|
+
#--------------------------------------------------------
|
1865
|
+
# Date-Style Teil 2 (Referenz zu Format oben)
|
1866
|
+
#--------------------------------------------------------
|
1867
|
+
writeStyleXml(CONTENT,{TAG => "style:style",
|
1868
|
+
"style:name" => "myDateDay",
|
1869
|
+
"style:family" => "table-cell",
|
1870
|
+
"style:parent-style-name" => "Default",
|
1871
|
+
"style:data-style-name" => "myDateFormatDay"})
|
1872
|
+
#------------------------------------------------------------------------
|
1873
|
+
# Waehrung
|
1874
|
+
#------------------------------------------------------------------------
|
1875
|
+
# Currency-Style Teil 1 (Mapping bei positiver Zahl)
|
1876
|
+
#--------------------------------------------------------
|
1877
|
+
writeStyleXml(STYLES,{TAG => "number:currency-style",
|
1878
|
+
"style:name" => "myCurrencyFormatPositive",
|
1879
|
+
"child1" => {TAG => "number:number",
|
1880
|
+
"number:decimal-places" => "2",
|
1881
|
+
"number:min-integer-digits" => "1",
|
1882
|
+
"number:grouping" => "true"},
|
1883
|
+
"child2" => {TAG => "number:text",
|
1884
|
+
TEXT => " "},
|
1885
|
+
"child3" => {TAG => "number:currency-symbol",
|
1886
|
+
"number:language" => @language,
|
1887
|
+
"number:country" => @country,
|
1888
|
+
TEXT => @currencySymbol}})
|
1889
|
+
#--------------------------------------------------------
|
1890
|
+
# Currency-Style Teil 2 (Format mit Referenz zu Mapping)
|
1891
|
+
#--------------------------------------------------------
|
1892
|
+
writeStyleXml(STYLES,{TAG => "number:currency-style",
|
1893
|
+
"style:name" => "myCurrencyFormat",
|
1894
|
+
"child1" => {TAG => "style:text-properties",
|
1895
|
+
"fo:color" => "#ff0000"},
|
1896
|
+
"child2" => {TAG => "number:text",
|
1897
|
+
TEXT => "-" },
|
1898
|
+
"child3" => {TAG => "number:number",
|
1899
|
+
"number:decimal-places" => "2",
|
1900
|
+
"number:min-integer-digits" => "1",
|
1901
|
+
"number:grouping" => "true"},
|
1902
|
+
"child4" => {TAG => "number:text",
|
1903
|
+
TEXT => " " },
|
1904
|
+
"child5" => {TAG => "number:currency-symbol",
|
1905
|
+
"number:language" => @language,
|
1906
|
+
"number:country" => @country,
|
1907
|
+
TEXT => @currencySymbol },
|
1908
|
+
"child6" => {TAG => "style:map",
|
1909
|
+
"style:condition" => "value()>=0",
|
1910
|
+
"style:apply-style-name" => "myCurrencyFormatPositive" }})
|
1911
|
+
#--------------------------------------------------------
|
1912
|
+
# Currency-Style Teil 3 (Referenz zu Format oben)
|
1913
|
+
#--------------------------------------------------------
|
1914
|
+
writeStyleXml(CONTENT,{TAG => "style:style",
|
1915
|
+
"style:name" => "myCurrency",
|
1916
|
+
"style:family" => "table-cell",
|
1917
|
+
"style:parent-style-name" => "Default",
|
1918
|
+
"style:data-style-name" => "myCurrencyFormat"})
|
1919
|
+
#--------------------------------------------------------
|
1920
|
+
# Annotation-Styles Teil 1
|
1921
|
+
#--------------------------------------------------------
|
1922
|
+
writeStyleXml(STYLES,{TAG => "style:style",
|
1923
|
+
"style:name" => "myCommentParagraph",
|
1924
|
+
"style:family" => "paragraph",
|
1925
|
+
"child1" => {TAG => "style:paragraph-properties",
|
1926
|
+
"style:writing-mode" => "page",
|
1927
|
+
"style:text-autospace" => "none",
|
1928
|
+
"style:line-break" => "normal"},
|
1929
|
+
"child2" => {TAG => "style:text-properties",
|
1930
|
+
"style:text-overline-mode" => "continuous",
|
1931
|
+
"fo:country" => @country,
|
1932
|
+
"style:country-asian" => "CN",
|
1933
|
+
"fo:font-size" => "10pt",
|
1934
|
+
"fo:font-weight" => "normal",
|
1935
|
+
"fo:text-shadow" => "none",
|
1936
|
+
"fo:hyphenate" => "false",
|
1937
|
+
"style:font-name-asian" => "DejaVu Sans",
|
1938
|
+
"style:font-style-asian" => "normal",
|
1939
|
+
"style:font-name-comlex" => "Lohit Hindi",
|
1940
|
+
"style:text-overline-style" => "none",
|
1941
|
+
"style:text-outline" => "false",
|
1942
|
+
"style:font-size-asian" => "10pt",
|
1943
|
+
"fo:language" => @language,
|
1944
|
+
"style:text-emphasize" => "none",
|
1945
|
+
"style:font-style-complex" => "normal",
|
1946
|
+
"style:text-line-through-style" => "none",
|
1947
|
+
"style:font-weight-complex" => "normal",
|
1948
|
+
"style:font-weight-asian" => "normal",
|
1949
|
+
"style:font-relief" => "home",
|
1950
|
+
"style:font-size-complex" => "10 pt",
|
1951
|
+
"style:language-asian" => "zh",
|
1952
|
+
"style-text-underline-mode" => "continuous",
|
1953
|
+
"style:country-complex" => "IN",
|
1954
|
+
"fo:font-style" => "normal",
|
1955
|
+
"style:text-line-through-mode" => "continuous",
|
1956
|
+
"style:text-overline-color" => "font-color",
|
1957
|
+
"style:text-underline-style" => "none",
|
1958
|
+
"style:language-complex" => "hi",
|
1959
|
+
"style:font-name" => "Arial"}})
|
1960
|
+
#--------------------------------------------------------
|
1961
|
+
# Annotation-Styles Teil 2
|
1962
|
+
#--------------------------------------------------------
|
1963
|
+
writeStyleXml(STYLES,{TAG => "style:style",
|
1964
|
+
"style:name" => "myCommentText",
|
1965
|
+
"style:family" => "text",
|
1966
|
+
"child" => {TAG => "style:text-properties",
|
1967
|
+
"style:text-overline-mode" => "continuous",
|
1968
|
+
"fo:country" => @country,
|
1969
|
+
"style:country-asian" => "CN",
|
1970
|
+
"fo:font-size" => "10pt",
|
1971
|
+
"fo:font-weight" => "normal",
|
1972
|
+
"fo:text-shadow" => "none",
|
1973
|
+
"style:font-name-asian" => "DejaVu Sans",
|
1974
|
+
"style:font-style-asian" => "normal",
|
1975
|
+
"style:font-name-complex" => "Lohit Hindi",
|
1976
|
+
"style:text-overline-style" => "none",
|
1977
|
+
"style:text-outline" => "false",
|
1978
|
+
"style:font-size-asian" => "10pt",
|
1979
|
+
"fo:language" => @language,
|
1980
|
+
"style:text-emphasize" => "none",
|
1981
|
+
"style:font-style-complex" => "normal",
|
1982
|
+
"style:text-line-through-style" => "none",
|
1983
|
+
"style:font-weight-complex" => "normal",
|
1984
|
+
"style:font-weight-asian" => "normal",
|
1985
|
+
"style:font-relief" => "none",
|
1986
|
+
"style:font-size-complex" => "10pt",
|
1987
|
+
"style:language-asian" => "zh",
|
1988
|
+
"style:text-underline-mode" => "continuous",
|
1989
|
+
"style:country-complex" => "IN",
|
1990
|
+
"fo:font-style" => "normal",
|
1991
|
+
"style:text-line-through-mode" => "continuous",
|
1992
|
+
"style:text-overline-color" => "font-color",
|
1993
|
+
"style:text-underline-style" => "none",
|
1994
|
+
"style:language-complex" => "hi",
|
1995
|
+
"style:font-name" => "Arial"}})
|
1996
|
+
#--------------------------------------------------------
|
1997
|
+
# Annotation-Styles Teil 3
|
1998
|
+
#--------------------------------------------------------
|
1999
|
+
writeStyleXml(CONTENT,{TAG => "style:style",
|
2000
|
+
"style:name" => "myCommentGraphics",
|
2001
|
+
"style:family" => "graphic",
|
2002
|
+
CHILD => {TAG => "style:graphic-properties",
|
2003
|
+
"fo:padding-right" => "0.1cm",
|
2004
|
+
"draw:marker-start-width" => "0.2cm",
|
2005
|
+
"draw:auto-grow-width" => "false",
|
2006
|
+
"draw:marker-start-center" => "false",
|
2007
|
+
"draw:shadow" => "hidden",
|
2008
|
+
"draw:shadow-offset-x" => "0.1cm",
|
2009
|
+
"draw:shadow-offset-y" => "0.1cm",
|
2010
|
+
"draw:marker-start" => "Linienende_20_1",
|
2011
|
+
"fo:padding-top" => "0.1cm",
|
2012
|
+
"draw:fill" => "solid",
|
2013
|
+
"draw:caption-escape-direction" => "auto",
|
2014
|
+
"fo:padding-left" => "0.1cm",
|
2015
|
+
"draw:fill-color" => "#ffffcc",
|
2016
|
+
"draw:auto-grow-height" => "true",
|
2017
|
+
"fo:padding-bottom" => "0.1cm"}})
|
2018
|
+
end
|
2019
|
+
##########################################################################
|
2020
|
+
# Helper-Tool: Prints all styles of styles.xml in indented ASCII-notation
|
2021
|
+
# myHash.printOfficeStyles()
|
2022
|
+
# * Lines starting with 'E' are Element-Tags
|
2023
|
+
# * Lines starting with 'A' are Attributes
|
2024
|
+
# * Lines starting with 'T' are Element-Text
|
2025
|
+
# Sample output:
|
2026
|
+
# E: style:style
|
2027
|
+
# A: style:name => "myCommentGraphics"
|
2028
|
+
# A: style:family => "graphic"
|
2029
|
+
# E: style:graphic-properties
|
2030
|
+
# A: fo:padding-right => "0.1cm"
|
2031
|
+
# A: draw:marker-start-width => "0.2cm"
|
2032
|
+
# A: draw:auto-grow-width => "false"
|
2033
|
+
# A: draw:marker-start-center => "false"
|
2034
|
+
# A: draw:shadow => "hidden"
|
2035
|
+
# A: draw:shadow-offset-x => "0.1cm"
|
2036
|
+
# A: draw:shadow-offset-y => "0.1cm"
|
2037
|
+
# A: draw:marker-start => "Linienende_20_1"
|
2038
|
+
# A: fo:padding-top => "0.1cm"
|
2039
|
+
# A: draw:fill => "solid"
|
2040
|
+
# A: draw:caption-escape-direction => "auto"
|
2041
|
+
# A: fo:padding-left => "0.1cm"
|
2042
|
+
# A: draw:fill-color => "#ffffcc"
|
2043
|
+
# A: draw:auto-grow-height => "true"
|
2044
|
+
# A: fo:padding-bottom => "0.1cm"
|
2045
|
+
#-------------------------------------------------------------------------
|
2046
|
+
def printOfficeStyles()
|
2047
|
+
printStyles(@officeStyles," ")
|
2048
|
+
end
|
2049
|
+
##########################################################################
|
2050
|
+
# Helper-Tool: Prints all styles of content.xml in indented ASCII-notation
|
2051
|
+
# myHash.printAutoStyles()
|
2052
|
+
# * Lines starting with 'E' are Element-Tags
|
2053
|
+
# * Lines starting with 'A' are Attributes
|
2054
|
+
# * Lines starting with 'T' are Element-Text
|
2055
|
+
# Sample output:
|
2056
|
+
# E: number:date-style
|
2057
|
+
# A: style:name => "myDateFormat"
|
2058
|
+
# A: number:automatic-order => "true"
|
2059
|
+
# A: number:format-source => "language"
|
2060
|
+
# E: number:day
|
2061
|
+
# E: number:text
|
2062
|
+
# T: "."
|
2063
|
+
# E: number:month
|
2064
|
+
# E: number:text
|
2065
|
+
# T: "."
|
2066
|
+
# E: number:year
|
2067
|
+
#-------------------------------------------------------------------------
|
2068
|
+
def printAutoStyles()
|
2069
|
+
printStyles(@autoStyles," ")
|
2070
|
+
end
|
2071
|
+
##########################################################################
|
2072
|
+
# internal: Helper-Tool: Prints out all styles of given node in an indented ASCII-notation
|
2073
|
+
#------------------------------------------------------------------------
|
2074
|
+
def printStyles(start,indent)
|
2075
|
+
start.elements.each("*"){ |element|
|
2076
|
+
#------------------------------------------
|
2077
|
+
# Tag extrahieren (Standard-Tag-Zeichen nach '<')
|
2078
|
+
#------------------------------------------
|
2079
|
+
# puts("Element: #{element}")
|
2080
|
+
element.to_s.match(/<\s*([A-Za-z:-]+)/)
|
2081
|
+
puts("#{indent}E: #{$1}")
|
2082
|
+
#------------------------------------------
|
2083
|
+
# Attribute ausgeben
|
2084
|
+
#------------------------------------------
|
2085
|
+
element.attributes.each{ |attribute, value|
|
2086
|
+
puts(" #{indent}A: #{attribute} => \"#{value}\"")
|
2087
|
+
}
|
2088
|
+
#------------------------------------------
|
2089
|
+
# Text
|
2090
|
+
#------------------------------------------
|
2091
|
+
if(element.has_text?())
|
2092
|
+
puts(" #{indent}T: \"#{element.text}\"")
|
2093
|
+
end
|
2094
|
+
#------------------------------------------
|
2095
|
+
# Rekursion
|
2096
|
+
#------------------------------------------
|
2097
|
+
if(element.has_elements?())
|
2098
|
+
printStyles(element,indent+" ")
|
2099
|
+
end
|
2100
|
+
}
|
2101
|
+
end
|
2102
|
+
##########################################################################
|
2103
|
+
# internal: Recursively writes an XML-tree out of the given hash and returns
|
2104
|
+
# the written node. The returned node is irrelevant for the recursion but
|
2105
|
+
# valid for saving the node in a hash-pool for later style-comparisons.
|
2106
|
+
#------------------------------------------------------------------------
|
2107
|
+
def writeXml(node,treeHash)
|
2108
|
+
die("writeXml: Node #{node} is not a REXML::Element") unless (node.class.to_s == "REXML::Element")
|
2109
|
+
die("writeXml: Hash #{treeHash} is not a Hash") unless (treeHash.class.to_s == "Hash")
|
2110
|
+
tag=""
|
2111
|
+
text=""
|
2112
|
+
attributes=Hash.new()
|
2113
|
+
grandChildren=Hash.new()
|
2114
|
+
#------------------------------
|
2115
|
+
# Uebergabe-Hash analysieren und Wertelisten aufbauen
|
2116
|
+
#------------------------------
|
2117
|
+
treeHash.each{ |key,value|
|
2118
|
+
case key
|
2119
|
+
when TAG then tag=value
|
2120
|
+
when TEXT then text=value
|
2121
|
+
else
|
2122
|
+
if(key.match(/child/))
|
2123
|
+
die("writeXml: Hash #{value} for key #{key} is not a Hash") unless (value.class.to_s == "Hash")
|
2124
|
+
grandChildren[key]=value
|
2125
|
+
else
|
2126
|
+
die("writeXml: Hash-key #{key} is not a String") unless (key.class.to_s == "String")
|
2127
|
+
die("writeXml: Hash-Value #{value} for key #{key} is not a String") unless (value.class.to_s == "String")
|
2128
|
+
attributes[key]=value
|
2129
|
+
end
|
2130
|
+
end
|
2131
|
+
}
|
2132
|
+
#------------------------------
|
2133
|
+
# Kind-Element schreiben ...
|
2134
|
+
#------------------------------
|
2135
|
+
die("writeXml: Missing Tag for XML-Tree") unless (tag != "")
|
2136
|
+
child=node.add_element(tag,attributes)
|
2137
|
+
child.text=text unless (text == "")
|
2138
|
+
#------------------------------
|
2139
|
+
# ... und Enkel ebenfalls rekursiv schreiben
|
2140
|
+
#------------------------------
|
2141
|
+
grandChildren.each{ |key,hash|
|
2142
|
+
writeXml(child,hash) # hash wurde oben bereits als Typ Hash verifiziert
|
2143
|
+
}
|
2144
|
+
return child
|
2145
|
+
end
|
2146
|
+
##########################################################################
|
2147
|
+
# internal: Convert given formula to internal representation.
|
2148
|
+
# Example: "=E6-E5+0,27" => "of:=[.E6]+[.E5]+0.27"
|
2149
|
+
#------------------------------------------------------------------------
|
2150
|
+
def internalizeFormula(formulaIn)
|
2151
|
+
if(!formulaIn.match(/^=/))
|
2152
|
+
die("internalizeFormula: Formula #{formulaIn} does not begin with \'=\'")
|
2153
|
+
end
|
2154
|
+
formulaOut=String.new(formulaIn)
|
2155
|
+
#---------------------------------------------
|
2156
|
+
# Praefix setzen
|
2157
|
+
#---------------------------------------------
|
2158
|
+
formulaOut.sub!(/^=/,"of:=")
|
2159
|
+
#---------------------------------------------
|
2160
|
+
# Dezimaltrennzeichen ',' durch '.' in Zahlen ersetzen
|
2161
|
+
#---------------------------------------------
|
2162
|
+
formulaOut.gsub!(/(\d),(\d)/,"\\1.\\2")
|
2163
|
+
#---------------------------------------------
|
2164
|
+
# Zellbezeichnerformat AABC3421 in [.AABC3421] wandeln
|
2165
|
+
#---------------------------------------------
|
2166
|
+
formulaOut.gsub!(/([A-Za-z]+\d+)/,"[.\\1]")
|
2167
|
+
tell("internalizeFormula: #{formulaIn} -> #{formulaOut}")
|
2168
|
+
return formulaOut
|
2169
|
+
end
|
2170
|
+
##########################################################################
|
2171
|
+
# Applies style of given name to given cell and overwrites all previous style-settings
|
2172
|
+
# of the latter including the former data-style !
|
2173
|
+
# mySheet.writeStyleAbbr({"name" => "myStrange",
|
2174
|
+
# "text-align" => "right",
|
2175
|
+
# "data-style-name" => "myCurrencyFormat" <- don't forget data-style !
|
2176
|
+
# "border-left" => "0.01cm solid grey4"})
|
2177
|
+
# mySheet.setStyle(cell,"myStrange") # <- style-name has to exist !
|
2178
|
+
#-------------------------------------------------------------------------
|
2179
|
+
def setStyle(cell,styleName)
|
2180
|
+
#-----------------------------------------------------------------------
|
2181
|
+
# Ist Style gueltig, d.h. in content.xml vorhanden ?
|
2182
|
+
#-----------------------------------------------------------------------
|
2183
|
+
die("setStyle: style \'#{styleName}\' does not exist") unless (@autoStyles.elements["*[@style:name = '#{styleName}']"])
|
2184
|
+
cell.attributes['table:style-name']=styleName
|
2185
|
+
end
|
2186
|
+
##########################################################################
|
2187
|
+
# Inserts an annotation field for the given cell.
|
2188
|
+
# Caveat: When you make the annotation permanently visible in a subsequent
|
2189
|
+
# OpenOffice.org-session, the annotation will always be displayed in the upper
|
2190
|
+
# left corner of the sheet. The temporary display of the annotation is not
|
2191
|
+
# affected however.
|
2192
|
+
# mySheet.writeComment(cell,"by Dr. Heinz Breinlinger (who else)")
|
2193
|
+
#------------------------------------------------------------------------
|
2194
|
+
def writeComment(cell,comment)
|
2195
|
+
die("writeComment: cell #{cell} is not a REXML::Element") unless (cell.class.to_s == "REXML::Element")
|
2196
|
+
die("writeComment: comment #{comment} is not a string") unless (comment.class.to_s == "String")
|
2197
|
+
#--------------------------------------------
|
2198
|
+
# Ggf. alten Kommentar loeschen
|
2199
|
+
#--------------------------------------------
|
2200
|
+
cell.elements.delete("office:annotation")
|
2201
|
+
writeXml(cell,{TAG => "office:annotation",
|
2202
|
+
"svg:x" => "4.119cm",
|
2203
|
+
"draw:caption-point-x" => "-0.61cm",
|
2204
|
+
"svg:y" => "0cm",
|
2205
|
+
"draw:caption-point-y" => "0.011cm",
|
2206
|
+
"draw:text-style-name" => "myCommentParagraph",
|
2207
|
+
"svg:height" => "0.596cm",
|
2208
|
+
"draw:style-name" => "myCommentGraphics",
|
2209
|
+
"svg:width" => "2.899cm",
|
2210
|
+
"child1" => {TAG => "dc:date",
|
2211
|
+
TEXT => "2010-01-01T00:00:00"
|
2212
|
+
},
|
2213
|
+
"child2" => {TAG => "text:p",
|
2214
|
+
"text:style-name" => "myCommentParagraph",
|
2215
|
+
TEXT => comment
|
2216
|
+
}
|
2217
|
+
})
|
2218
|
+
end
|
2219
|
+
##########################################################################
|
2220
|
+
# internal: Helper-tool to extract a large amount of color-values and help
|
2221
|
+
# build a color-lookup-table.
|
2222
|
+
#-------------------------------------------------------------------------
|
2223
|
+
def getColorPalette()
|
2224
|
+
#------------------------------------------------
|
2225
|
+
# Automatic-Styles aus content.xml
|
2226
|
+
#------------------------------------------------
|
2227
|
+
myStyles=@contentText.elements["/office:document-content/office:automatic-styles"]
|
2228
|
+
currentTable=@tables[@currentTableName][NODE]
|
2229
|
+
currentTable.elements.each("//table:table-cell"){ |cell|
|
2230
|
+
textElement=cell.elements["text:p"]
|
2231
|
+
#-----------------------------
|
2232
|
+
# Zelle mit Text ?
|
2233
|
+
#-----------------------------
|
2234
|
+
if(textElement)
|
2235
|
+
text=textElement.text
|
2236
|
+
#-------------------------------
|
2237
|
+
# Ist Zelle Style zugewiesen ?
|
2238
|
+
#-------------------------------
|
2239
|
+
styleName=cell.attributes['table:style-name']
|
2240
|
+
if(styleName)
|
2241
|
+
#-------------------------------------
|
2242
|
+
# Style vorhanden ?
|
2243
|
+
#-------------------------------------
|
2244
|
+
style=myStyles.elements["style:style[@style:name = '#{styleName}']"]
|
2245
|
+
die("Could not find style #{styleName}") unless (style)
|
2246
|
+
#-------------------------------------
|
2247
|
+
# Properties-Element ebenfalls vorhanden ?
|
2248
|
+
#-------------------------------------
|
2249
|
+
properties=style.elements["style:table-cell-properties"]
|
2250
|
+
die("Could not find table-cell-properties for #{styleName}") unless (properties)
|
2251
|
+
#-------------------------------------
|
2252
|
+
# Nun noch Hintergrundfarbe extrahieren
|
2253
|
+
#-------------------------------------
|
2254
|
+
hexColor=properties.attributes["fo:background-color"]
|
2255
|
+
puts("\"#{text}\" => \"#{hexColor}\",")
|
2256
|
+
end
|
2257
|
+
end
|
2258
|
+
}
|
2259
|
+
end
|
2260
|
+
##########################################################################
|
2261
|
+
# Saves the file associated with the current RODS-object.
|
2262
|
+
# mySheet.save()
|
2263
|
+
#-------------------------------------------------------------------------
|
2264
|
+
def save()
|
2265
|
+
die("save: internal error: @myFile is not set -> cannot save file") unless (@myFile && (! @myFile.empty?))
|
2266
|
+
die("save: this should not happen: file #{@myFile} is missing") unless (File.exists?(@myFile))
|
2267
|
+
tell("save: saving as file #{@myFile}")
|
2268
|
+
Zip::ZipFile.open(@myFile){ |zipfile|
|
2269
|
+
finalize(zipfile)
|
2270
|
+
}
|
2271
|
+
end
|
2272
|
+
##########################################################################
|
2273
|
+
# Saves the current content to a new destination/file.
|
2274
|
+
# Caveat: Thumbnails are not created (these are normally part of the *.ods-zip-file).
|
2275
|
+
# mySheet.saveAs("/home/heinz/Work/Example.ods")
|
2276
|
+
#-------------------------------------------------------------------------
|
2277
|
+
def saveAs(newFile)
|
2278
|
+
die("saveAs: file #{newFile} does not have valid ending '*.ods'") unless (newFile.match(/\.ods$/))
|
2279
|
+
if(File.exists?(newFile))
|
2280
|
+
tell("saveAs: file #{newFile} exists -> deleting")
|
2281
|
+
File.delete(newFile)
|
2282
|
+
end
|
2283
|
+
#--------------------------------------------------------
|
2284
|
+
# Datei anlegen
|
2285
|
+
#--------------------------------------------------------
|
2286
|
+
tell("saveAs: saving as file #{newFile}")
|
2287
|
+
Zip::ZipFile.open(newFile,true){ |zipfile|
|
2288
|
+
["Configurations2","META-INF","Thumbnails"].each{ |dir|
|
2289
|
+
zipfile.mkdir(dir)
|
2290
|
+
zipfile.file.chmod(0755,dir)
|
2291
|
+
}
|
2292
|
+
["accelerator","floater","images","menubar","popupmenu","progressbar","statusbar","toolbar"].each{ |dir|
|
2293
|
+
subDir="Configurations2/"+dir
|
2294
|
+
zipfile.mkdir(subDir)
|
2295
|
+
zipfile.file.chmod(0755,subDir)
|
2296
|
+
}
|
2297
|
+
finalize(zipfile)
|
2298
|
+
}
|
2299
|
+
end
|
2300
|
+
##########################################################################
|
2301
|
+
# Constructor: The given file has to have a *.ods-ending
|
2302
|
+
#
|
2303
|
+
# mySheet=Rods.new("/home/heinz/Work/Template.ods")
|
2304
|
+
# mySheet=Rods.new("/home/heinz/Work/Template.ods",["de,"DE","€","EUR"])
|
2305
|
+
# mySheet=Rods.new("/home/heinz/Work/Another.ods",["us","US","$","DOLLAR"])
|
2306
|
+
#
|
2307
|
+
# "de","DE","€","EUR" are the default-settings for the language, country,
|
2308
|
+
# external and internal currency-symbol. All these values merely affect
|
2309
|
+
# currency-values and annotations (the latter though not visibly).
|
2310
|
+
#-------------------------------------------------------------------------
|
2311
|
+
def initialize(file,languageArray=["de","DE","€","EUR"])
|
2312
|
+
die("Contructor: second parameter is not an array") unless(languageArray.class.to_s == "Array")
|
2313
|
+
die("Contructor: wrong size of languageArray ... expected 4") unless(languageArray.size == 4)
|
2314
|
+
languageArray.each{ |element|
|
2315
|
+
die("Constructor: element #{element} is not a string") unless (element.class.to_s == "String")
|
2316
|
+
}
|
2317
|
+
@contentText
|
2318
|
+
@language=languageArray[0]
|
2319
|
+
@country=languageArray[1]
|
2320
|
+
@currencySymbol=languageArray[2]
|
2321
|
+
@currencySymbolInternal=languageArray[3]
|
2322
|
+
@spreadSheet
|
2323
|
+
@stylesText
|
2324
|
+
@metaText
|
2325
|
+
@officeMeta
|
2326
|
+
@manifestText
|
2327
|
+
@manifestRoot
|
2328
|
+
@settingsText
|
2329
|
+
@officeSettings
|
2330
|
+
@currentTableName # Name der aktuellen Tabelle
|
2331
|
+
@tables=Hash.new() # Hash der Tabellen und ihrer Eigenschaften
|
2332
|
+
@numTables # Anzahl der Tabellen
|
2333
|
+
@officeStyles
|
2334
|
+
@autoStyles
|
2335
|
+
@floatStyle="myFloat"
|
2336
|
+
@dateStyle="myDate"
|
2337
|
+
@stringStyle="myString"
|
2338
|
+
@currencyStyle="myCurrency"
|
2339
|
+
@percentStyle="myPercent"
|
2340
|
+
@timeStyle="myTime"
|
2341
|
+
@styleCounter=0
|
2342
|
+
@myFile # (ggf. qualifizierter) Dateiname der eingelesenen Datei
|
2343
|
+
#---------------------------------------------------------------
|
2344
|
+
# Hash-Tabelle der geschriebenen Styles
|
2345
|
+
#---------------------------------------------------------------
|
2346
|
+
@styleArchive=Hash.new()
|
2347
|
+
#---------------------------------------------------------------
|
2348
|
+
# Farbpalette
|
2349
|
+
#---------------------------------------------------------------
|
2350
|
+
@palette={"black" => "#000000",
|
2351
|
+
"blue" => "#000080",
|
2352
|
+
"green" => "#008000",
|
2353
|
+
"turquoise" => "#008080",
|
2354
|
+
"red" => "#800000",
|
2355
|
+
"magenta" => "#800080",
|
2356
|
+
"brown" => "#808000",
|
2357
|
+
"grey" => "#808080",
|
2358
|
+
"lightgrey" => "#c0c0c0",
|
2359
|
+
"lightblue" => "#0000ff",
|
2360
|
+
"lightgreen" => "#00ff00",
|
2361
|
+
"lightturquoise" => "#00ffff",
|
2362
|
+
"lightred" => "#ff0000",
|
2363
|
+
"lightmagenta" => "#ff00ff",
|
2364
|
+
"yellow" => "#ffff00",
|
2365
|
+
"white" => "#ffffff",
|
2366
|
+
"grey30" => "#b3b3b3",
|
2367
|
+
"grey20" => "#cccccc",
|
2368
|
+
"grey10" => "#e6e6e6",
|
2369
|
+
"red1" => "#ff3366",
|
2370
|
+
"red2" => "#dc2300",
|
2371
|
+
"red3" => "#b84700",
|
2372
|
+
"red4" => "#ff3333",
|
2373
|
+
"red5" => "#eb613d",
|
2374
|
+
"red6" => "#b84747",
|
2375
|
+
"red7" => "#b80047",
|
2376
|
+
"red8" => "#99284c",
|
2377
|
+
"magenta1" => "#94006b",
|
2378
|
+
"magenta2" => "#94476b",
|
2379
|
+
"magenta3" => "#944794",
|
2380
|
+
"magenta4" => "#9966cc",
|
2381
|
+
"magenta5" => "#6b4794",
|
2382
|
+
"magenta6" => "#6b2394",
|
2383
|
+
"magenta7" => "#6b0094",
|
2384
|
+
"magenta8" => "#5e11a6",
|
2385
|
+
"blue1" => "#280099",
|
2386
|
+
"blue2" => "#4700b8",
|
2387
|
+
"blue3" => "#2300dc",
|
2388
|
+
"blue4" => "#2323dc",
|
2389
|
+
"blue5" => "#0047ff",
|
2390
|
+
"blue6" => "#0099ff",
|
2391
|
+
"blue7" => "#00b8ff",
|
2392
|
+
"blue8" => "#99ccff",
|
2393
|
+
"turquoise1" => "#00dcff",
|
2394
|
+
"turquoise2" => "#00cccc",
|
2395
|
+
"turquoise3" => "#23b8dc",
|
2396
|
+
"turquoise4" => "#47b8b8",
|
2397
|
+
"turquoise5" => "#33a3a3",
|
2398
|
+
"turquoise6" => "#198a8a",
|
2399
|
+
"turquoise7" => "#006b6b",
|
2400
|
+
"turquoise8" => "#004a4a",
|
2401
|
+
"green1" => "#355e00",
|
2402
|
+
"green2" => "#5c8526",
|
2403
|
+
"green3" => "#7da647",
|
2404
|
+
"green4" => "#94bd5e",
|
2405
|
+
"green5" => "#00ae00",
|
2406
|
+
"green6" => "#33cc66",
|
2407
|
+
"yellow1" => "#e6ff00",
|
2408
|
+
"yellow2" => "#ffff99",
|
2409
|
+
"yellow3" => "#ffff66",
|
2410
|
+
"yellow4" => "#e6e64c",
|
2411
|
+
"yellow5" => "#cccc00",
|
2412
|
+
"yellow6" => "#b3b300",
|
2413
|
+
"yellow7" => "#808019",
|
2414
|
+
"yellow8" => "#666600",
|
2415
|
+
"brown1" => "#4c1900",
|
2416
|
+
"brown2" => "#663300",
|
2417
|
+
"brown3" => "#804c19",
|
2418
|
+
"brown4" => "#996633",
|
2419
|
+
"orange1" => "#cc6633",
|
2420
|
+
"orange2" => "#ff6633",
|
2421
|
+
"orange3" => "#ff9966",
|
2422
|
+
"orange4" => "#ffcc99",
|
2423
|
+
"purple" => "#9999ff",
|
2424
|
+
"bordeaux" => "#993366",
|
2425
|
+
"paleyellow" => "#ffffcc",
|
2426
|
+
"palegreen" => "#ccffff",
|
2427
|
+
"darkpurple" => "#660066",
|
2428
|
+
"salmon" => "#ff8080"
|
2429
|
+
}
|
2430
|
+
@fixedStyles=["myTable", "myRow", "myColumn", "myFloatFormat", "myFloat", "myTimeFormat",
|
2431
|
+
"myTime", "myPercentFormat", "myPercent", "myString", "myDateFormat",
|
2432
|
+
"myDate", "myDateFormatDay", "myDateDay", "myCurrencyFormatPositive",
|
2433
|
+
"myCurrencyFormat", "myCurrency", "myCommentParagraph", "myCommentText",
|
2434
|
+
"myCommentGraphics"]
|
2435
|
+
open(file)
|
2436
|
+
end
|
2437
|
+
##########################################################################
|
2438
|
+
# Helper-function: Print palette of implemented color-mappings
|
2439
|
+
# myHash.printColorMap()
|
2440
|
+
# generates ouput like ...
|
2441
|
+
# "lightturquoise" => "#00ffff",
|
2442
|
+
# "lightred" => "#ff0000",
|
2443
|
+
# "lightmagenta" => "#ff00ff",
|
2444
|
+
# "yellow" => "#ffff00",
|
2445
|
+
# you can use for 'setAttributes' and 'writeStyleAbbr'.
|
2446
|
+
#-------------------------------------------------------------------------
|
2447
|
+
def printColorMap()
|
2448
|
+
puts("printColorMap: convenience color-mappings")
|
2449
|
+
puts("-----------------------------------------")
|
2450
|
+
@palette.each{ |key,value|
|
2451
|
+
puts(" #{key} -> #{value}")
|
2452
|
+
}
|
2453
|
+
puts("You can use the convenience keys in 'setAttribute' and 'writeStyleAbbr'")
|
2454
|
+
puts("for the attributes")
|
2455
|
+
puts(" border,border-bottom, border-top, border-left, border-right")
|
2456
|
+
puts(" background-color")
|
2457
|
+
puts(" color")
|
2458
|
+
end
|
2459
|
+
##########################################################################
|
2460
|
+
# Oeffnet zip-Datei und erzeugt diese, wenn nicht existent
|
2461
|
+
#-------------------------------------------------------------------------
|
2462
|
+
def open(file)
|
2463
|
+
die("open: file #{file} does not have valid ending '*.ods'") unless (file.match(/\.ods$/))
|
2464
|
+
if(File.exists?(file))
|
2465
|
+
tell("open: found file #{file}")
|
2466
|
+
Zip::ZipFile.open(file){ |zipfile|
|
2467
|
+
init(zipfile)
|
2468
|
+
}
|
2469
|
+
@myFile=file
|
2470
|
+
else
|
2471
|
+
die("open: file #{file} does not exist")
|
2472
|
+
end
|
2473
|
+
end
|
2474
|
+
#-------------------------------------------------------------------------
|
2475
|
+
public :setDateFormat, :writeGetCell, :writeCell, :writeGetCellFromRow, :writeCellFromRow,
|
2476
|
+
:getCellFromRow, :getCell, :getRow, :renameTable, :setCurrentTable,
|
2477
|
+
:insertTable, :deleteTable, :readCellFromRow, :readCell, :setAttributes, :writeStyleAbbr,
|
2478
|
+
:setStyle, :printOfficeStyles, :printAutoStyles,
|
2479
|
+
:writeComment, :save, :saveAs, :initialize, :writeText
|
2480
|
+
|
2481
|
+
private :tell, :die, :createCell, :createRow, :getChildByIndex, :createElement, :setRepetition, :initHousekeeping,
|
2482
|
+
:getTableWidth, :padTable, :padRow, :time2TimeVal, :percent2PercentVal, :date2DateVal,
|
2483
|
+
:finalize, :init, :normalizeText, :getColor, :normStyleHash, :getStyle,
|
2484
|
+
:getAppropriateStyle, :checkStyleAttributes, :insertStyleAttributes, :cloneNode,
|
2485
|
+
:writeStyle, :writeStyleXml, :style2Hash, :writeDefaultStyles, :writeXml,
|
2486
|
+
:internalizeFormula, :getColorPalette, :open, :printStyles
|
2487
|
+
end # Klassenende
|