rspreadsheet 0.2.15 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8c4d7239e35070d3406af3f5c7aadb872d5c0038
4
- data.tar.gz: e06d7243078d7531984714f29ed403ce62fcc9df
3
+ metadata.gz: b7c4756f4b74164e9aeafb35af79f923aff8a5fa
4
+ data.tar.gz: 04814801b5e23bd0d4077ffbe39a65fbc7401d67
5
5
  SHA512:
6
- metadata.gz: dfe7532c19164f3f96611793df3eb75e2c5d67c9c3763cfaee72e88f1e060ec54d9c0978c3e59183520ddc05f0ab5a88d09f8e5e03d15fc4397de5a78740c81f
7
- data.tar.gz: f84bbe8045dfe515ac18e50df5a7482e7e6ba0282b57ba2f1ffa50991d3aec3b45a60c7080d7183a867a3839bd7f639a801b0b7e90b9d7aec9a1a59ab1a5fa34
6
+ metadata.gz: e9e8fd002a0fdd21bd453b62088df71ece09d107eb6b6b8217f6805e989e0c18d63d376d8e7c338b2b8c292f7d46deec8dab8b161372a59978b10b33990de417
7
+ data.tar.gz: 59db224cb0928ee5494f79583e6f340ecba729670688e39f0a64216647947a274022e73740f801bbe0e639984d7806d610484742805f469a2c8df0d033dfebaa
data/.gitignore CHANGED
@@ -43,4 +43,7 @@ Gemfile.lock
43
43
 
44
44
  # specific to the project
45
45
  /*.ods
46
- /spec/testfile1/
46
+ /spec/testfile1/
47
+
48
+ #temporary
49
+ /w
data/.travis.yml CHANGED
@@ -1,13 +1,14 @@
1
1
  language: ruby
2
2
  cache: bundler
3
3
  rvm:
4
- - 2.2.3
5
- - 2.2.0
6
- - 2.1.7
7
4
  - 2.0.0
8
- - 1.9.3
5
+ - 2.1.10
6
+ - 2.2.6
7
+ - 2.3.3
8
+ - 2.4.0
9
9
  script: 'bundle exec rake'
10
10
  before_install:
11
+ - gem uninstall libxml-ruby
11
12
  - gem update bundler
12
13
  notifications:
13
14
  email:
data/DEVEL_BLOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  See [GUIDE.md](GUIDE.md#conventions) for syntax conventions.
2
2
 
3
+ ## Installation troubleshooting
4
+
5
+ After you have cloned the source run `bundle` command in gem directory to install needed tools. If there are any errors try to
6
+
7
+ 1. Make sure that necessary tools are installed. In debian based distros you may try this `apt-get install make gcc`. In other distros, use their native way to install tools.
8
+ 2. If you have installed `ruby-libxml` than you may want to comment out the line containing libxml-ruby dependency in rspreadseet.gemspec file before running bundle.
9
+ 3. Comment out `guard` and `guard-rspec` lines from gemfile if you use ruby version less than 2.2.1.
10
+
3
11
  ## Ideas/wishlist
4
12
 
5
13
  * In future inntroduce syntax like `sheet.range('C3:E4')` for mass operations.
@@ -13,6 +21,8 @@ See [GUIDE.md](GUIDE.md#conventions) for syntax conventions.
13
21
  * implement to_csv
14
22
  * longterm plan - go through other used libraries and try to find out whose syntax could be adopted, so this library is drop in replacement (possibly with some config options) for them
15
23
  * iterative generation like this
24
+ * Currently the way you insert cells and rows is that you insert empty one and than copy values. It would be interesting to add "duplicate" method as well as other "copy pasting" stuff, which would be more forward compatible.
25
+ * Optionally allow negative coordinates count from end
16
26
 
17
27
  ```ruby
18
28
  RSpreadsheet.generate('pricelist.ods') do
@@ -53,7 +63,7 @@ RSpreadsheet.generate('pricelist.ods') do
53
63
  * array returned by rows of cells can have predefined dummy object as defaults for out of range indexes
54
64
 
55
65
 
56
- ##Guiding ideas
66
+ ## Guiding ideas
57
67
  * xml document is always synchronized with the data. So the save is trivial.
58
68
  * no duplication of data. Objects like RowArray should containg minimum information. This one exists solely to speed up cell search. Taken to extream it is questionable, whether we need such objects at all, it might be possible to always work with xml directly.
59
69
  * all cells and rows only server as proxy. they hold index and worksheet pointer and everytime read or write is done, the xml is newly searched. until there is a xmlnode caching we have no problem
@@ -62,6 +72,12 @@ RSpreadsheet.generate('pricelist.ods') do
62
72
  ## Known Issues
63
73
  * currently there is a confusing syntax @worksheet.rows(1).cells[5] which returns a cell object in SIXT column, which is not intended. It is side effecto of Row#cells returning an array
64
74
 
75
+ ## Release notes
76
+
77
+ 2017-01
78
+ * basic image handling implemented (issue [#24](https://github.com/gorn/rspreadsheet/issues/24))
79
+ * bug corrected: inserted row was not empty, but rather copy of the row below.
80
+
65
81
  ## Developing this gem
66
82
 
67
83
  ### Automated testing
@@ -84,7 +100,7 @@ RSpreadsheet.generate('pricelist.ods') do
84
100
  5. When happy, increment the version number and `git add .; git commit -am'commit message'; git push`
85
101
  6. ``rake release`` - creates a version tag in git and pushes the code to github + Rubygems. After this is succesfull the new version appears as release in Github and RubyGems.
86
102
 
87
- gem alternativa to points 3-6
103
+ gem alternative to points 3-6
88
104
 
89
105
  gem build rspreadsheet.gemspec -\ These two lines together are in install.sh
90
106
  sudo gem install rspreadsheet-x.y.z.gem -/ which should be invoked from parent directory
data/GUIDE.md CHANGED
@@ -28,6 +28,11 @@ You can mix these two at will, for example like this
28
28
  @row[4] = 10
29
29
  ````
30
30
 
31
+ ### Working with images
32
+ @sheet.insert_image_to('10.21mm','15mm','image.png')
33
+ i = @sheet.images.first
34
+ i.move_to('100mm','99.98mm')
35
+
31
36
  ## Examples
32
37
 
33
38
  * [basic functionality](https://gist.github.com/gorn/42e33d086d9b4fda10ec)
data/Guardfile CHANGED
@@ -13,13 +13,13 @@ end
13
13
  # classical part
14
14
  scope group: :normal
15
15
 
16
- group :normal do
17
- guard 'rspec' do watch_all end
16
+ group :normal, cmd: "bundle exec rspec" do
17
+ guard 'rspec', cmd: 'bundle exec rspec' do watch_all end
18
18
  end
19
19
 
20
20
  # see http://stackoverflow.com/questions/18501471/guard-how-to-run-specific-tags-from-w-in-guards-console
21
21
  group :focus do
22
- guard 'rspec', cli: '--tag focus' do watch_all end
22
+ guard 'rspec', cmd: 'bundle exec rspec --tag focus' do watch_all end
23
23
  end
24
24
 
25
25
  #group :f do
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
+ [![Build Status](https://api.travis-ci.org/gorn/rspreadsheet.svg?branch=master)](https://travis-ci.org/gorn/rspreadsheet) [![Coverage Status](https://coveralls.io/repos/gorn/rspreadsheet/badge.svg?branch=master&service=github)](https://coveralls.io/r/gorn/rspreadsheet) [![User opinion wanted](https://badge.waffle.io/gorn/rspreadsheet.png?label=user opinion wanted&title=User opinion wanted)](https://waffle.io/gorn/rspreadsheet)
1
2
 
2
- [![Build Status](https://api.travis-ci.org/gorn/rspreadsheet.svg?branch=master)](https://travis-ci.org/gorn/rspreadsheet) [![Coverage Status](https://coveralls.io/repos/gorn/rspreadsheet/badge.png)](https://coveralls.io/r/gorn/rspreadsheet) [![User opinion wanted](https://badge.waffle.io/gorn/rspreadsheet.png?label=user opinion wanted&title=User opinion wanted)](https://waffle.io/gorn/rspreadsheet)
3
3
  # rspreadsheet
4
4
 
5
5
  Manipulating spreadsheets with Ruby. Read, **modify**, write or create new OpenDocument Spreadsheet files from ruby code.
@@ -32,7 +32,10 @@ sheet.row(5).cellvalues.sum
32
32
  sheet.row(5).cells.sum{ |cell| cell.value.to_f }
33
33
 
34
34
  # or set formula to a cell
35
- sheet.A1.formula='=SUM(A2:A9)'
35
+ sheet.cell('A1').formula='=SUM(A2:A9)'
36
+
37
+ # insert company logo to the file
38
+ sheet.insert_image_to('10mm','15mm','company_logo.png')
36
39
 
37
40
  # iterating over list of people and displaying the data
38
41
  total = 0
@@ -47,11 +50,8 @@ book.save
47
50
  book.save('different_filename.ods')
48
51
  ```
49
52
 
50
- This is also pubished as Gist **where you can leave you comments and suggestions**:
51
-
52
- * [basic functionality](https://gist.github.com/gorn/42e33d086d9b4fda10ec)
53
- * [extended examples](https://gist.github.com/gorn/b432e6a69e82628349e6) of lots of alternative syntax
54
- * [GUIDE.md](GUIDE.md) some other notes
53
+ * [More examples](https://gist.github.com/gorn/b432e6a69e82628349e6) of lots of alternative syntax **you can leave you comments and suggestions there**
54
+ * [Guide to using Rspreadsheet](GUIDE.md) some other notes
55
55
 
56
56
  ## Installation and Configuration
57
57
 
@@ -76,12 +76,12 @@ Alhought this gem is still in beta stage I use in everyday and it works fine. Cu
76
76
 
77
77
  ### Programmers
78
78
 
79
- 1. [Fork it](http://github.com/gorn/rspreadsheet/fork)
79
+ 1. [Fork it](http://github.com/gorn/rspreadsheet/fork) and clone it ([troubleshooting](https://github.com/gorn/rspreadsheet/blob/master/DEVEL_BLOG.md#installation-troubleshooting))
80
80
  2. Create your feature branch (`git checkout -b my-new-feature`)
81
81
  3. Commit your changes (`git commit -am 'Add some feature'`)
82
82
  4. Push to the branch (`git push origin my-new-feature`)
83
83
  5. Create new Pull Request
84
-
84
+
85
85
  ## Why another OpenDocument spreadsheet gem?
86
86
 
87
87
  I would be glad to safe myself work, but surprisingly, there are not that many gems for OpenDocument spreadsheets. Most of them also look abandoned and inactive, or can only read or write spreadsheets, but not modify them. I have investigated these options (you might as well):
@@ -1,49 +1,112 @@
1
- # @private
1
+ if RUBY_VERSION > '2.1'
2
2
 
3
- class LibXML::XML::Node
4
- def elements
5
- result = []
6
- each_element { |e| result << e }
7
- return result
8
- end
9
- # if node2 contains at least all that I do
10
- def simplification_of?(node2)
11
- first_diff(node2).nil?
12
- end
13
- # return first difference where self has something more than node2 does
14
- def first_diff(node2)
15
- where = self.path.split('/').last
3
+ module ClassExtensions
4
+
5
+ refine Array do
6
+ def sum(identity = 0, &block)
7
+ if block_given?
8
+ map(&block).sum(identity)
9
+ else
10
+ inject(0){ |sum, element| sum.to_f + element.to_f } || identity
11
+ end
12
+ end
13
+ end
16
14
 
17
- return "#{where}> Equivalent node does not exist: #{self.name} != NOTHING" if node2.nil?
18
- return "#{where}> Names are different: #{self.name} != #{node2.name}" if (self.name != node2.name)
19
- self.attributes.each do |attr|
20
- return "#{where}> Attribute #{attr} have diffent values: #{attr.value} != #{node2.attributes[attr.name]}" unless node2.attributes[attr.name] == attr.value
15
+ refine LibXML::XML::Node do
16
+ def equals?(node2); raise 'nic' end
17
+ def ==(node2)
18
+ self.simplification_of?(node2) and node2.simplification_of?(self)
19
+ end
20
+ # if node2 contains at least all that I do
21
+ def simplification_of?(node2)
22
+ first_diff(node2).nil?
23
+ end
24
+ # return first difference where self has something more than node2 does
25
+ def first_diff(node2)
26
+ where = self.path.split('/').last
27
+
28
+ return "#{where}> Equivalent node does not exist: #{self.name} != NOTHING" if node2.nil?
29
+ return "#{where}> Names are different: #{self.name} != #{node2.name}" if (self.name != node2.name)
30
+ self.attributes.each do |attr|
31
+ return "#{where}> Attribute #{attr} have diffent values: #{attr.value} != #{node2.attributes[attr.name]}" unless node2.attributes[attr.name] == attr.value
32
+ end
33
+
34
+ elems1 = self.elements
35
+ elems2 = node2.elements
36
+ return "#{where}> elements have different number of subelements #{elems1.length} != #{elems2.length}" if (elems1.length != elems2.length)
37
+
38
+ elems1.each_index do |i|
39
+ raise "Nil for i=#{i}" if elems1[i].nil?
40
+ if (elems1[i].node_type_name == 'text')
41
+ if elems2[i].nil? || (elems1[i].to_s) != (elems2[i].to_s)
42
+ return "#{where}> #{i+1}th text subelements are different: #{elems1[i].to_s} != #{elems2[i].to_s}"
43
+ end
44
+ elsif (elems1[i].node_type_name == 'element')
45
+ unless elems1[i].simplification_of?(elems2[i])
46
+ return "#{where}/[#{i+1}]#{elems1[i].first_diff(elems2[i])}"
47
+ end
48
+ end
49
+ end
50
+
51
+ return nil
52
+ end
53
+ def elements
54
+ result = []
55
+ each_element { |e| result << e }
56
+ return result
57
+ end
21
58
  end
22
59
 
23
- elems1 = self.elements
24
- elems2 = node2.elements
25
- # return "#{where}> elements have different number of subelements #{elems1.length} != #{elems2.length}" if (elems1.length != elems2.length)
26
- elems1.length.times do |i|
27
- if (elems1[i].node_type_name == 'text') && ((elems1[i].to_s != elems2[i].to_s) )
28
- return "#{where}> #{i+1}th text subelements are different: #{elems1[i].to_s} != #{elems2[i].to_s}"
29
- elsif (elems1[i].node_type_name == 'element') && (!elems1[i].simplification_of?(elems2[i]))
30
- return "#{where}/[#{i+1}]#{elems1[i].first_diff(elems2[i])}"
60
+ end # module ClassExtensions
61
+
62
+ else # Monkeypatching
63
+
64
+ class Array
65
+ def sum(identity = 0, &block)
66
+ if block_given?
67
+ map(&block).sum(identity)
68
+ else
69
+ inject(0){ |sum, element| sum.to_f + element.to_f } || identity
31
70
  end
32
71
  end
33
-
34
- return nil
35
- end
36
- def equals?(node2) #TODO redefine == with this
37
- self.simplification_of?(node2) and node2.simplification_of?(self)
38
72
  end
39
- end
40
73
 
41
- class Array
42
- def sum(identity = 0, &block)
43
- if block_given?
44
- map(&block).sum(identity)
45
- else
46
- inject(0){ |sum, element| sum.to_f + element.to_f } || identity
74
+ class LibXML::XML::Node
75
+ def ==(node2)
76
+ self.simplification_of?(node2) and node2.simplification_of?(self)
77
+ end
78
+ # if node2 contains at least all that I do
79
+ def simplification_of?(node2)
80
+ first_diff(node2).nil?
81
+ end
82
+ # return first difference where self has something more than node2 does
83
+ def first_diff(node2)
84
+ where = self.path.split('/').last
85
+
86
+ return "#{where}> Equivalent node does not exist: #{self.name} != NOTHING" if node2.nil?
87
+ return "#{where}> Names are different: #{self.name} != #{node2.name}" if (self.name != node2.name)
88
+ self.attributes.each do |attr|
89
+ return "#{where}> Attribute #{attr} have diffent values: #{attr.value} != #{node2.attributes[attr.name]}" unless node2.attributes[attr.name] == attr.value
90
+ end
91
+
92
+ elems1 = self.elements
93
+ elems2 = node2.elements
94
+ # return "#{where}> elements have different number of subelements #{elems1.length} != #{elems2.length}" if (elems1.length != elems2.length)
95
+ elems1.length.times do |i|
96
+ if (elems1[i].node_type_name == 'text') && ((elems1[i].to_s != elems2[i].to_s) )
97
+ return "#{where}> #{i+1}th text subelements are different: #{elems1[i].to_s} != #{elems2[i].to_s}"
98
+ elsif (elems1[i].node_type_name == 'element') && (!elems1[i].simplification_of?(elems2[i]))
99
+ return "#{where}/[#{i+1}]#{elems1[i].first_diff(elems2[i])}"
100
+ end
101
+ end
102
+
103
+ return nil
104
+ end
105
+ def elements
106
+ result = []
107
+ each_element { |e| result << e }
108
+ return result
47
109
  end
48
110
  end
111
+
49
112
  end
data/lib/rspreadsheet.rb CHANGED
@@ -1,8 +1,9 @@
1
1
  require 'rspreadsheet/version'
2
2
  require 'rspreadsheet/workbook'
3
3
  require 'rspreadsheet/worksheet'
4
- require 'helpers/class_extensions'
5
4
  require 'helpers/configuration'
5
+ # refinements
6
+ require 'helpers/class_extensions'
6
7
 
7
8
  module Rspreadsheet
8
9
  extend Configuration
@@ -3,12 +3,15 @@
3
3
  # @title rspreadsheet Cell
4
4
 
5
5
  require 'andand'
6
- require 'rspreadsheet/xml_tied'
6
+ require 'rspreadsheet/xml_tied_item'
7
7
  require 'date'
8
+ require 'time'
8
9
  require 'bigdecimal'
9
10
  require 'bigdecimal/util' # for to_d method
11
+ require 'helpers/class_extensions'
10
12
 
11
13
  module Rspreadsheet
14
+ using ClassExtensions if RUBY_VERSION > '2.1'
12
15
 
13
16
  ###
14
17
  # Represents a cell in spreadsheet which has coordinates, contains value, formula and can be formated.
@@ -17,30 +20,33 @@ module Rspreadsheet
17
20
  # @worksheet.cells(5,2)
18
21
  #
19
22
  # Note that when using syntax like `@worksheet[5,2]` or `@worksheet.B5` you won't get this object, but rather the value of the cell.
23
+ # More precisely it is equvalient to @worksheet.cells(5,2).value.
24
+ #
20
25
 
21
26
  class Cell < XMLTiedItem
22
- attr_accessor :worksheet, :coli, :rowi
23
- # `xml_options[:xml_items_node_name]` gives the name of the tag representing cell
24
- # `xml_options[:number-columns-repeated]` gives the name of the previous tag which sais how many times the item is repeated
25
- def xml_options; {:xml_items_node_name => 'table-cell', :xml_repeated_attribute => 'number-columns-repeated'} end
27
+ attr_accessor :worksheet
28
+ attr_reader :rowi
29
+ InternalDateFormat = '%Y-%m-%d'
30
+ InternalTimeFormat = 'PT%HH%MM%SS'
31
+ # InternalTimeFormat = 'PT%HH%MM%SS,%LS'
26
32
 
27
- ## defining abstract methods from XMLTiedItem
28
- # returns parent XMLTiedArray object of myself (XMLTiedItem)
33
+ # @!group XMLTiedItem related methods and extensions
34
+ def xml_options; {:xml_items_node_name => 'table-cell', :xml_repeated_attribute => 'number-columns-repeated'} end
29
35
  def parent; row end
30
- def index; @coli end
31
- def set_index(value); @coli=value end
36
+ def coli; index end
37
+
32
38
  def set_rowi(arowi); @rowi = arowi end # this should ONLY be used by parent row
33
39
  def initialize(aworksheet,arowi,acoli)
34
40
  raise "First parameter should be Worksheet object not #{aworksheet.class}" unless aworksheet.kind_of?(Rspreadsheet::Worksheet)
35
41
  @worksheet = aworksheet
36
42
  @rowi = arowi
37
- @coli = acoli
43
+ initialize_xml_tied_item(row,acoli)
38
44
  end
39
45
  def row; @worksheet.rows(rowi) end
40
46
  def coordinates; [rowi,coli] end
41
47
  def to_s; value.to_s end
42
48
  def valuexml; self.valuexmlnode.andand.inner_xml end
43
- def valuexmlnode; self.xmlnode.children.first end
49
+ def valuexmlnode; self.xmlnode.elements.first end
44
50
  # use this to find node in cell xml. ex. xmlfind('.//text:a') finds all link nodes
45
51
  def valuexmlfindall(path)
46
52
  valuexmlnode.nil? ? [] : valuexmlnode.find(path)
@@ -49,7 +55,7 @@ class Cell < XMLTiedItem
49
55
  valuexmlfindall(path).first
50
56
  end
51
57
  def inspect
52
- "#<Rspreadsheet::Cell:Cell\n row:#{rowi}, col:#{coli} address:#{address}\n type: #{guess_cell_type.to_s}, value:#{value}\n mode: #{mode}\n>"
58
+ "#<Rspreadsheet::Cell\n row:#{rowi}, col:#{coli} address:#{address}\n type: #{guess_cell_type.to_s}, value:#{value}\n mode: #{mode}, format: #{format.inspect}\n>"
53
59
  end
54
60
  def value
55
61
  gt = guess_cell_type
@@ -58,7 +64,8 @@ class Cell < XMLTiedItem
58
64
  when gt == nil then nil
59
65
  when gt == Float then xmlnode.attributes['value'].to_f
60
66
  when gt == String then xmlnode.elements.first.andand.content.to_s
61
- when gt == Date then Date.strptime(xmlnode.attributes['date-value'].to_s, '%Y-%m-%d')
67
+ when gt == Date then Date.strptime(xmlnode.attributes['date-value'].to_s, InternalDateFormat)
68
+ when gt == Time then Time.strptime(xmlnode.attributes['time-value'].to_s, InternalTimeFormat)
62
69
  when gt == :percentage then xmlnode.attributes['value'].to_f
63
70
  when gt == :currency then xmlnode.attributes['value'].to_d
64
71
  end
@@ -86,8 +93,14 @@ class Cell < XMLTiedItem
86
93
  when gt == Date then
87
94
  remove_all_value_attributes_and_content(xmlnode)
88
95
  set_type_attribute('date')
89
- Tools.set_ns_attribute(xmlnode,'office','date-value', avalue.strftime('%Y-%m-%d'))
90
- xmlnode << Tools.prepare_ns_node('text','p', avalue.strftime('%Y-%m-%d'))
96
+ avalue = avalue.strftime(InternalDateFormat)
97
+ Tools.set_ns_attribute(xmlnode,'office','date-value', avalue)
98
+ xmlnode << Tools.prepare_ns_node('text','p', avalue)
99
+ when gt == Time then
100
+ remove_all_value_attributes_and_content(xmlnode)
101
+ set_type_attribute('time')
102
+ Tools.set_ns_attribute(xmlnode,'office','time-value', avalue.strftime(InternalTimeFormat))
103
+ xmlnode << Tools.prepare_ns_node('text','p', avalue.strftime('%H:%M'))
91
104
  when gt == :percentage then
92
105
  remove_all_value_attributes_and_content(xmlnode)
93
106
  set_type_attribute('percentage')
@@ -105,6 +118,7 @@ class Cell < XMLTiedItem
105
118
  def remove_all_value_attributes_and_content(node=xmlnode)
106
119
  if att = Tools.get_ns_attribute(node, 'office','value') then att.remove! end
107
120
  if att = Tools.get_ns_attribute(node, 'office','date-value') then att.remove! end
121
+ if att = Tools.get_ns_attribute(node, 'office','time-value') then att.remove! end
108
122
  if att = Tools.get_ns_attribute(node, 'table','formula') then att.remove! end
109
123
  node.content=''
110
124
  end
@@ -120,6 +134,7 @@ class Cell < XMLTiedItem
120
134
  when gct == Float then :float
121
135
  when gct == String then :string
122
136
  when gct == Date then :date
137
+ when gct == Time then :time
123
138
  when gct == :percentage then :percentage
124
139
  when gct == :unassigned then :unassigned
125
140
  when gct == :currency then :currency
@@ -132,28 +147,29 @@ class Cell < XMLTiedItem
132
147
  # try guessing by value
133
148
  valueguess = case avalue
134
149
  when Numeric then Float
150
+ when Time then Time
135
151
  when Date then Date
136
152
  when String,nil then nil
137
153
  else nil
138
154
  end
139
155
  result = valueguess
140
156
 
141
- if valueguess.nil? # valueguess is most important
142
- # if not succesfull then try guessing by type from node xml
157
+ if valueguess.nil? # valueguess is most important if not succesfull then try guessing by type from node xml
143
158
  typ = xmlnode.nil? ? 'N/A' : xmlnode.attributes['value-type']
144
159
  typeguess = case typ
145
160
  when nil then nil
146
161
  when 'float' then Float
147
162
  when 'string' then String
163
+ when 'time' then Time
148
164
  when 'date' then Date
149
165
  when 'percentage' then :percentage
150
166
  when 'N/A' then :unassigned
151
167
  when 'currency' then :currency
152
168
  else
153
- if xmlnode.children.size == 0
169
+ if xmlnode.elements.size == 0
154
170
  nil
155
171
  else
156
- raise "Unknown type at #{coordinates.to_s} from #{xmlnode.to_s} / children size=#{xmlnode.children.size.to_s} / type=#{xmlnode.attributes['value-type'].to_s}"
172
+ raise "Unknown type at #{coordinates.to_s} from #{xmlnode.to_s} / elements size=#{xmlnode.elements.size.to_s} / type=#{xmlnode.attributes['value-type'].to_s}"
157
173
  end
158
174
  end
159
175
 
@@ -209,6 +225,11 @@ class Cell < XMLTiedItem
209
225
  Tools.set_ns_attribute(xmlnode,'table','formula','of:'+formulastring.to_s)
210
226
  end
211
227
  def blank?; self.type==:empty or self.type==:unassigned end
228
+
229
+ def border_top; format.border_top end
230
+ def border_right; format.border_right end
231
+ def border_bottom; format.border_bottom end
232
+ def border_left; format.border_left end
212
233
 
213
234
  end
214
235
 
@@ -249,6 +270,7 @@ class CellFormat
249
270
  end
250
271
  def background_color=(value); set_cell_style_node_attribute('background-color', value) end
251
272
  def set_cell_style_node_attribute(attribute_name,value)
273
+ @cell.detach if @cell.mode != :regular
252
274
  if cell_style_node.nil?
253
275
  self.create_cell_style_node
254
276
  raise 'Style node was not correctly initialized' if cell_style_node.nil?
@@ -300,10 +322,79 @@ class CellFormat
300
322
  def currency
301
323
  Tools.get_ns_attribute_value(cellnode,'office','currency',nil)
302
324
  end
325
+ #returns object representing top border of the cell
326
+ def top; @top ||= Border.new(self,:top) end
327
+ def bottom; @bottom ||= Border.new(self,:bottom) end
328
+ def left; @left ||= Border.new(self,:left) end
329
+ def right; @right ||= Border.new(self,:right) end
330
+ alias :border_top :top
331
+ alias :border_right :right
332
+ alias :border_bottom :bottom
333
+ alias :border_left :left
334
+
335
+ def inspect
336
+ "#<Rspreadsheet::CellFormat bold:#{bold?.inspect}, borders:#{top.get_value_string.inspect} #{right.get_value_string.inspect} #{bottom.get_value_string.inspect} #{left.get_value_string.inspect}>"
337
+ end
338
+
303
339
  end
304
340
 
341
+ # represents one of the borders of a cell
342
+ class Border
343
+ def initialize(cellformat,side)
344
+ @cellformat = cellformat
345
+ @side = side.to_s
346
+ raise "Wrong side of border object, can be top, bottom, left or right" unless ['left','right','top','bottom'].include? @side
347
+ end
348
+ def cellnode; @cell.xmlnode end
349
+ def attribute_name; "border-#{@side}" end
350
+
351
+ def width=(value); set_border_string_part(1, value) end
352
+ def style=(value); set_border_string_part(2, value.to_s) end
353
+ def color=(value); set_border_string_part(3, value) end
354
+ def width; get_border_string_part(1).to_f end
355
+ def style; get_border_string_part(2) end
356
+ def color; get_border_string_part(3) end
357
+ def delete
358
+ @cellformat.set_cell_style_node_attribute(attribute_name, 'none')
359
+ end
360
+
361
+
362
+ ## internals
363
+
364
+ # set parth-th part of string which represents the border. String looks like "0.06pt solid #00ee00"
365
+ # part is 1 for width, 2 for style or 3 for color
366
+ def set_border_string_part(part,value)
367
+ current_value = @cellformat.get_cell_style_node_attribute(attribute_name)
368
+
369
+ if current_value.nil? or (current_value=='none')
370
+ value_array = ['0.75pt', 'solid', '#000000'] # set default values
371
+ else
372
+ value_array = current_value.split(' ')
373
+ end
374
+ raise 'Strange border attribute value. Does not have 3 parts' unless value_array.length == 3
375
+ value_array[part-1]=value
376
+ @cellformat.set_cell_style_node_attribute(attribute_name, value_array.join(' '))
377
+ end
378
+
379
+ def get_border_string_part(part)
380
+ current_value = @cellformat.get_cell_style_node_attribute(attribute_name) || @cellformat.get_cell_style_node_attribute('border')
381
+ if current_value.nil? or (current_value=='none')
382
+ return nil
383
+ else
384
+ value_array = current_value.split(' ')
385
+ raise 'Strange border attribute value. Does not have 3 parts' unless value_array.length == 3
386
+ return value_array[part-1]
387
+ end
388
+ end
389
+
390
+ def get_value_string
391
+ @cellformat.get_cell_style_node_attribute(attribute_name)
392
+ end
393
+
305
394
  end
306
395
 
396
+ end # module
397
+
307
398
 
308
399
 
309
400