rodf 0.3.7 → 1.2.0

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 (74) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +77 -0
  3. data/LICENSE +7 -0
  4. data/README.md +244 -64
  5. data/Rakefile +9 -17
  6. data/lib/rodf/cell.rb +171 -0
  7. data/lib/rodf/column.rb +21 -0
  8. data/lib/rodf/container.rb +18 -0
  9. data/lib/rodf/data_style.rb +46 -0
  10. data/lib/rodf/document.rb +133 -0
  11. data/lib/rodf/hyperlink.rb +33 -0
  12. data/lib/rodf/master_page.rb +11 -0
  13. data/lib/rodf/page_layout.rb +41 -0
  14. data/lib/rodf/paragraph.rb +28 -0
  15. data/lib/rodf/paragraph_container.rb +30 -0
  16. data/lib/rodf/property.rb +138 -0
  17. data/lib/rodf/row.rb +56 -0
  18. data/lib/{odf → rodf}/skeleton/manifest.xml.erb +0 -0
  19. data/lib/{odf → rodf}/skeleton/styles.pxml +0 -0
  20. data/lib/rodf/skeleton.rb +17 -0
  21. data/lib/rodf/span.rb +45 -0
  22. data/lib/rodf/spreadsheet.rb +113 -0
  23. data/lib/rodf/style.rb +77 -0
  24. data/lib/rodf/style_section.rb +33 -0
  25. data/lib/rodf/tab.rb +17 -0
  26. data/lib/rodf/table.rb +95 -0
  27. data/lib/rodf/text.rb +126 -0
  28. data/lib/rodf/version.rb +3 -0
  29. data/lib/rodf.rb +31 -0
  30. metadata +76 -115
  31. data/CHANGELOG +0 -17
  32. data/Gemfile +0 -9
  33. data/LICENSE.LGPL +0 -166
  34. data/Manifest +0 -48
  35. data/lib/odf/cell.rb +0 -104
  36. data/lib/odf/column.rb +0 -33
  37. data/lib/odf/compatibility.rb +0 -25
  38. data/lib/odf/container.rb +0 -57
  39. data/lib/odf/data_style.rb +0 -45
  40. data/lib/odf/document.rb +0 -67
  41. data/lib/odf/hyperlink.rb +0 -51
  42. data/lib/odf/master_page.rb +0 -33
  43. data/lib/odf/page_layout.rb +0 -39
  44. data/lib/odf/paragraph.rb +0 -46
  45. data/lib/odf/paragraph_container.rb +0 -35
  46. data/lib/odf/property.rb +0 -131
  47. data/lib/odf/row.rb +0 -44
  48. data/lib/odf/skeleton.rb +0 -34
  49. data/lib/odf/span.rb +0 -70
  50. data/lib/odf/spreadsheet.rb +0 -64
  51. data/lib/odf/style.rb +0 -65
  52. data/lib/odf/style_section.rb +0 -42
  53. data/lib/odf/tab.rb +0 -38
  54. data/lib/odf/table.rb +0 -50
  55. data/lib/odf/text.rb +0 -66
  56. data/rodf.gemspec +0 -48
  57. data/spec/cell_spec.rb +0 -189
  58. data/spec/data_style_spec.rb +0 -61
  59. data/spec/file_storage_spec.rb +0 -47
  60. data/spec/hyperlink_spec.rb +0 -62
  61. data/spec/master_page_spec.rb +0 -35
  62. data/spec/page_layout_spec.rb +0 -45
  63. data/spec/paragraph_spec.rb +0 -75
  64. data/spec/property_spec.rb +0 -270
  65. data/spec/row_spec.rb +0 -59
  66. data/spec/skeleton_spec.rb +0 -33
  67. data/spec/span_spec.rb +0 -65
  68. data/spec/spec_helper.rb +0 -23
  69. data/spec/spreadsheet_spec.rb +0 -123
  70. data/spec/style_section_spec.rb +0 -42
  71. data/spec/style_spec.rb +0 -109
  72. data/spec/tab_spec.rb +0 -34
  73. data/spec/table_spec.rb +0 -90
  74. data/spec/text_spec.rb +0 -79
@@ -0,0 +1,18 @@
1
+ module RODF
2
+ class Container
3
+ def initialize(*_args, &contents)
4
+ return unless contents
5
+
6
+ if contents.arity.zero?
7
+ instance_exec(self, &contents)
8
+ else
9
+ yield(self)
10
+ end
11
+ end
12
+
13
+ def self.create(*args, &contents)
14
+ container = self.new(*args, &contents)
15
+ container.xml
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,46 @@
1
+ module RODF
2
+ class DataStyle < Container
3
+ def initialize(name, type)
4
+ super
5
+
6
+ @type, @name = type, name
7
+ end
8
+
9
+ def style_sections
10
+ @style_sections ||= []
11
+ end
12
+
13
+ def style_section(*args, &block)
14
+ x = StyleSection.new(*args, &block)
15
+
16
+ style_sections << x
17
+
18
+ return x
19
+ end
20
+ alias section style_section
21
+
22
+ def style_sections_xml
23
+ style_sections.map(&:xml).join
24
+ end
25
+
26
+ def add_style_sections(*elements)
27
+ if elements.first.is_a?(Array)
28
+ elements = elements.first
29
+ end
30
+
31
+ elements.each do |element|
32
+ style_section(element)
33
+ end
34
+ end
35
+
36
+ def xml
37
+ Builder::XmlMarkup.new.tag! "number:#{@type}-style", 'style:name' => @name do |xml|
38
+ xml << style_sections_xml
39
+ end
40
+ end
41
+
42
+ def method_missing(name, *args)
43
+ section(name, *args)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,133 @@
1
+ module RODF
2
+ class Document < Container
3
+ def self.file(ods_file_name, &contents)
4
+ doc = new
5
+
6
+ if contents.arity.zero?
7
+ contents.call(doc)
8
+ else
9
+ doc.instance_exec(doc, &contents)
10
+ end
11
+
12
+ doc.write_to ods_file_name
13
+ end
14
+
15
+ def write_to(ods_file_name)
16
+ File.open(ods_file_name, 'wb') { |f| f << self.bytes }
17
+ end
18
+
19
+ def bytes
20
+ buffer = Zip::OutputStream::write_buffer do |zio|
21
+ zio.put_next_entry('META-INF/manifest.xml')
22
+
23
+ zio << self.class.skeleton.manifest(self.class.doc_type)
24
+
25
+ zio.put_next_entry('styles.xml')
26
+
27
+ zio << self.class.skeleton.styles
28
+
29
+ zio << self.office_styles_xml unless self.office_styles.empty?
30
+
31
+ zio << "</office:styles> </office:document-styles>"
32
+
33
+ zio.put_next_entry('content.xml')
34
+
35
+ zio << self.xml
36
+ end
37
+
38
+ buffer.set_encoding('ASCII-8BIT')
39
+
40
+ buffer.rewind
41
+
42
+ buffer.sysread
43
+ end
44
+
45
+ def styles
46
+ @styles ||= []
47
+ end
48
+
49
+ def style(*args, &block)
50
+ x = Style.new(*args, &block)
51
+
52
+ styles << x
53
+
54
+ return x
55
+ end
56
+
57
+ def styles_xml
58
+ styles.map(&:xml).join
59
+ end
60
+
61
+ def add_styles(*elements)
62
+ if elements.first.is_a?(Array)
63
+ elements = elements.first
64
+ end
65
+
66
+ elements.each do |element|
67
+ style(element)
68
+ end
69
+ end
70
+
71
+ def default_styles
72
+ @default_styles ||= []
73
+ end
74
+
75
+ def default_style(*args, &block)
76
+ x = DefaultStyle.new(*args, &block)
77
+
78
+ default_styles << x
79
+
80
+ return x
81
+ end
82
+
83
+ def default_styles_xml
84
+ default_styles.map(&:xml).join
85
+ end
86
+
87
+ def add_default_styles(*elements)
88
+ if elements.first.is_a?(Array)
89
+ elements = elements.first
90
+ end
91
+
92
+ elements.each do |element|
93
+ default_style(element)
94
+ end
95
+ end
96
+
97
+ def office_styles
98
+ @office_styles ||= []
99
+ end
100
+
101
+ def office_style(*args, &block)
102
+ x = OfficeStyle.new(*args, &block)
103
+
104
+ office_styles << x
105
+
106
+ return x
107
+ end
108
+
109
+ def office_styles_xml
110
+ office_styles.map(&:xml).join
111
+ end
112
+
113
+ def add_office_styles(*elements)
114
+ if elements.first.is_a?(Array)
115
+ elements = elements.first
116
+ end
117
+
118
+ elements.each do |element|
119
+ office_style(element)
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ def self.skeleton
126
+ @skeleton ||= Skeleton.new
127
+ end
128
+
129
+ def self.doc_type
130
+ name.split('::').last.downcase
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,33 @@
1
+ module RODF
2
+ class Hyperlink < ParagraphContainer
3
+ def initialize(first, second = {})
4
+ super
5
+
6
+ if second.instance_of?(Hash) && second.empty?
7
+ @href = first
8
+ else
9
+ span(first)
10
+ @href = second.instance_of?(Hash) ? second[:href] : second
11
+ end
12
+ end
13
+
14
+ def xml
15
+ Builder::XmlMarkup.new.text:a, 'xlink:href' => @href do |a|
16
+ a << content_parts_xml
17
+ end
18
+ end
19
+ end
20
+
21
+ class ParagraphContainer < Container
22
+ def link(*args)
23
+ l = Hyperlink.new(*args)
24
+
25
+ yield l if block_given?
26
+
27
+ content_parts << l
28
+
29
+ l
30
+ end
31
+ alias a link
32
+ end
33
+ end
@@ -0,0 +1,11 @@
1
+ module RODF
2
+ class MasterPage
3
+ def initialize(name, opts = {})
4
+ @name, @layout = name, opts[:layout]
5
+ end
6
+
7
+ def xml
8
+ Builder::XmlMarkup.new.tag! 'style:master-page', 'style:name' => @name, 'style:page-layout-name' => @layout
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,41 @@
1
+ module RODF
2
+ class PageLayout < Container
3
+ def initialize(name)
4
+ super
5
+
6
+ @name = name
7
+ end
8
+
9
+ def properties
10
+ @properties ||= []
11
+ end
12
+
13
+ def property(*args, &block)
14
+ x = Property.new(*args, &block)
15
+
16
+ properties << x
17
+
18
+ return x
19
+ end
20
+
21
+ def properties_xml
22
+ properties.map(&:xml).join
23
+ end
24
+
25
+ def add_properties(*elements)
26
+ if elements.first.is_a?(Array)
27
+ elements = elements.first
28
+ end
29
+
30
+ elements.each do |element|
31
+ property(element)
32
+ end
33
+ end
34
+
35
+ def xml
36
+ Builder::XmlMarkup.new.tag! 'style:page-layout', 'style:name' => @name do |b|
37
+ b << properties_xml
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,28 @@
1
+ module RODF
2
+ class Paragraph < ParagraphContainer
3
+ def initialize(fst = nil, snd = {})
4
+ super
5
+
6
+ first_is_hash = fst.instance_of?(Hash)
7
+
8
+ span(fst) unless first_is_hash
9
+
10
+ @elem_attrs = make_element_attributes(first_is_hash ? fst : snd)
11
+ end
12
+
13
+ def xml
14
+ Builder::XmlMarkup.new.text:p, @elem_attrs do |xml|
15
+ xml << content_parts_xml
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def make_element_attributes(opts)
22
+ attrs = {}
23
+ attrs['text:style-name'] = opts[:style] unless opts[:style].nil?
24
+ attrs
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ module RODF
2
+ # Container for all kinds of paragraph content
3
+ class ParagraphContainer < Container
4
+ def content_parts
5
+ @content_parts ||= []
6
+ end
7
+
8
+ def content_parts_xml
9
+ content_parts.map {|p| p.xml}.join
10
+ end
11
+
12
+ def span(*args)
13
+ s = Span.new(*args)
14
+
15
+ yield s if block_given?
16
+
17
+ content_parts << s
18
+
19
+ s
20
+ end
21
+
22
+ def <<(content)
23
+ span(content)
24
+ end
25
+
26
+ def method_missing(style, *args)
27
+ span(style, *args)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,138 @@
1
+ module RODF
2
+ class Property
3
+ PROPERTY_NAMES = {
4
+ cell: 'table-cell-properties',
5
+ text: 'text-properties',
6
+ column: 'table-column-properties',
7
+ row: 'table-row-properties',
8
+ page_layout: 'page-layout-properties',
9
+ header_footer: 'header-footer-properties',
10
+ list_level: 'list-level-properties',
11
+ conditional: 'map',
12
+ }
13
+
14
+ TRANSLATED_SPECS = [:border_color, :border_style, :border_width]
15
+
16
+ ATTRIBUTES_TO_NAMESPACES = [
17
+ [
18
+ [
19
+ 'column-width', 'rotation-angle', 'text-underline-type', 'tab-stop-distance',
20
+ 'condition', 'apply-style-name', 'base-cell-address', 'row-height',
21
+ 'country-asian', 'country-complex', 'font-charset', 'font-charset-asian',
22
+ 'font-charset-complex', 'font-family-asian', 'font-family-complex', 'font-family-generic',
23
+ 'font-family-generic-asian', 'font-family-generic-complex', 'font-name', 'font-name-asian',
24
+ 'font-name-complex', 'font-pitch', 'font-pitch-asian', 'font-pitch-complex', 'font-relief',
25
+ 'font-size-asian', 'font-size-complex', 'font-size-rel', 'font-size-rel-asian',
26
+ 'font-size-rel-complex', 'font-style-asian', 'font-style-complex', 'font-style-name',
27
+ 'font-style-name-asian', 'font-style-name-complex', 'font-weight-asian', 'font-weight-complex',
28
+ 'language-asian', 'language-complex', 'letter-kerning', 'rfc-language-tag',
29
+ 'rfc-language-tag-asian', 'rfc-language-tag-complex', 'script-asian', 'script-complex',
30
+ 'script-type', 'text-blinking', 'text-combine', 'text-combine-end-char',
31
+ 'text-combine-start-char', 'text-emphasize', 'text-line-through-color',
32
+ 'text-line-through-mode', 'text-line-through-style', 'text-line-through-text',
33
+ 'text-line-through-text-style', 'text-line-through-type', 'text-line-through-width',
34
+ 'text-outline', 'text-overline-color', 'text-overline-mode', 'text-overline-style',
35
+ 'text-overline-type', 'text-overline-width', 'text-position', 'text-rotation-angle',
36
+ 'text-rotation-scale', 'text-scale', 'text-underline-color', 'text-underline-mode',
37
+ 'text-underline-style', 'text-underline-width', 'use-window-font-color',
38
+ 'border-line-width', 'border-line-width-bottom', 'border-line-width-left',
39
+ 'border-line-width-right', 'border-line-width-top', 'cell-protect', 'decimal-places',
40
+ 'diagonal-bl-tr', 'diagonal-bl-tr-widths', 'diagonal-tl-br', 'diagonal-tl-br-widths',
41
+ 'direction', 'glyph-orientation-vertical', 'print-content', 'repeat-content',
42
+ 'rotation-align', 'shadow', 'shrink-to-fit', 'text-align-source',
43
+ 'vertical-align', 'writing-mode', 'min-row-height', 'use-optimal-row-height',
44
+ 'rel-column-width', 'use-optimal-column-width', 'auto-text-indent', 'background-transparency',
45
+ 'font-independent-line-spacing', 'join-border', 'justify-single-word', 'line-break',
46
+ 'line-height-at-least', 'line-spacing', 'page-number', 'punctuation-wrap', 'register-true',
47
+ 'snap-to-layout-grid', 'text-autospace',
48
+ 'writing-mode-automatic',
49
+ 'first-page-number', 'footnote-max-height', 'layout-grid-base-height', 'layout-grid-base-width',
50
+ 'layout-grid-color', 'layout-grid-display', 'layout-grid-lines', 'layout-grid-mode',
51
+ 'layout-grid-print', 'layout-grid-ruby-below', 'layout-grid-ruby-height', 'layout-grid-snap-to',
52
+ 'layout-grid-standard-mode', 'num-format', 'num-letter-sync', 'num-prefix', 'num-suffix',
53
+ 'paper-tray-name', 'print', 'print-orientation', 'print-page-order',
54
+ 'register-truth-ref-style-name', 'scale-to', 'scale-to-pages', 'table-centering',
55
+ 'dynamic-spacing',
56
+ 'ruby-align', 'ruby-position',
57
+ 'editable', 'protect',
58
+ 'may-break-between-rows', 'rel-width', 'width',
59
+ 'vertical-pos', 'vertical-rel'
60
+ ],
61
+ 'style'
62
+ ],
63
+ [
64
+ ['height', 'y'],
65
+ 'svg'
66
+ ],
67
+ [
68
+ ['dont-balance-text-columns', 'list-level-position-and-space-mode', 'min-label-distance', 'min-label-width', 'space-before'],
69
+ 'text'
70
+ ],
71
+ [
72
+ ['align', 'border-model', 'display'],
73
+ 'table'
74
+ ]
75
+ ]
76
+
77
+ def initialize(type, specs={})
78
+ @name = 'style:' + (PROPERTY_NAMES[type] || "#{type}-properties")
79
+
80
+ @specs = translate(specs).map { |k, v| [k.to_s, v] }
81
+ end
82
+
83
+ def xml
84
+ specs = @specs.inject({}) do |acc, kv|
85
+ acc.merge Property.lookup_namespace_for(kv.first) + ':' + kv.first => kv.last
86
+ end
87
+
88
+ Builder::XmlMarkup.new.tag! @name, specs
89
+ end
90
+
91
+ def self.lookup_namespace_for(property_name)
92
+ as = ATTRIBUTES_TO_NAMESPACES.select {|a| a[0].include? property_name}
93
+
94
+ as.empty? ? 'fo' : as[0][1]
95
+ end
96
+
97
+ private
98
+
99
+ def translate(specs)
100
+ result = specs.clone
101
+
102
+ tspecs = specs.select {|k, v| TRANSLATED_SPECS.include? k}
103
+
104
+ tspecs.map {|k, v| result.delete k}
105
+
106
+ tspecs = tspecs.inject({}) {|acc, e| acc.merge e.first => e.last}
107
+
108
+ if tspecs[:border_width] && tspecs[:border_style] && tspecs[:border_color] then
109
+ width = tspecs[:border_width].split
110
+ style = tspecs[:border_style].split
111
+ color = tspecs[:border_color].split
112
+
113
+ if width.length == 1 && style.length == 1 && color.length == 1 then
114
+ result[:border] = [width[0], style[0], color[0]].join(' ')
115
+ else
116
+ result['border-top'] = cascading_join(width, style, color, 0)
117
+ result['border-right'] = cascading_join(width, style, color, 1, 0)
118
+ result['border-bottom'] = cascading_join(width, style, color, 2, 0)
119
+ result['border-left'] = cascading_join(width, style, color, 3, 1, 0)
120
+ end
121
+ end
122
+
123
+ result
124
+ end
125
+
126
+ def cascading_join(width_parts, style_parts, color_parts, *prefs)
127
+ [
128
+ cascade(width_parts, prefs),
129
+ cascade(style_parts, prefs),
130
+ cascade(color_parts, prefs),
131
+ ].join(' ')
132
+ end
133
+
134
+ def cascade(list, prefs)
135
+ prefs.inject(nil) {|acc, i| acc || list[i]}
136
+ end
137
+ end
138
+ end
data/lib/rodf/row.rb ADDED
@@ -0,0 +1,56 @@
1
+ module RODF
2
+ class Row < Container
3
+ attr_reader :number
4
+ attr_writer :style
5
+
6
+ def initialize(number=0, opts={})
7
+ @number = number
8
+
9
+ @elem_attrs = {}
10
+
11
+ @elem_attrs['table:style-name'] = opts[:style] unless opts[:style].nil?
12
+
13
+ if opts[:attributes]
14
+ @elem_attrs.merge!(opts[:attributes])
15
+ end
16
+
17
+ super
18
+ end
19
+
20
+ def cells
21
+ @cells ||= []
22
+ end
23
+
24
+ def cell(*args, &block)
25
+ x = Cell.new(*args, &block)
26
+
27
+ cells << x
28
+
29
+ return x
30
+ end
31
+
32
+ def cells_xml
33
+ cells.map(&:xml).join
34
+ end
35
+
36
+ def style=(style_name)
37
+ @elem_attrs['table:style-name'] = style_name
38
+ end
39
+
40
+ def add_cells(*elements)
41
+ if elements.first.is_a?(Array)
42
+ elements = elements.first
43
+ end
44
+
45
+ elements.each do |element|
46
+ cell(element)
47
+ end
48
+ end
49
+
50
+ def xml
51
+ Builder::XmlMarkup.new.tag! 'table:table-row', @elem_attrs do |xml|
52
+ xml << cells_xml
53
+ end
54
+ end
55
+ end
56
+ end
File without changes
File without changes
@@ -0,0 +1,17 @@
1
+ module RODF
2
+ class Skeleton
3
+ def manifest(document_type)
4
+ ERB.new(template('manifest.xml.erb')).result(binding)
5
+ end
6
+
7
+ def styles
8
+ template('styles.pxml')
9
+ end
10
+
11
+ private
12
+
13
+ def template(fname)
14
+ File.open(File.dirname(__FILE__) + '/skeleton/' + fname).read
15
+ end
16
+ end
17
+ end
data/lib/rodf/span.rb ADDED
@@ -0,0 +1,45 @@
1
+ module RODF
2
+ class TextNode
3
+ def initialize(content)
4
+ @content = content
5
+ end
6
+
7
+ def xml
8
+ Builder::XChar.encode(@content.to_s)
9
+ end
10
+ end
11
+
12
+ class Span < ParagraphContainer
13
+ def initialize(first, second=nil, style: nil, &block)
14
+ super(first, second, &block)
15
+
16
+ @style = nil
17
+
18
+ if first.instance_of?(Symbol)
19
+ ### Legacy behaviour
20
+
21
+ @style = first
22
+
23
+ if !second.nil?
24
+ content_parts << TextNode.new(second)
25
+ end
26
+ else
27
+ if style
28
+ @style = style
29
+ end
30
+
31
+ content_parts << TextNode.new(first)
32
+ end
33
+ end
34
+
35
+ def xml
36
+ if @style.nil?
37
+ return content_parts_xml
38
+ end
39
+
40
+ Builder::XmlMarkup.new.text(:span, 'text:style-name' => @style) do |xml|
41
+ xml << content_parts_xml
42
+ end
43
+ end
44
+ end
45
+ end