uniword 1.7.1 → 1.7.3

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/lib/uniword/accessibility/rules/image_alt_text_rule.rb +36 -13
  4. data/lib/uniword/builder/image_builder.rb +2 -0
  5. data/lib/uniword/builder/paragraph_builder.rb +5 -6
  6. data/lib/uniword/builder/sdt_builder.rb +12 -4
  7. data/lib/uniword/builder/style_builder.rb +5 -5
  8. data/lib/uniword/builder/table_builder.rb +2 -2
  9. data/lib/uniword/builder/table_cell_builder.rb +3 -3
  10. data/lib/uniword/builder/table_row_builder.rb +2 -1
  11. data/lib/uniword/cli/comments_cli.rb +173 -0
  12. data/lib/uniword/cli/diff_cli.rb +60 -0
  13. data/lib/uniword/cli/main.rb +104 -0
  14. data/lib/uniword/cli/plain_text_command.rb +101 -0
  15. data/lib/uniword/cli/review_cli.rb +173 -34
  16. data/lib/uniword/cli/revisions_cli.rb +88 -0
  17. data/lib/uniword/cli/template_cli.rb +81 -0
  18. data/lib/uniword/converter/plain_text.rb +425 -0
  19. data/lib/uniword/converter/plain_text_result.rb +112 -0
  20. data/lib/uniword/converter.rb +11 -0
  21. data/lib/uniword/diff/document_differ.rb +2 -88
  22. data/lib/uniword/diff/lcs.rb +101 -0
  23. data/lib/uniword/diff/redline_result.rb +94 -0
  24. data/lib/uniword/diff/redliner.rb +319 -0
  25. data/lib/uniword/diff.rb +3 -0
  26. data/lib/uniword/errors.rb +33 -0
  27. data/lib/uniword/mhtml/word_css.rb +16 -8
  28. data/lib/uniword/omml_integration.rb +62 -0
  29. data/lib/uniword/ooxml/types/ooxml_boolean.rb +19 -7
  30. data/lib/uniword/ooxml/types/ooxml_boolean_optional.rb +4 -8
  31. data/lib/uniword/properties/boolean_element_factory.rb +48 -3
  32. data/lib/uniword/properties/boolean_formatting.rb +4 -0
  33. data/lib/uniword/properties/outline.rb +4 -13
  34. data/lib/uniword/quality/rules/image_alt_text_rule.rb +22 -43
  35. data/lib/uniword/review/review_manager.rb +128 -1
  36. data/lib/uniword/template/fill_result.rb +121 -0
  37. data/lib/uniword/template/filler.rb +292 -0
  38. data/lib/uniword/template.rb +2 -0
  39. data/lib/uniword/transformation/mhtml_element_renderer.rb +9 -3
  40. data/lib/uniword/transformation/ooxml_to_html_converter.rb +8 -3
  41. data/lib/uniword/version.rb +1 -1
  42. data/lib/uniword/wordprocessingml/deleted_paragraph_mark.rb +32 -0
  43. data/lib/uniword/wordprocessingml/document_root.rb +40 -6
  44. data/lib/uniword/wordprocessingml/drawing.rb +51 -0
  45. data/lib/uniword/wordprocessingml/inserted_paragraph_mark.rb +32 -0
  46. data/lib/uniword/wordprocessingml/paragraph_properties.rb +128 -34
  47. data/lib/uniword/wordprocessingml/run_properties/conversion.rb +30 -9
  48. data/lib/uniword/wordprocessingml/run_properties/predicates.rb +25 -50
  49. data/lib/uniword/wordprocessingml/run_properties/yaml_transforms.rb +61 -44
  50. data/lib/uniword/wordprocessingml/run_properties.rb +10 -0
  51. data/lib/uniword/wordprocessingml/structured_document_tag/showing_placeholder_header.rb +13 -2
  52. data/lib/uniword/wordprocessingml/structured_document_tag/temporary.rb +13 -2
  53. data/lib/uniword/wordprocessingml/style.rb +43 -40
  54. data/lib/uniword/wordprocessingml/table_row_properties.rb +16 -12
  55. data/lib/uniword/wordprocessingml/yaml_writer.rb +24 -0
  56. data/lib/uniword/wordprocessingml.rb +17 -4
  57. data/lib/uniword.rb +19 -0
  58. metadata +21 -6
@@ -9,6 +9,8 @@ module Uniword
9
9
  # Represents w:pPr element containing paragraph-level formatting.
10
10
  # Used in StyleSets and document paragraph elements.
11
11
  class ParagraphProperties < Lutaml::Model::Serializable
12
+ include YamlWriter
13
+
12
14
  # Pattern 0: ATTRIBUTES FIRST, then XML mappings
13
15
 
14
16
  # Simple element attributes (OOXML w:val attributes stored in
@@ -22,8 +24,9 @@ module Uniword
22
24
  attribute :outline_level, Properties::OutlineLevel
23
25
  attribute :numbering_properties, Properties::NumberingProperties
24
26
 
25
- # Complex spacing object
26
- attribute :spacing, Properties::Spacing
27
+ # Complex spacing objects. Word can emit more than one w:spacing in a
28
+ # single w:pPr, so this is a collection.
29
+ attribute :spacing, Properties::Spacing, collection: true
27
30
 
28
31
  # Complex indentation object
29
32
  attribute :indentation, Properties::Indentation
@@ -64,10 +67,15 @@ module Uniword
64
67
 
65
68
  # Spacing options
66
69
  attribute :contextual_spacing, Properties::ContextualSpacing
67
- attribute :suppress_line_numbers, :boolean, default: -> { false }
70
+
71
+ # w:suppressLineNumbers and w:bidi are ST_OnOff elements, not plain
72
+ # booleans. Declared as :boolean they read "" for every spelling, so an
73
+ # explicitly-off flag was indistinguishable from an on one, and a
74
+ # parsed element was dropped on the way back out.
75
+ attribute :suppress_line_numbers_wrapper, Properties::SuppressLineNumbers
68
76
 
69
77
  # Bidirectional text
70
- attribute :bidirectional, :boolean, default: -> { false }
78
+ attribute :bidirectional_wrapper, Properties::Bidi
71
79
 
72
80
  # East Asian typography
73
81
  attribute :auto_space_de, Properties::AutoSpaceDE
@@ -104,11 +112,15 @@ module Uniword
104
112
  to: :yaml_page_break_before_to }
105
113
  map "outline_level",
106
114
  with: { from: :yaml_outline_level_from, to: :yaml_outline_level_to }
107
- map "suppress_line_numbers", to: :suppress_line_numbers
115
+ map "suppress_line_numbers",
116
+ with: { from: :yaml_suppress_line_numbers_from,
117
+ to: :yaml_suppress_line_numbers_to }
108
118
  map "contextual_spacing",
109
119
  with: { from: :yaml_contextual_spacing_from,
110
120
  to: :yaml_contextual_spacing_to }
111
- map "bidirectional", to: :bidirectional
121
+ map "bidirectional",
122
+ with: { from: :yaml_bidirectional_from,
123
+ to: :yaml_bidirectional_to }
112
124
  map "indent_left", to: :indent_left
113
125
  map "indent_right", to: :indent_right
114
126
  map "indent_first_line", to: :indent_first_line
@@ -117,7 +129,9 @@ module Uniword
117
129
  end
118
130
 
119
131
  # YAML transform methods (instance methods called by lutaml-model's
120
- # `with:` transform mechanism)
132
+ # `with:` transform mechanism). Every writer assigns through
133
+ # YamlWriter#yaml_put; see that module for why.
134
+
121
135
  def yaml_style_from(instance, value)
122
136
  if value
123
137
  instance.style = [
@@ -126,40 +140,42 @@ module Uniword
126
140
  end
127
141
  end
128
142
 
129
- def yaml_style_to(instance, _doc)
130
- Array(instance.style).first&.value
143
+ def yaml_style_to(instance, doc)
144
+ yaml_put(doc, "style", Array(instance.style).first&.value)
131
145
  end
132
146
 
133
147
  def yaml_alignment_from(instance, value)
134
148
  instance.alignment = Properties::Alignment.new(value: value) if value
135
149
  end
136
150
 
137
- def yaml_alignment_to(instance, _doc)
138
- instance.alignment&.value
151
+ def yaml_alignment_to(instance, doc)
152
+ yaml_put(doc, "alignment", instance.alignment&.value)
139
153
  end
140
154
 
141
155
  def yaml_keep_next_from(instance, value)
142
156
  instance.keep_next_wrapper = Properties::KeepNext.new(value: value) unless value.nil?
143
157
  end
144
158
 
145
- def yaml_keep_next_to(instance, _doc)
146
- instance.keep_next_wrapper&.value
159
+ # Toggles read through BooleanElement#on?, the same reading the XML and
160
+ # predicate consumers get, so w:val="0" and w:val="off" stay off.
161
+ def yaml_keep_next_to(instance, doc)
162
+ yaml_put(doc, "keep_next", instance.keep_next_wrapper&.on?)
147
163
  end
148
164
 
149
165
  def yaml_keep_lines_from(instance, value)
150
166
  instance.keep_lines_wrapper = Properties::KeepLines.new(value: value) unless value.nil?
151
167
  end
152
168
 
153
- def yaml_keep_lines_to(instance, _doc)
154
- instance.keep_lines_wrapper&.value
169
+ def yaml_keep_lines_to(instance, doc)
170
+ yaml_put(doc, "keep_lines", instance.keep_lines_wrapper&.on?)
155
171
  end
156
172
 
157
173
  def yaml_outline_level_from(instance, value)
158
174
  instance.outline_level = Properties::OutlineLevel.new(value: value.to_i) if value
159
175
  end
160
176
 
161
- def yaml_outline_level_to(instance, _doc)
162
- instance.outline_level&.value
177
+ def yaml_outline_level_to(instance, doc)
178
+ yaml_put(doc, "outline_level", instance.outline_level&.value)
163
179
  end
164
180
 
165
181
  def yaml_contextual_spacing_from(instance, value)
@@ -168,8 +184,8 @@ module Uniword
168
184
  instance.contextual_spacing = Properties::ContextualSpacing.new(value: value)
169
185
  end
170
186
 
171
- def yaml_contextual_spacing_to(instance, _doc)
172
- instance.contextual_spacing&.value
187
+ def yaml_contextual_spacing_to(instance, doc)
188
+ yaml_put(doc, "contextual_spacing", instance.contextual_spacing&.on?)
173
189
  end
174
190
 
175
191
  def yaml_page_break_before_from(instance, value)
@@ -178,8 +194,8 @@ module Uniword
178
194
  instance.page_break_before_wrapper = Properties::PageBreakBefore.new(value: value)
179
195
  end
180
196
 
181
- def yaml_page_break_before_to(instance, _doc)
182
- instance.page_break_before_wrapper&.value
197
+ def yaml_page_break_before_to(instance, doc)
198
+ yaml_put(doc, "page_break_before", instance.page_break_before_wrapper&.on?)
183
199
  end
184
200
 
185
201
  def yaml_widow_control_from(instance, value)
@@ -188,8 +204,44 @@ module Uniword
188
204
  instance.widow_control_wrapper = Properties::WidowControl.new(value: value)
189
205
  end
190
206
 
191
- def yaml_widow_control_to(instance, _doc)
192
- instance.widow_control_wrapper&.value
207
+ def yaml_widow_control_to(instance, doc)
208
+ yaml_put(doc, "widow_control", instance.widow_control_wrapper&.on?)
209
+ end
210
+
211
+ def yaml_suppress_line_numbers_from(instance, value)
212
+ return if value.nil?
213
+
214
+ instance.suppress_line_numbers_wrapper =
215
+ Properties::SuppressLineNumbers.new(value: value)
216
+ end
217
+
218
+ def yaml_suppress_line_numbers_to(instance, doc)
219
+ yaml_put(doc, "suppress_line_numbers",
220
+ instance.suppress_line_numbers_wrapper&.on?)
221
+ end
222
+
223
+ def yaml_bidirectional_from(instance, value)
224
+ return if value.nil?
225
+
226
+ instance.bidirectional_wrapper = Properties::Bidi.new(value: value)
227
+ end
228
+
229
+ def yaml_bidirectional_to(instance, doc)
230
+ yaml_put(doc, "bidirectional", instance.bidirectional_wrapper&.on?)
231
+ end
232
+
233
+ # Is line numbering suppressed for this paragraph?
234
+ #
235
+ # @return [Boolean] false when w:suppressLineNumbers is absent or off
236
+ def suppress_line_numbers
237
+ suppress_line_numbers_wrapper&.on? || false
238
+ end
239
+
240
+ # Does this paragraph run right-to-left?
241
+ #
242
+ # @return [Boolean] false when w:bidi is absent or off
243
+ def bidirectional
244
+ bidirectional_wrapper&.on? || false
193
245
  end
194
246
 
195
247
  # XML mappings come AFTER attributes
@@ -214,8 +266,9 @@ module Uniword
214
266
  # Numbering properties (wrapped in w:numPr)
215
267
  map_element "numPr", to: :numbering_properties, render_nil: false
216
268
 
217
- # Suppress line numbers (only render if true)
218
- map_element "suppressLineNumbers", to: :suppress_line_numbers, render_nil: false,
269
+ # Suppress line numbers (only render if present)
270
+ map_element "suppressLineNumbers", to: :suppress_line_numbers_wrapper,
271
+ render_nil: false,
219
272
  render_default: false
220
273
 
221
274
  # Borders (complex object)
@@ -233,8 +286,8 @@ module Uniword
233
286
  map_element "autoSpaceDN", to: :auto_space_dn, render_nil: false,
234
287
  render_default: false
235
288
 
236
- # Bidirectional (only render if true)
237
- map_element "bidi", to: :bidirectional, render_nil: false,
289
+ # Bidirectional (only render if present)
290
+ map_element "bidi", to: :bidirectional_wrapper, render_nil: false,
238
291
  render_default: false
239
292
 
240
293
  # Right indent adjustment
@@ -271,6 +324,9 @@ module Uniword
271
324
  keep_lines_val = attrs.key?(:keep_lines) ? attrs.delete(:keep_lines) : nil
272
325
  page_break_before_val = attrs.key?(:page_break_before) ? attrs.delete(:page_break_before) : nil
273
326
  widow_control_val = attrs.key?(:widow_control) ? attrs.delete(:widow_control) : nil
327
+ suppress_line_numbers_val =
328
+ attrs.key?(:suppress_line_numbers) ? attrs.delete(:suppress_line_numbers) : nil
329
+ bidirectional_val = attrs.key?(:bidirectional) ? attrs.delete(:bidirectional) : nil
274
330
  style_val = attrs.key?(:style) ? attrs.delete(:style) : nil
275
331
 
276
332
  super
@@ -290,12 +346,47 @@ module Uniword
290
346
  self.widow_control_wrapper =
291
347
  Properties::WidowControl.new(value: widow_control_val)
292
348
  end
349
+ unless suppress_line_numbers_val.nil?
350
+ self.suppress_line_numbers_wrapper =
351
+ Properties::SuppressLineNumbers.new(value: suppress_line_numbers_val)
352
+ end
353
+ unless bidirectional_val.nil?
354
+ self.bidirectional_wrapper =
355
+ Properties::Bidi.new(value: bidirectional_val)
356
+ end
293
357
  self.style = style_val if style_val
294
358
 
295
359
  # Convert flat attributes to wrapper objects (Pattern 0: after super)
296
360
  convert_flat_attributes!
297
361
  end
298
362
 
363
+ # First w:spacing entry, appending one when absent
364
+ #
365
+ # Word can emit more than one w:spacing inside a single w:pPr, so the
366
+ # attribute is a collection. Callers setting individual spacing fields
367
+ # want the first entry.
368
+ #
369
+ # Each field named in +clearing+ is dropped from the later entries. A
370
+ # value written into the first entry while a later one still carries its
371
+ # own would emit two contradictory values, and a last-wins reader keeps
372
+ # the stale one.
373
+ #
374
+ # @param clearing [Array<Symbol>] fields the caller is about to write
375
+ # @return [Properties::Spacing] the first spacing entry
376
+ def ensure_spacing(*clearing)
377
+ entries = Array(spacing)
378
+ if entries.empty?
379
+ entries = [Properties::Spacing.new]
380
+ self.spacing = entries
381
+ end
382
+
383
+ entries.drop(1).each do |stale|
384
+ clearing.each { |field| stale.public_send(:"#{field}=", nil) }
385
+ end
386
+
387
+ entries.first
388
+ end
389
+
299
390
  # Convert flat convenience attributes to proper wrapper objects
300
391
  # This handles cases like ParagraphProperties.new(spacing_before: 120)
301
392
  # where the flat attribute is set but the wrapper object is not
@@ -303,13 +394,16 @@ module Uniword
303
394
  # Alignment - convert string to Alignment wrapper
304
395
  self.alignment = Properties::Alignment.new(value: @alignment) if @alignment.is_a?(String)
305
396
 
306
- # Spacing - create spacing object from flat spacing attributes
307
- if (@spacing_before || @spacing_after || @line_spacing || @line_rule) && !@spacing
308
- self.spacing = Properties::Spacing.new
309
- spacing.before = @spacing_before if @spacing_before
310
- spacing.after = @spacing_after if @spacing_after
311
- spacing.line = @line_spacing.to_i if @line_spacing
312
- spacing.line_rule = @line_rule if @line_rule
397
+ # Spacing - create spacing object from flat spacing attributes.
398
+ # Array() covers both absent forms: nil when built, [] when parsed.
399
+ if (@spacing_before || @spacing_after || @line_spacing || @line_rule) &&
400
+ Array(@spacing).empty?
401
+ entry = Properties::Spacing.new
402
+ entry.before = @spacing_before if @spacing_before
403
+ entry.after = @spacing_after if @spacing_after
404
+ entry.line = @line_spacing.to_i if @line_spacing
405
+ entry.line_rule = @line_rule if @line_rule
406
+ self.spacing = [entry]
313
407
  end
314
408
 
315
409
  # Indentation - create indentation object from flat indent attributes
@@ -9,6 +9,25 @@ module Uniword
9
9
  # (e.g., RunProperties.new(bold: true)) and the wrapper
10
10
  # objects (e.g., Properties::Bold) required by lutaml-model.
11
11
  module Conversion
12
+ # Every rPr attribute whose value is an ST_OnOff toggle element.
13
+ BOOLEAN_WRAPPERS = {
14
+ bold: Properties::Bold,
15
+ bold_cs: Properties::BoldCs,
16
+ italic: Properties::Italic,
17
+ italic_cs: Properties::ItalicCs,
18
+ strike: Properties::Strike,
19
+ double_strike: Properties::DoubleStrike,
20
+ small_caps: Properties::SmallCaps,
21
+ caps: Properties::Caps,
22
+ hidden: Properties::Vanish,
23
+ no_proof: Properties::NoProof,
24
+ web_hidden: Properties::WebHidden,
25
+ shadow: Properties::Shadow,
26
+ emboss: Properties::Emboss,
27
+ imprint: Properties::Imprint,
28
+ outline: Properties::Outline,
29
+ }.freeze
30
+
12
31
  def initialize(attrs = {})
13
32
  # Extract flat convenience keys before super (lutaml-model ignores
14
33
  # unknown keys). These are converted to proper wrapper objects below.
@@ -96,16 +115,18 @@ module Uniword
96
115
  shading.pattern = type_val if type_val
97
116
  end
98
117
 
118
+ # Wrap every ST_OnOff toggle handed in as a primitive.
119
+ #
120
+ # `false` is a value, not an absence: testing truthiness here left a
121
+ # raw Ruby false on the attribute and to_xml then died on it.
99
122
  def convert_boolean_attrs!
100
- @bold = Properties::Bold.new(value: @bold) if @bold && !@bold.is_a?(Properties::Bold)
101
- @bold_cs = Properties::BoldCs.new(value: @bold_cs) if @bold_cs && !@bold_cs.is_a?(Properties::BoldCs)
102
- @italic = Properties::Italic.new(value: @italic) if @italic && !@italic.is_a?(Properties::Italic)
103
- @italic_cs = Properties::ItalicCs.new(value: @italic_cs) if @italic_cs && !@italic_cs.is_a?(Properties::ItalicCs)
104
- @strike = Properties::Strike.new(value: @strike) if @strike && !@strike.is_a?(Properties::Strike)
105
- @double_strike = Properties::DoubleStrike.new(value: @double_strike) if @double_strike && !@double_strike.is_a?(Properties::DoubleStrike)
106
- @small_caps = Properties::SmallCaps.new(value: @small_caps) if @small_caps && !@small_caps.is_a?(Properties::SmallCaps)
107
- @caps = Properties::Caps.new(value: @caps) if @caps && !@caps.is_a?(Properties::Caps)
108
- @hidden = Properties::Vanish.new(value: @hidden) if @hidden && !@hidden.is_a?(Properties::Vanish)
123
+ BOOLEAN_WRAPPERS.each do |name, klass|
124
+ current = instance_variable_get(:"@#{name}")
125
+ next if current.nil? || current.is_a?(klass) ||
126
+ Lutaml::Model::Utils.uninitialized?(current)
127
+
128
+ instance_variable_set(:"@#{name}", klass.new(val: current))
129
+ end
109
130
  end
110
131
 
111
132
  def convert_style_attr!
@@ -5,30 +5,19 @@ module Uniword
5
5
  class RunProperties < Lutaml::Model::Serializable
6
6
  # Boolean predicate methods for RunProperties.
7
7
  #
8
- # Unwraps boolean property objects and returns true/false.
8
+ # Every predicate reads through BooleanElement#on?, so all of them
9
+ # agree on what "0", "off" and an absent w:val mean.
9
10
  module Predicates
10
11
  def bold?
11
- val = bold
12
- return false if val.nil?
13
-
14
- val = val.value if val.is_a?(Uniword::Properties::BooleanElement)
15
- val == true
12
+ toggle_on?(bold)
16
13
  end
17
14
 
18
15
  def italic?
19
- val = italic
20
- return false if val.nil?
21
-
22
- val = val.value if val.is_a?(Uniword::Properties::BooleanElement)
23
- val == true
16
+ toggle_on?(italic)
24
17
  end
25
18
 
26
19
  def strike?
27
- val = strike
28
- return false if val.nil?
29
-
30
- val = val.value if val.is_a?(Uniword::Properties::BooleanElement)
31
- val == true
20
+ toggle_on?(strike)
32
21
  end
33
22
 
34
23
  def all_caps
@@ -36,59 +25,45 @@ module Uniword
36
25
  end
37
26
 
38
27
  def caps?
39
- val = caps
40
- return false if val.nil?
41
-
42
- val = val.value if val.is_a?(Uniword::Properties::BooleanElement)
43
- val == true
28
+ toggle_on?(caps)
44
29
  end
45
30
 
46
31
  def small_caps?
47
- val = small_caps
48
- return false if val.nil?
49
-
50
- val = val.value if val.is_a?(Properties::SmallCaps)
51
- val == true
32
+ toggle_on?(small_caps)
52
33
  end
53
34
 
54
35
  def shadow?
55
- val = shadow
56
- return false if val.nil?
57
-
58
- val = val.value if val.is_a?(Properties::Shadow)
59
- val == true
36
+ toggle_on?(shadow)
60
37
  end
61
38
 
62
39
  def imprint?
63
- val = imprint
64
- return false if val.nil?
65
-
66
- val = val.value if val.is_a?(Properties::Imprint)
67
- val == true
40
+ toggle_on?(imprint)
68
41
  end
69
42
 
70
43
  def emboss?
71
- val = emboss
72
- return false if val.nil?
73
-
74
- val = val.value if val.is_a?(Properties::Emboss)
75
- val == true
44
+ toggle_on?(emboss)
76
45
  end
77
46
 
78
47
  def hidden?
79
- val = hidden
80
- return false if val.nil?
81
-
82
- val = val.value if val.is_a?(Properties::Vanish)
83
- val == true
48
+ toggle_on?(hidden)
84
49
  end
85
50
 
86
51
  def outline?
87
- val = outline
88
- return false if val.nil?
52
+ toggle_on?(outline)
53
+ end
54
+
55
+ private
56
+
57
+ # Toggles arrive as BooleanElement wrappers when parsed and as plain
58
+ # booleans when a caller assigns one directly.
59
+ #
60
+ # @param toggle [Object, nil] Wrapper, boolean, or nil when absent
61
+ # @return [Boolean] true when the toggle is on
62
+ def toggle_on?(toggle)
63
+ return false if toggle.nil?
64
+ return toggle.on? if toggle.is_a?(Uniword::Properties::BooleanElement)
89
65
 
90
- val = val.val if val.is_a?(Properties::Outline)
91
- val != "false"
66
+ toggle == true
92
67
  end
93
68
  end
94
69
  end