ruby_pptx 0.1.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 (101) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +83 -0
  3. data/LICENSE +21 -0
  4. data/NOTICE +48 -0
  5. data/README.md +272 -0
  6. data/lib/ruby_pptx/action.rb +120 -0
  7. data/lib/ruby_pptx/autoshape_spec.rb +337 -0
  8. data/lib/ruby_pptx/builder.rb +228 -0
  9. data/lib/ruby_pptx/chart/categories.rb +208 -0
  10. data/lib/ruby_pptx/chart/chart.rb +443 -0
  11. data/lib/ruby_pptx/chart/combo.rb +230 -0
  12. data/lib/ruby_pptx/chart/data.rb +147 -0
  13. data/lib/ruby_pptx/chart/format.rb +647 -0
  14. data/lib/ruby_pptx/chart/workbook_writer.rb +212 -0
  15. data/lib/ruby_pptx/chart/xml_writer.rb +953 -0
  16. data/lib/ruby_pptx/chart/xy_data.rb +182 -0
  17. data/lib/ruby_pptx/core_ext.rb +12 -0
  18. data/lib/ruby_pptx/dml/color.rb +155 -0
  19. data/lib/ruby_pptx/dml/effect.rb +33 -0
  20. data/lib/ruby_pptx/dml/fill.rb +234 -0
  21. data/lib/ruby_pptx/element_proxy.rb +53 -0
  22. data/lib/ruby_pptx/enum/action.rb +46 -0
  23. data/lib/ruby_pptx/enum/base.rb +139 -0
  24. data/lib/ruby_pptx/enum/chart.rb +278 -0
  25. data/lib/ruby_pptx/enum/dml.rb +237 -0
  26. data/lib/ruby_pptx/enum/lang.rb +441 -0
  27. data/lib/ruby_pptx/enum/prog_id.rb +38 -0
  28. data/lib/ruby_pptx/enum/shapes.rb +514 -0
  29. data/lib/ruby_pptx/enum/text.rb +108 -0
  30. data/lib/ruby_pptx/errors.rb +15 -0
  31. data/lib/ruby_pptx/geometry.rb +89 -0
  32. data/lib/ruby_pptx/image.rb +347 -0
  33. data/lib/ruby_pptx/length.rb +120 -0
  34. data/lib/ruby_pptx/media.rb +64 -0
  35. data/lib/ruby_pptx/numeric_lengths.rb +30 -0
  36. data/lib/ruby_pptx/opc/constants.rb +205 -0
  37. data/lib/ruby_pptx/opc/oxml.rb +99 -0
  38. data/lib/ruby_pptx/opc/pack_uri.rb +146 -0
  39. data/lib/ruby_pptx/opc/package.rb +482 -0
  40. data/lib/ruby_pptx/opc/serialized.rb +229 -0
  41. data/lib/ruby_pptx/opc/spec.rb +37 -0
  42. data/lib/ruby_pptx/oxml/action.rb +43 -0
  43. data/lib/ruby_pptx/oxml/chart.rb +785 -0
  44. data/lib/ruby_pptx/oxml/content_model.rb +186 -0
  45. data/lib/ruby_pptx/oxml/core_properties.rb +145 -0
  46. data/lib/ruby_pptx/oxml/dml/color.rb +115 -0
  47. data/lib/ruby_pptx/oxml/dml/fill.rb +137 -0
  48. data/lib/ruby_pptx/oxml/element.rb +329 -0
  49. data/lib/ruby_pptx/oxml/ns.rb +115 -0
  50. data/lib/ruby_pptx/oxml/presentation.rb +110 -0
  51. data/lib/ruby_pptx/oxml/section.rb +65 -0
  52. data/lib/ruby_pptx/oxml/shapes/autoshape.rb +310 -0
  53. data/lib/ruby_pptx/oxml/shapes/groupshape.rb +217 -0
  54. data/lib/ruby_pptx/oxml/shapes/other.rb +405 -0
  55. data/lib/ruby_pptx/oxml/shapes/shared.rb +292 -0
  56. data/lib/ruby_pptx/oxml/simple_types.rb +563 -0
  57. data/lib/ruby_pptx/oxml/slide.rb +290 -0
  58. data/lib/ruby_pptx/oxml/table.rb +253 -0
  59. data/lib/ruby_pptx/oxml/text.rb +324 -0
  60. data/lib/ruby_pptx/oxml/theme.rb +93 -0
  61. data/lib/ruby_pptx/package.rb +139 -0
  62. data/lib/ruby_pptx/parts/chart.rb +75 -0
  63. data/lib/ruby_pptx/parts/core_properties.rb +40 -0
  64. data/lib/ruby_pptx/parts/embedded_package.rb +43 -0
  65. data/lib/ruby_pptx/parts/image.rb +55 -0
  66. data/lib/ruby_pptx/parts/media.rb +17 -0
  67. data/lib/ruby_pptx/parts/presentation.rb +79 -0
  68. data/lib/ruby_pptx/parts/slide.rb +221 -0
  69. data/lib/ruby_pptx/parts/theme.rb +26 -0
  70. data/lib/ruby_pptx/pattern_matching.rb +50 -0
  71. data/lib/ruby_pptx/presentation.rb +128 -0
  72. data/lib/ruby_pptx/refinements.rb +21 -0
  73. data/lib/ruby_pptx/section.rb +130 -0
  74. data/lib/ruby_pptx/shapes/adjustments.rb +83 -0
  75. data/lib/ruby_pptx/shapes/authoring.rb +110 -0
  76. data/lib/ruby_pptx/shapes/base.rb +138 -0
  77. data/lib/ruby_pptx/shapes/freeform.rb +141 -0
  78. data/lib/ruby_pptx/shapes/placeholder.rb +184 -0
  79. data/lib/ruby_pptx/shapes/shape.rb +337 -0
  80. data/lib/ruby_pptx/shapes/shape_tree.rb +634 -0
  81. data/lib/ruby_pptx/sliceable.rb +34 -0
  82. data/lib/ruby_pptx/slide.rb +430 -0
  83. data/lib/ruby_pptx/table.rb +316 -0
  84. data/lib/ruby_pptx/table_paging.rb +91 -0
  85. data/lib/ruby_pptx/templates/default.pptx +0 -0
  86. data/lib/ruby_pptx/templates/docx-icon.emf +0 -0
  87. data/lib/ruby_pptx/templates/generic-icon.emf +0 -0
  88. data/lib/ruby_pptx/templates/media-speaker.png +0 -0
  89. data/lib/ruby_pptx/templates/notes.xml +23 -0
  90. data/lib/ruby_pptx/templates/notesMaster.xml +352 -0
  91. data/lib/ruby_pptx/templates/pptx-icon.emf +0 -0
  92. data/lib/ruby_pptx/templates/slideMaster.xml +277 -0
  93. data/lib/ruby_pptx/templates/theme.xml +321 -0
  94. data/lib/ruby_pptx/templates/xlsx-icon.emf +0 -0
  95. data/lib/ruby_pptx/text/fitter.rb +72 -0
  96. data/lib/ruby_pptx/text/font_metrics.rb +221 -0
  97. data/lib/ruby_pptx/text/text.rb +393 -0
  98. data/lib/ruby_pptx/theme.rb +98 -0
  99. data/lib/ruby_pptx/version.rb +5 -0
  100. data/lib/ruby_pptx.rb +90 -0
  101. metadata +174 -0
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zip"
4
+ require "stringio"
5
+
6
+ module Pptx
7
+ # Writes the small Excel workbook embedded alongside a chart.
8
+ #
9
+ # PowerPoint opens this workbook when the user chooses "Edit Data"; it is not
10
+ # used for rendering, which is driven entirely by the cached values in the
11
+ # chart XML itself.
12
+ #
13
+ # python-pptx delegates this to XlsxWriter. This writes the minimum a
14
+ # conforming consumer needs directly, which keeps the gem free of a
15
+ # spreadsheet dependency. Strings are written inline rather than through a
16
+ # shared-strings part, which is valid and removes a whole part from the
17
+ # package.
18
+ class ChartWorkbookWriter
19
+ SHEET_NAME = "Sheet1"
20
+
21
+ NS_SPREADSHEETML = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
22
+ NS_OFC_RELS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
23
+ NS_PKG_RELS = "http://schemas.openxmlformats.org/package/2006/relationships"
24
+
25
+ CT_SHEET_MAIN = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"
26
+ CT_WORKSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
27
+ RT_OFFICE_DOCUMENT = "#{NS_OFC_RELS}/officeDocument".freeze
28
+ RT_WORKSHEET = "#{NS_OFC_RELS}/worksheet".freeze
29
+ RT_STYLES = "#{NS_OFC_RELS}/styles".freeze
30
+ CT_STYLES = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"
31
+ # The first id free for a custom number format; 0-163 are built in.
32
+ DATE_FORMAT_ID = 164
33
+
34
+ def initialize(chart_data)
35
+ @chart_data = chart_data
36
+ end
37
+
38
+ # @return [String] the .xlsx bytes
39
+ def blob
40
+ buffer = Zip::OutputStream.write_buffer(StringIO.new) do |zos|
41
+ entries.each do |name, content|
42
+ zos.put_next_entry(name)
43
+ zos.write(content)
44
+ end
45
+ end
46
+ buffer.rewind
47
+ buffer.read
48
+ end
49
+
50
+ private
51
+
52
+ def entries
53
+ parts = {
54
+ "[Content_Types].xml" => content_types_xml,
55
+ "_rels/.rels" => package_rels_xml,
56
+ "xl/workbook.xml" => workbook_xml,
57
+ "xl/_rels/workbook.xml.rels" => workbook_rels_xml,
58
+ "xl/worksheets/sheet1.xml" => worksheet_xml
59
+ }
60
+ parts["xl/styles.xml"] = styles_xml if dates?
61
+ parts
62
+ end
63
+
64
+ # Only date categories need a cell style -- a number format that makes
65
+ # the serial numbers read as dates -- so only they get a styles part.
66
+ # XY and bubble data have no categories, so never dates.
67
+ def dates? = @chart_data.respond_to?(:categories) && @chart_data.categories.dates?
68
+
69
+ def declaration = %(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n)
70
+
71
+ def content_types_xml
72
+ <<~XML
73
+ #{declaration.chomp}
74
+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
75
+ <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
76
+ <Default Extension="xml" ContentType="application/xml"/>
77
+ <Override PartName="/xl/workbook.xml" ContentType="#{CT_SHEET_MAIN}"/>
78
+ <Override PartName="/xl/worksheets/sheet1.xml" ContentType="#{CT_WORKSHEET}"/>
79
+ #{%(<Override PartName="/xl/styles.xml" ContentType="#{CT_STYLES}"/>) if dates?}
80
+ </Types>
81
+ XML
82
+ end
83
+
84
+ def package_rels_xml
85
+ <<~XML
86
+ #{declaration.chomp}
87
+ <Relationships xmlns="#{NS_PKG_RELS}">
88
+ <Relationship Id="rId1" Type="#{RT_OFFICE_DOCUMENT}" Target="xl/workbook.xml"/>
89
+ </Relationships>
90
+ XML
91
+ end
92
+
93
+ def workbook_xml
94
+ <<~XML
95
+ #{declaration.chomp}
96
+ <workbook xmlns="#{NS_SPREADSHEETML}" xmlns:r="#{NS_OFC_RELS}">
97
+ <sheets><sheet name="#{SHEET_NAME}" sheetId="1" r:id="rId1"/></sheets>
98
+ </workbook>
99
+ XML
100
+ end
101
+
102
+ def workbook_rels_xml
103
+ <<~XML
104
+ #{declaration.chomp}
105
+ <Relationships xmlns="#{NS_PKG_RELS}">
106
+ <Relationship Id="rId1" Type="#{RT_WORKSHEET}" Target="worksheets/sheet1.xml"/>
107
+ #{%(<Relationship Id="rId2" Type="#{RT_STYLES}" Target="styles.xml"/>) if dates?}
108
+ </Relationships>
109
+ XML
110
+ end
111
+
112
+ def worksheet_xml
113
+ "#{declaration}<worksheet xmlns=\"#{NS_SPREADSHEETML}\"><sheetData>" \
114
+ "#{rows_xml}</sheetData></worksheet>\n"
115
+ end
116
+
117
+ # Row 1 holds the series names, after the category columns. Each later
118
+ # row holds that row's category labels and one value per series.
119
+ #
120
+ # Grouped categories take one column per level, the leaf level
121
+ # rightmost; a group's label sits on the row where its run begins, as
122
+ # python-pptx writes it.
123
+ def rows_xml
124
+ rows = Hash.new { |hash, row| hash[row] = [] }
125
+ @chart_data.series.each do |series|
126
+ rows[1] << [series.index + depth, string_cell("#{@chart_data.column_letter(series)}1", series.name)]
127
+ end
128
+ category_cells(rows)
129
+ series_cells(rows)
130
+ rows.keys.sort.map do |row|
131
+ %(<row r="#{row}">#{rows[row].sort_by(&:first).map(&:last).join}</row>)
132
+ end.join
133
+ end
134
+
135
+ def depth = @chart_data.categories.depth
136
+
137
+ def category_cells(rows)
138
+ @chart_data.categories.levels.each_with_index do |level, level_index|
139
+ column = depth - level_index - 1
140
+ letter = ChartData.column_reference(column + 1)
141
+ level.each do |offset, label|
142
+ row = ChartData::FIRST_DATA_ROW + offset
143
+ rows[row] << [column, category_cell("#{letter}#{row}", label)]
144
+ end
145
+ end
146
+ end
147
+
148
+ def series_cells(rows)
149
+ @chart_data.series.each do |series|
150
+ letter = @chart_data.column_letter(series)
151
+ series.values.each_with_index do |value, index|
152
+ row = ChartData::FIRST_DATA_ROW + index
153
+ rows[row] << [series.index + depth, number_cell("#{letter}#{row}", value)]
154
+ end
155
+ end
156
+ end
157
+
158
+ def category_cell(reference, label)
159
+ return date_cell(reference, label) if label.is_a?(Date) || label.is_a?(Time)
160
+
161
+ cell_for(reference, label)
162
+ end
163
+
164
+ # A date is its Excel serial number, styled with the date format.
165
+ def date_cell(reference, date)
166
+ category = ChartDataCategory.new(date, nil)
167
+ %(<c r="#{reference}" s="1"><v>#{category.numeric_str_val.to_f.to_i}</v></c>)
168
+ end
169
+
170
+ def styles_xml
171
+ format_code = escape(@chart_data.categories.number_format)
172
+ <<~XML
173
+ #{declaration.chomp}
174
+ <styleSheet xmlns="#{NS_SPREADSHEETML}">
175
+ <numFmts count="1"><numFmt numFmtId="#{DATE_FORMAT_ID}" formatCode="#{format_code}"/></numFmts>
176
+ <fonts count="1"><font><sz val="11"/><name val="Calibri"/></font></fonts>
177
+ <fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>
178
+ <borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>
179
+ <cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>
180
+ <cellXfs count="2"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/><xf numFmtId="#{DATE_FORMAT_ID}" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1"/></cellXfs>
181
+ </styleSheet>
182
+ XML
183
+ end
184
+
185
+ def cell_for(reference, value)
186
+ value.is_a?(Numeric) ? number_cell(reference, value) : string_cell(reference, value)
187
+ end
188
+
189
+ def string_cell(reference, value)
190
+ %(<c r="#{reference}" t="inlineStr"><is><t>#{escape(value)}</t></is></c>)
191
+ end
192
+
193
+ # A nil value means "no data for this category", which is written as an
194
+ # empty cell rather than a zero.
195
+ def number_cell(reference, value)
196
+ return %(<c r="#{reference}"/>) if value.nil?
197
+
198
+ %(<c r="#{reference}"><v>#{format_number(value)}</v></c>)
199
+ end
200
+
201
+ # Integral floats are written without a trailing ".0", as Excel does.
202
+ def format_number(value)
203
+ return value.to_s unless value.is_a?(Float)
204
+
205
+ value == value.truncate ? value.to_i.to_s : value.to_s
206
+ end
207
+
208
+ def escape(value)
209
+ value.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
210
+ end
211
+ end
212
+ end