ruby_pptx 0.1.0 → 0.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/README.md +83 -2
  4. data/lib/ruby_pptx/action.rb +12 -4
  5. data/lib/ruby_pptx/autoshape_spec.rb +6 -2
  6. data/lib/ruby_pptx/builder.rb +3 -1
  7. data/lib/ruby_pptx/chart/categories.rb +45 -15
  8. data/lib/ruby_pptx/chart/chart.rb +77 -27
  9. data/lib/ruby_pptx/chart/combo.rb +9 -3
  10. data/lib/ruby_pptx/chart/data.rb +45 -15
  11. data/lib/ruby_pptx/chart/format.rb +229 -76
  12. data/lib/ruby_pptx/chart/workbook_writer.rb +9 -3
  13. data/lib/ruby_pptx/chart/xml_writer.rb +54 -18
  14. data/lib/ruby_pptx/chart/xy_data.rb +68 -22
  15. data/lib/ruby_pptx/dml/color.rb +27 -9
  16. data/lib/ruby_pptx/dml/effect.rb +6 -2
  17. data/lib/ruby_pptx/dml/fill.rb +42 -14
  18. data/lib/ruby_pptx/element_proxy.rb +12 -4
  19. data/lib/ruby_pptx/enum/base.rb +30 -10
  20. data/lib/ruby_pptx/enum/prog_id.rb +3 -1
  21. data/lib/ruby_pptx/geometry.rb +32 -10
  22. data/lib/ruby_pptx/image.rb +27 -9
  23. data/lib/ruby_pptx/length.rb +94 -27
  24. data/lib/ruby_pptx/media.rb +12 -4
  25. data/lib/ruby_pptx/numeric_lengths.rb +34 -9
  26. data/lib/ruby_pptx/opc/oxml.rb +7 -5
  27. data/lib/ruby_pptx/opc/pack_uri.rb +15 -5
  28. data/lib/ruby_pptx/opc/package.rb +97 -31
  29. data/lib/ruby_pptx/opc/serialized.rb +15 -5
  30. data/lib/ruby_pptx/oxml/backend.rb +585 -0
  31. data/lib/ruby_pptx/oxml/chart.rb +112 -42
  32. data/lib/ruby_pptx/oxml/content_model.rb +6 -2
  33. data/lib/ruby_pptx/oxml/dml/color.rb +6 -2
  34. data/lib/ruby_pptx/oxml/dml/fill.rb +6 -2
  35. data/lib/ruby_pptx/oxml/element.rb +138 -101
  36. data/lib/ruby_pptx/oxml/ns.rb +7 -7
  37. data/lib/ruby_pptx/oxml/presentation.rb +9 -3
  38. data/lib/ruby_pptx/oxml/section.rb +3 -1
  39. data/lib/ruby_pptx/oxml/shapes/autoshape.rb +39 -13
  40. data/lib/ruby_pptx/oxml/shapes/groupshape.rb +15 -7
  41. data/lib/ruby_pptx/oxml/shapes/other.rb +47 -16
  42. data/lib/ruby_pptx/oxml/shapes/shared.rb +96 -30
  43. data/lib/ruby_pptx/oxml/simple_types.rb +169 -52
  44. data/lib/ruby_pptx/oxml/slide.rb +24 -8
  45. data/lib/ruby_pptx/oxml/table.rb +39 -13
  46. data/lib/ruby_pptx/oxml/text.rb +37 -15
  47. data/lib/ruby_pptx/oxml/theme.rb +13 -4
  48. data/lib/ruby_pptx/package.rb +9 -3
  49. data/lib/ruby_pptx/parts/chart.rb +6 -2
  50. data/lib/ruby_pptx/parts/image.rb +12 -4
  51. data/lib/ruby_pptx/parts/media.rb +3 -1
  52. data/lib/ruby_pptx/parts/presentation.rb +15 -5
  53. data/lib/ruby_pptx/parts/slide.rb +33 -11
  54. data/lib/ruby_pptx/parts/theme.rb +3 -1
  55. data/lib/ruby_pptx/pattern_matching.rb +6 -2
  56. data/lib/ruby_pptx/presentation.rb +24 -8
  57. data/lib/ruby_pptx/section.rb +30 -10
  58. data/lib/ruby_pptx/shapes/adjustments.rb +9 -3
  59. data/lib/ruby_pptx/shapes/authoring.rb +3 -1
  60. data/lib/ruby_pptx/shapes/base.rb +74 -24
  61. data/lib/ruby_pptx/shapes/freeform.rb +36 -12
  62. data/lib/ruby_pptx/shapes/placeholder.rb +44 -15
  63. data/lib/ruby_pptx/shapes/shape.rb +91 -30
  64. data/lib/ruby_pptx/shapes/shape_tree.rb +66 -22
  65. data/lib/ruby_pptx/slide.rb +123 -41
  66. data/lib/ruby_pptx/table.rb +105 -35
  67. data/lib/ruby_pptx/text/font_metrics.rb +30 -9
  68. data/lib/ruby_pptx/text/text.rb +108 -36
  69. data/lib/ruby_pptx/theme.rb +30 -10
  70. data/lib/ruby_pptx/version.rb +1 -1
  71. data/lib/ruby_pptx.rb +8 -1
  72. metadata +22 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6cd5ac8827fcd5b81d86a1b0a0faa9f619f776dd005580589bbe88fd3f666d4a
4
- data.tar.gz: 6dafdf4a15d53bb8d4a795d3e2c0a18beebab2bd35070076d69e2a7cddddd29f
3
+ metadata.gz: 7f8ea512f0616c3815f62322b952b77ff602f61cbb907de7168308aebd26507b
4
+ data.tar.gz: '00468b31384c6b3dee28b54c28934e802eff15a7e1f9a7654d30db47c72d1e7f'
5
5
  SHA512:
6
- metadata.gz: b54ec7a6115da3d4da42ee124faefb0fee0e51d200525af1c05527fe9a6c579d279ca7da59293ce71b81a56a03c4e1567b228cb86caa234e28f1b0faf44bd9ef
7
- data.tar.gz: 2d991bf2f20c571783f5d1243d586b9e0aa9b4f2db655872c1b60a71ada29495b5e91ab587674e82fa98d34a8cb552e51f5c6fc20b76afa680691cf84bbccce8
6
+ metadata.gz: 3c696588e9468af5de7c99e48fa7129b1066579eb85d1a955e9d1994d42c71da96765465c108653734b54a4fd301a73d6ef159b6e5a2c58c984bbb2612e05e8f
7
+ data.tar.gz: 19fe045441180661f4629bf93c46a1efc12e6ac62d6d31207bd792f871ff60f9c3bcb7e96ef74fb4632d98dea56a4d24192a9425071e0c811a9db37322dce9e3
data/CHANGELOG.md CHANGED
@@ -5,6 +5,37 @@ All notable changes to this gem are recorded here. The format follows
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Before 1.0 a minor
6
6
  version may change the public API.
7
7
 
8
+ ## [0.2.0] - 2026-09-28
9
+
10
+ ### Added
11
+
12
+ - A pure-Ruby XML backend built on REXML, used automatically where Nokogiri
13
+ cannot be loaded -- notably ruby.wasm. It writes the same packages as the
14
+ Nokogiri backend; CI runs every spec under both, and builds, saves and
15
+ rereads a deck inside ruby.wasm. `Pptx.xml_backend` reports which is in
16
+ use, and `RUBY_PPTX_XML_BACKEND` chooses one explicitly.
17
+ - `rexml` is now a runtime dependency. Nokogiri remains one too.
18
+
19
+ ### Changed
20
+
21
+ - `require "ruby_pptx"` no longer requires Nokogiri directly; the XML
22
+ backend loads it.
23
+
24
+ ## [0.1.1] - 2026-09-28
25
+
26
+ ### Fixed
27
+
28
+ - `gem install ruby_pptx` failed on Windows while generating ri
29
+ documentation (`Errno::ENOENT` on a very long `.ri` path). RDoc 7 and
30
+ earlier, which Ruby 3.3 to 4.0 bundle, cannot parse endless method
31
+ definitions (`def x = ...`); each one ended its class early, so every later
32
+ class was documented inside the one before it. On other platforms the
33
+ install succeeded but the ri documentation was wrong. The library no longer
34
+ uses endless methods, and CI checks that RDoc places every class correctly.
35
+ - `Presentation.open(path)` left a file handle open for every member of the
36
+ package until garbage collection, so on Windows the `.pptx` stayed locked
37
+ and could not be deleted or overwritten after opening it.
38
+
8
39
  ## [0.1.0] - 2026-09-28
9
40
 
10
41
  The first release: a Ruby port of [python-pptx](https://github.com/scanny/python-pptx)
@@ -80,4 +111,6 @@ or where matching it cannot be done. Each case is listed, with its reason, in
80
111
  - Getters that write to the document in python-pptx -- data-label flags, axis
81
112
  and chart titles -- do not here.
82
113
 
114
+ [0.2.0]: https://github.com/Largo/ruby_pptx/releases/tag/v0.2.0
115
+ [0.1.1]: https://github.com/Largo/ruby_pptx/releases/tag/v0.1.1
83
116
  [0.1.0]: https://github.com/Largo/ruby_pptx/releases/tag/v0.1.0
data/README.md CHANGED
@@ -92,6 +92,52 @@ end
92
92
  deck.save("out.pptx")
93
93
  ```
94
94
 
95
+ Speaker notes are plain text for the common case, with the full text frame
96
+ underneath when you want formatting. Reading `notes` never creates a notes
97
+ page; assigning does.
98
+
99
+ ```ruby
100
+ slide.notes = "Mention the Q3 dip.\nThank the team."
101
+ slide.notes #=> "Mention the Q3 dip.\nThank the team."
102
+ slide.notes_slide.notes_text_frame.paragraphs.last.runs.first.font.bold = true
103
+ ```
104
+
105
+ Picture, table and chart placeholders are filled in place, so the content takes
106
+ the layout's position and size. A picture is cropped to fill its placeholder,
107
+ never stretched.
108
+
109
+ ```ruby
110
+ photo = deck.slides.add(deck.slide_layouts["Picture with Caption"])
111
+ photo.placeholders[1].insert_picture("office.jpg")
112
+ photo.placeholders[2].text_frame.text = "Our new office"
113
+
114
+ # The default template has no table or chart placeholders, so add a layout
115
+ # with both to its master.
116
+ layout = deck.slide_masters[0].slide_layouts.add("Table and Chart", type: "twoObj") do |l|
117
+ l.placeholders.add(:title)
118
+ l.placeholders.add(:table, idx: 1, at: [Pptx.inches(0.5), Pptx.inches(1.5)],
119
+ size: [Pptx.inches(4.25), Pptx.inches(4.5)])
120
+ l.placeholders.add(:chart, idx: 2, at: [Pptx.inches(5.25), Pptx.inches(1.5)],
121
+ size: [Pptx.inches(4.25), Pptx.inches(4.5)])
122
+ end
123
+
124
+ report = deck.slides.add(layout)
125
+ report.placeholders[1].insert_table(3, 2).table[0, 0].text = "Region"
126
+ report.placeholders[2].insert_chart(:column_clustered, data)
127
+ ```
128
+
129
+ Any file can be embedded as an OLE object, shown as an icon that opens it when
130
+ double-clicked. Word, Excel and PowerPoint files get Office's own icon; for
131
+ anything else, name its ProgID and optionally supply an icon.
132
+
133
+ ```ruby
134
+ sheet = slide.shapes.add_ole_object("budget.xlsx", prog_id: :xlsx, at: [x, y])
135
+ sheet.ole_format.prog_id #=> "Excel.Sheet.12"
136
+
137
+ slide.shapes.add_ole_object("report.pdf", prog_id: "AcroExch.Document", at: [x, y],
138
+ icon_file: "pdf-icon.png")
139
+ ```
140
+
95
141
  Five things python-pptx does not do: **SVG pictures**, **slide sections**,
96
142
  **paging a long table across as many slides as it needs**, **combo charts**
97
143
  with a secondary axis, and **defining a slide master in code**.
@@ -213,6 +259,36 @@ end
213
259
  Collections index like arrays — `slides[2]`, `slides[-1]`, `slides[1..3]`,
214
260
  `slides[1, 2]` — and deconstruct into array patterns.
215
261
 
262
+ ## Nokogiri, REXML and ruby.wasm
263
+
264
+ ruby_pptx reads and writes XML with Nokogiri. Where Nokogiri cannot load --
265
+ ruby.wasm, in a browser or elsewhere -- it falls back to REXML, which is pure
266
+ Ruby, with no configuration:
267
+
268
+ ```ruby
269
+ Pptx.xml_backend #=> :nokogiri, or :rexml without Nokogiri
270
+ ```
271
+
272
+ The output does not depend on the backend: CI runs every spec under both, and
273
+ the packages they write are identical part for part. REXML is slower -- in a
274
+ 60-slide deck with text, tables and charts, about 3.3 s against 0.5 s to build
275
+ and save, and 0.7 s against 0.1 s to open and read it back. Set
276
+ `RUBY_PPTX_XML_BACKEND=rexml` (or `nokogiri`) to choose explicitly.
277
+
278
+ Nokogiri stays a declared dependency, so a normal `gem install` or `bundle
279
+ install` gets the fast path. Two things to know when loading the gem in
280
+ ruby.wasm yourself:
281
+
282
+ - Load `ruby_pptx`, `rubyzip` and `rexml`, but not `nokogiri`, which is
283
+ native. A loader that installs a gem together with its runtime
284
+ dependencies will stop at Nokogiri.
285
+ - Ship the whole `lib/` directory, not only its `.rb` files: the templates
286
+ in `lib/ruby_pptx/templates` (the default deck, notes pages, icons) are
287
+ read as files.
288
+
289
+ `tools/wasm/run.mjs` runs the gem this way under ruby.wasm's browser
290
+ filesystem, and CI uses it to build, save and reread a deck on every push.
291
+
216
292
  ## Development
217
293
 
218
294
  ```bash
@@ -258,8 +334,13 @@ git tag -a v0.1.0 -m "ruby_pptx 0.1.0" && git push origin v0.1.0
258
334
 
259
335
  `.github/workflows/release.yml` runs the full CI suite, builds the gem, installs
260
336
  it into an empty gem home to prove it loads, and creates a GitHub release with
261
- the gem attached and the changelog entry as its notes. It pushes to RubyGems
262
- only once a `RUBYGEMS_API_KEY` secret is set.
337
+ the gem attached and the changelog entry as its notes.
338
+
339
+ Publishing to RubyGems is a separate step: in the Actions tab, open **Publish to
340
+ RubyGems**, choose **Run workflow**, and pick the release's tag under "Use
341
+ workflow from". It refuses to run from a branch. RubyGems' `release-gem` action
342
+ builds the tagged commit and pushes it, authenticating by RubyGems trusted
343
+ publishing rather than a stored API key.
263
344
 
264
345
  ## License
265
346
 
@@ -33,7 +33,9 @@ module Pptx
33
33
  @hover = hover
34
34
  end
35
35
 
36
- def part = @parent.part
36
+ def part
37
+ @parent.part
38
+ end
37
39
 
38
40
  # @return [Pptx::Enum::PP_ACTION] NONE when nothing happens on click
39
41
  def action
@@ -78,7 +80,9 @@ module Pptx
78
80
  #
79
81
  # Unlike {#address} this does not report a slide partname for a jump: a
80
82
  # thing called a URL should be a URL.
81
- def url = action == Enum::PP_ACTION::HYPERLINK ? address : nil
83
+ def url
84
+ action == Enum::PP_ACTION::HYPERLINK ? address : nil
85
+ end
82
86
 
83
87
  # The slide this click jumps to, or nil.
84
88
  def target_slide
@@ -107,11 +111,15 @@ module Pptx
107
111
  self
108
112
  end
109
113
 
110
- def inspect = "#<Pptx::ActionSetting #{action.name} #{address.inspect}>"
114
+ def inspect
115
+ "#<Pptx::ActionSetting #{action.name} #{address.inspect}>"
116
+ end
111
117
 
112
118
  private
113
119
 
114
- def hlink = @element.find(@hover ? "a:hlinkHover" : "a:hlinkClick")
120
+ def hlink
121
+ @element.find(@hover ? "a:hlinkHover" : "a:hlinkClick")
122
+ end
115
123
 
116
124
  def get_or_add_hlink
117
125
  @hover ? @element.get_or_add_hlinkHover : @element.get_or_add_hlinkClick
@@ -330,8 +330,12 @@ module Pptx
330
330
  end
331
331
  end
332
332
 
333
- def basename(shape_type) = self.for(shape_type)[:basename]
333
+ def basename(shape_type)
334
+ self.for(shape_type)[:basename]
335
+ end
334
336
 
335
- def default_adjustments(shape_type) = self.for(shape_type)[:default_adjustments]
337
+ def default_adjustments(shape_type)
338
+ self.for(shape_type)[:default_adjustments]
339
+ end
336
340
  end
337
341
  end
@@ -223,6 +223,8 @@ module Pptx
223
223
  end
224
224
 
225
225
  # A colour may be given as an RGBColor or as a hex string.
226
- def rgb(value) = value.is_a?(RGBColor) ? value : RGBColor.from_string(value)
226
+ def rgb(value)
227
+ value.is_a?(RGBColor) ? value : RGBColor.from_string(value)
228
+ end
227
229
  end
228
230
  end
@@ -54,14 +54,22 @@ module Pptx
54
54
  ChartDataCategory.new(label, self).tap { |category| @categories << category }
55
55
  end
56
56
 
57
- def each(&) = @categories.each(&)
57
+ def each(&)
58
+ @categories.each(&)
59
+ end
58
60
 
59
- def [](index) = @categories[index]
61
+ def [](index)
62
+ @categories[index]
63
+ end
60
64
 
61
- def size = @categories.size
65
+ def size
66
+ @categories.size
67
+ end
62
68
  alias length size
63
69
 
64
- def empty? = @categories.empty?
70
+ def empty?
71
+ @categories.empty?
72
+ end
65
73
 
66
74
  # Levels of labels: 0 with no categories, 1 for a plain list.
67
75
  #
@@ -76,7 +84,9 @@ module Pptx
76
84
  end
77
85
 
78
86
  # The number of categories actually plotted.
79
- def leaf_count = @categories.sum(&:leaf_count)
87
+ def leaf_count
88
+ @categories.sum(&:leaf_count)
89
+ end
80
90
 
81
91
  # Each level as [index, label] pairs, leaf level first. The index is the
82
92
  # leaf position where that label's run begins.
@@ -85,11 +95,17 @@ module Pptx
85
95
  end
86
96
 
87
97
  # The leaf labels in plotting order.
88
- def leaf_labels = levels.first.to_a.map(&:last)
98
+ def leaf_labels
99
+ levels.first.to_a.map(&:last)
100
+ end
89
101
 
90
- def dates? = depth == 1 && date?(first.label)
102
+ def dates?
103
+ depth == 1 && date?(first.label)
104
+ end
91
105
 
92
- def numeric? = depth == 1 && (first.label.is_a?(Numeric) || date?(first.label))
106
+ def numeric?
107
+ depth == 1 && (first.label.is_a?(Numeric) || date?(first.label))
108
+ end
93
109
 
94
110
  # The number format for the categories: as set, or a date format for
95
111
  # dates, or "General".
@@ -111,7 +127,9 @@ module Pptx
111
127
  raise Error, "category not in these categories"
112
128
  end
113
129
 
114
- def inspect = "#<Pptx::ChartDataCategories depth=#{depth} #{leaf_labels.inspect}>"
130
+ def inspect
131
+ "#<Pptx::ChartDataCategories depth=#{depth} #{leaf_labels.inspect}>"
132
+ end
115
133
 
116
134
  private
117
135
 
@@ -121,7 +139,9 @@ module Pptx
121
139
  lower + [categories.map { |category| [category.idx, category.label] }]
122
140
  end
123
141
 
124
- def date?(value) = value.is_a?(Date) || value.is_a?(Time)
142
+ def date?(value)
143
+ value.is_a?(Date) || value.is_a?(Time)
144
+ end
125
145
  end
126
146
 
127
147
  # One category, or a group of them when it has sub-categories.
@@ -135,7 +155,9 @@ module Pptx
135
155
  end
136
156
 
137
157
  # The label; "" for a category given as nil.
138
- def label = @label.nil? ? "" : @label
158
+ def label
159
+ @label.nil? ? "" : @label
160
+ end
139
161
 
140
162
  # @return [ChartDataCategory] the new sub-category
141
163
  def add_sub_category(label)
@@ -161,10 +183,14 @@ module Pptx
161
183
  depths.first + 1
162
184
  end
163
185
 
164
- def leaf_count = @sub_categories.empty? ? 1 : @sub_categories.sum(&:leaf_count)
186
+ def leaf_count
187
+ @sub_categories.empty? ? 1 : @sub_categories.sum(&:leaf_count)
188
+ end
165
189
 
166
190
  # The leaf position at which this category's run begins.
167
- def idx = @parent.index_of(self)
191
+ def idx
192
+ @parent.index_of(self)
193
+ end
168
194
 
169
195
  # @api private
170
196
  def index_of(sub_category)
@@ -190,9 +216,13 @@ module Pptx
190
216
  end
191
217
 
192
218
  # The label as it appears in the category cache and worksheet.
193
- def to_s = label.to_s
219
+ def to_s
220
+ label.to_s
221
+ end
194
222
 
195
- def inspect = "#<Pptx::ChartDataCategory #{label.inspect}>"
223
+ def inspect
224
+ "#<Pptx::ChartDataCategory #{label.inspect}>"
225
+ end
196
226
 
197
227
  private
198
228
 
@@ -31,10 +31,14 @@ module Pptx
31
31
  # python-pptx's PlotTypeInspector does.
32
32
  #
33
33
  # @return [Pptx::Enum::Member] a member of XL_CHART_TYPE
34
- def chart_type = ChartTypeInspector.chart_type(plot_area.plot_elements.first)
34
+ def chart_type
35
+ ChartTypeInspector.chart_type(plot_area.plot_elements.first)
36
+ end
35
37
 
36
38
  # The chart style number, 1 to 48, or nil when none is set.
37
- def chart_style = @element.style&.val
39
+ def chart_style
40
+ @element.style&.val
41
+ end
38
42
 
39
43
  def chart_style=(value)
40
44
  @element.remove_style
@@ -42,7 +46,9 @@ module Pptx
42
46
  end
43
47
 
44
48
  # The default font for all text in the chart, created on first use.
45
- def font = @font ||= Font.new(@element.defRPr)
49
+ def font
50
+ @font ||= Font.new(@element.defRPr)
51
+ end
46
52
 
47
53
  # Every series in the chart: plot by plot, and within a plot in the order
48
54
  # it draws them.
@@ -65,7 +71,9 @@ module Pptx
65
71
  end
66
72
 
67
73
  # Whether a legend is drawn.
68
- def legend? = !chart_element.legend.nil?
74
+ def legend?
75
+ !chart_element.legend.nil?
76
+ end
69
77
 
70
78
  def legend=(value)
71
79
  value ? chart_element.get_or_add_legend : chart_element.remove_legend
@@ -78,7 +86,9 @@ module Pptx
78
86
  element.nil? ? nil : (@legend ||= ChartLegend.new(element))
79
87
  end
80
88
 
81
- def title? = !chart_element.title.nil?
89
+ def title?
90
+ !chart_element.title.nil?
91
+ end
82
92
 
83
93
  # @return [ChartTitle, nil]
84
94
  def title
@@ -105,7 +115,9 @@ module Pptx
105
115
  #
106
116
  # Nearly every chart has exactly one; a combo chart has several, which is
107
117
  # why this is a collection rather than a property of the chart.
108
- def plots = plot_area.plot_elements.map { |element| ChartPlot.new(element, self) }
118
+ def plots
119
+ plot_area.plot_elements.map { |element| ChartPlot.new(element, self) }
120
+ end
109
121
 
110
122
  # The horizontal axis: a category or date axis, or, on an XY or bubble
111
123
  # chart, the first value axis. nil for a chart with no axes, such as a pie.
@@ -129,7 +141,9 @@ module Pptx
129
141
  ValueAxis.new(axes[axes.size > 1 ? 1 : 0])
130
142
  end
131
143
 
132
- def value_axes = plot_area.value_axes.map { |element| ValueAxis.new(element) }
144
+ def value_axes
145
+ plot_area.value_axes.map { |element| ValueAxis.new(element) }
146
+ end
133
147
 
134
148
  # The category labels cached in the chart XML.
135
149
  def categories
@@ -139,13 +153,19 @@ module Pptx
139
153
  first.xpath("./c:cat//c:pt/c:v").map(&:text)
140
154
  end
141
155
 
142
- def inspect = "#<Pptx::Chart #{chart_type.name} series=#{series.size}>"
156
+ def inspect
157
+ "#<Pptx::Chart #{chart_type.name} series=#{series.size}>"
158
+ end
143
159
 
144
160
  private
145
161
 
146
- def chart_element = @element.chart
162
+ def chart_element
163
+ @element.chart
164
+ end
147
165
 
148
- def plot_area = chart_element.plotArea
166
+ def plot_area
167
+ chart_element.plotArea
168
+ end
149
169
  end
150
170
 
151
171
  # Works out the full chart type from a plot element, after python-pptx's
@@ -200,11 +220,17 @@ module Pptx
200
220
  rule.call(plot)
201
221
  end
202
222
 
203
- def line_markers?(plot) = plot.xpath('c:ser/c:marker/c:symbol[@val="none"]').empty?
223
+ def line_markers?(plot)
224
+ plot.xpath('c:ser/c:marker/c:symbol[@val="none"]').empty?
225
+ end
204
226
 
205
- def exploded?(plot) = !plot.xpath("./c:ser/c:explosion").empty?
227
+ def exploded?(plot)
228
+ !plot.xpath("./c:ser/c:explosion").empty?
229
+ end
206
230
 
207
- def first_symbol(plot) = plot.xpath("c:ser/c:marker/c:symbol").first&.get("val")
231
+ def first_symbol(plot)
232
+ plot.xpath("c:ser/c:marker/c:symbol").first&.get("val")
233
+ end
208
234
 
209
235
  def bubble(plot)
210
236
  bubble3d = plot.xpath("c:ser/c:bubble3D").first
@@ -260,17 +286,27 @@ module Pptx
260
286
  end
261
287
 
262
288
  # The fill and outline of this series.
263
- def format = @format ||= ChartFormat.new(@element)
289
+ def format
290
+ @format ||= ChartFormat.new(@element)
291
+ end
264
292
 
265
293
  # The series' chart-wide index.
266
- def index = @element.idx.val
294
+ def index
295
+ @element.idx.val
296
+ end
267
297
 
268
- def name = @element.xpath("./c:tx//c:pt/c:v").first&.text.to_s
298
+ def name
299
+ @element.xpath("./c:tx//c:pt/c:v").first&.text.to_s
300
+ end
269
301
 
270
302
  # Cached values, with nil where the chart records a gap.
271
- def values = cached(@element.val)
303
+ def values
304
+ cached(@element.val)
305
+ end
272
306
 
273
- def inspect = "#<#{self.class.name} #{name.inspect}>"
307
+ def inspect
308
+ "#<#{self.class.name} #{name.inspect}>"
309
+ end
274
310
 
275
311
  private
276
312
 
@@ -286,15 +322,21 @@ module Pptx
286
322
  # Data labels and per-point formatting, for series plotted by category.
287
323
  module CategorySeriesFeatures
288
324
  # This series' own data labels, created on first use.
289
- def data_labels = @data_labels ||= ChartDataLabels.new(@element.get_or_add_dLbls)
325
+ def data_labels
326
+ @data_labels ||= ChartDataLabels.new(@element.get_or_add_dLbls)
327
+ end
290
328
 
291
329
  # The points of this series, one per category.
292
- def points = ChartPoints.new(@element, @element.cat_ptCount_val)
330
+ def points
331
+ ChartPoints.new(@element, @element.cat_ptCount_val)
332
+ end
293
333
  end
294
334
 
295
335
  # Markers, for the kinds of series that draw them.
296
336
  module MarkerFeatures
297
- def marker = @marker ||= Marker.new(@element)
337
+ def marker
338
+ @marker ||= Marker.new(@element)
339
+ end
298
340
  end
299
341
 
300
342
  class AreaSeriesView < ChartSeriesView
@@ -310,7 +352,9 @@ module Pptx
310
352
 
311
353
  # Whether a negative bar is drawn in inverted colours. An absent
312
354
  # `c:invertIfNegative` reads as true, the schema default.
313
- def invert_if_negative? = @element.invertIfNegative.nil? || @element.invertIfNegative.val
355
+ def invert_if_negative?
356
+ @element.invertIfNegative.nil? || @element.invertIfNegative.val
357
+ end
314
358
 
315
359
  def invert_if_negative=(value)
316
360
  @element.get_or_add_invertIfNegative.val = value ? true : false
@@ -323,7 +367,9 @@ module Pptx
323
367
 
324
368
  # Whether the line is drawn as a smooth curve. An absent `c:smooth` reads
325
369
  # as true, the schema default.
326
- def smooth? = @element.smooth.nil? || @element.smooth.val
370
+ def smooth?
371
+ @element.smooth.nil? || @element.smooth.val
372
+ end
327
373
 
328
374
  def smooth=(value)
329
375
  @element.get_or_add_smooth.val = value ? true : false
@@ -339,9 +385,13 @@ module Pptx
339
385
  class XySeriesView < ChartSeriesView
340
386
  include MarkerFeatures
341
387
 
342
- def values = cached(@element.yVal)
388
+ def values
389
+ cached(@element.yVal)
390
+ end
343
391
 
344
- def points = ChartPoints.new(@element, [@element.xVal_ptCount_val, @element.yVal_ptCount_val].min)
392
+ def points
393
+ ChartPoints.new(@element, [@element.xVal_ptCount_val, @element.yVal_ptCount_val].min)
394
+ end
345
395
  end
346
396
 
347
397
  class BubbleSeriesView < XySeriesView
@@ -387,10 +437,10 @@ module Pptx
387
437
 
388
438
  next_index = plot_area.series_elements.size
389
439
  count.times do |offset|
390
- copy = last.parent.build_from_xml(last.node.to_xml)
440
+ copy = last.parent.import(last)
391
441
  copy.get_or_add_idx.val = next_index + offset
392
442
  copy.get_or_add_order.val = next_index + offset
393
- last.node.add_next_sibling(copy.node)
443
+ last.add_next_sibling(copy)
394
444
  last = copy
395
445
  end
396
446
  end
@@ -62,7 +62,9 @@ module Pptx
62
62
  self
63
63
  end
64
64
 
65
- def secondary_axis? = @specs.any?(&:secondary_axis)
65
+ def secondary_axis?
66
+ @specs.any?(&:secondary_axis)
67
+ end
66
68
 
67
69
  private
68
70
 
@@ -122,7 +124,9 @@ module Pptx
122
124
 
123
125
  private
124
126
 
125
- def plot_elements = @builder.specs.map { |spec| plot_element(spec) }.join
127
+ def plot_elements
128
+ @builder.specs.map { |spec| plot_element(spec) }.join
129
+ end
126
130
 
127
131
  def plot_element(spec)
128
132
  family = ChartXmlWriter.family_of(spec.chart_type)
@@ -141,7 +145,9 @@ module Pptx
141
145
  XML
142
146
  end
143
147
 
144
- def plot_tag(family) = { bar: "c:barChart", line: "c:lineChart", area: "c:areaChart" }[family]
148
+ def plot_tag(family)
149
+ { bar: "c:barChart", line: "c:lineChart", area: "c:areaChart" }[family]
150
+ end
145
151
 
146
152
  def bar_plot_body(spec)
147
153
  direction = ChartXmlWriter::BarChartWriter::BAR_TYPES.include?(spec.chart_type.name) ? "bar" : "col"