rods 0.0.1

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