rods 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|