rods 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/Manifest +5 -0
  2. data/README +269 -0
  3. data/Rakefile +11 -0
  4. data/lib/example.rb +88 -0
  5. data/lib/rods.rb +2487 -0
  6. data/rods.gemspec +30 -0
  7. metadata +80 -0
@@ -0,0 +1,5 @@
1
+ Manifest
2
+ README
3
+ Rakefile
4
+ lib/example.rb
5
+ lib/rods.rb
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
+
@@ -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
@@ -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
@@ -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