berkeley_library-tind 0.4.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 (162) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/build.yml +18 -0
  3. data/.gitignore +388 -0
  4. data/.idea/inspectionProfiles/Project_Default.xml +20 -0
  5. data/.idea/misc.xml +4 -0
  6. data/.idea/modules.xml +8 -0
  7. data/.idea/tind.iml +138 -0
  8. data/.idea/vcs.xml +6 -0
  9. data/.rubocop.yml +334 -0
  10. data/.ruby-version +1 -0
  11. data/.simplecov +8 -0
  12. data/.yardopts +1 -0
  13. data/CHANGES.md +58 -0
  14. data/Dockerfile +57 -0
  15. data/Gemfile +3 -0
  16. data/Jenkinsfile +18 -0
  17. data/LICENSE.md +21 -0
  18. data/README.md +73 -0
  19. data/Rakefile +20 -0
  20. data/berkeley_library-tind.gemspec +50 -0
  21. data/bin/tind-export +14 -0
  22. data/docker-compose.yml +15 -0
  23. data/lib/berkeley_library/tind.rb +3 -0
  24. data/lib/berkeley_library/tind/api.rb +1 -0
  25. data/lib/berkeley_library/tind/api/api.rb +132 -0
  26. data/lib/berkeley_library/tind/api/api_exception.rb +131 -0
  27. data/lib/berkeley_library/tind/api/collection.rb +82 -0
  28. data/lib/berkeley_library/tind/api/date_range.rb +67 -0
  29. data/lib/berkeley_library/tind/api/format.rb +32 -0
  30. data/lib/berkeley_library/tind/api/search.rb +100 -0
  31. data/lib/berkeley_library/tind/config.rb +103 -0
  32. data/lib/berkeley_library/tind/export.rb +1 -0
  33. data/lib/berkeley_library/tind/export/column.rb +54 -0
  34. data/lib/berkeley_library/tind/export/column_group.rb +144 -0
  35. data/lib/berkeley_library/tind/export/column_group_list.rb +131 -0
  36. data/lib/berkeley_library/tind/export/column_width_calculator.rb +76 -0
  37. data/lib/berkeley_library/tind/export/config.rb +154 -0
  38. data/lib/berkeley_library/tind/export/csv_exporter.rb +29 -0
  39. data/lib/berkeley_library/tind/export/export.rb +47 -0
  40. data/lib/berkeley_library/tind/export/export_command.rb +168 -0
  41. data/lib/berkeley_library/tind/export/export_exception.rb +8 -0
  42. data/lib/berkeley_library/tind/export/export_format.rb +67 -0
  43. data/lib/berkeley_library/tind/export/exporter.rb +105 -0
  44. data/lib/berkeley_library/tind/export/filter.rb +52 -0
  45. data/lib/berkeley_library/tind/export/no_results_error.rb +7 -0
  46. data/lib/berkeley_library/tind/export/ods_exporter.rb +138 -0
  47. data/lib/berkeley_library/tind/export/row.rb +24 -0
  48. data/lib/berkeley_library/tind/export/row_metrics.rb +18 -0
  49. data/lib/berkeley_library/tind/export/table.rb +175 -0
  50. data/lib/berkeley_library/tind/export/table_metrics.rb +116 -0
  51. data/lib/berkeley_library/tind/marc.rb +1 -0
  52. data/lib/berkeley_library/tind/marc/xml_reader.rb +144 -0
  53. data/lib/berkeley_library/tind/module_info.rb +14 -0
  54. data/lib/berkeley_library/util/arrays.rb +178 -0
  55. data/lib/berkeley_library/util/logging.rb +1 -0
  56. data/lib/berkeley_library/util/ods/spreadsheet.rb +170 -0
  57. data/lib/berkeley_library/util/ods/xml/content_doc.rb +26 -0
  58. data/lib/berkeley_library/util/ods/xml/document_node.rb +57 -0
  59. data/lib/berkeley_library/util/ods/xml/element_node.rb +106 -0
  60. data/lib/berkeley_library/util/ods/xml/loext/table_protection.rb +26 -0
  61. data/lib/berkeley_library/util/ods/xml/manifest/file_entry.rb +42 -0
  62. data/lib/berkeley_library/util/ods/xml/manifest/manifest.rb +73 -0
  63. data/lib/berkeley_library/util/ods/xml/manifest_doc.rb +26 -0
  64. data/lib/berkeley_library/util/ods/xml/namespace.rb +46 -0
  65. data/lib/berkeley_library/util/ods/xml/office/automatic_styles.rb +181 -0
  66. data/lib/berkeley_library/util/ods/xml/office/body.rb +17 -0
  67. data/lib/berkeley_library/util/ods/xml/office/document_content.rb +98 -0
  68. data/lib/berkeley_library/util/ods/xml/office/document_styles.rb +39 -0
  69. data/lib/berkeley_library/util/ods/xml/office/font_face_decls.rb +30 -0
  70. data/lib/berkeley_library/util/ods/xml/office/scripts.rb +17 -0
  71. data/lib/berkeley_library/util/ods/xml/office/spreadsheet.rb +37 -0
  72. data/lib/berkeley_library/util/ods/xml/office/styles.rb +39 -0
  73. data/lib/berkeley_library/util/ods/xml/style/cell_style.rb +58 -0
  74. data/lib/berkeley_library/util/ods/xml/style/column_style.rb +36 -0
  75. data/lib/berkeley_library/util/ods/xml/style/default_style.rb +31 -0
  76. data/lib/berkeley_library/util/ods/xml/style/family.rb +85 -0
  77. data/lib/berkeley_library/util/ods/xml/style/font_face.rb +46 -0
  78. data/lib/berkeley_library/util/ods/xml/style/paragraph_properties.rb +30 -0
  79. data/lib/berkeley_library/util/ods/xml/style/row_style.rb +37 -0
  80. data/lib/berkeley_library/util/ods/xml/style/style.rb +44 -0
  81. data/lib/berkeley_library/util/ods/xml/style/table_cell_properties.rb +40 -0
  82. data/lib/berkeley_library/util/ods/xml/style/table_column_properties.rb +30 -0
  83. data/lib/berkeley_library/util/ods/xml/style/table_properties.rb +25 -0
  84. data/lib/berkeley_library/util/ods/xml/style/table_row_properties.rb +28 -0
  85. data/lib/berkeley_library/util/ods/xml/style/table_style.rb +27 -0
  86. data/lib/berkeley_library/util/ods/xml/style/text_properties.rb +52 -0
  87. data/lib/berkeley_library/util/ods/xml/styles_doc.rb +26 -0
  88. data/lib/berkeley_library/util/ods/xml/table/named_expressions.rb +17 -0
  89. data/lib/berkeley_library/util/ods/xml/table/repeatable.rb +38 -0
  90. data/lib/berkeley_library/util/ods/xml/table/table.rb +193 -0
  91. data/lib/berkeley_library/util/ods/xml/table/table_cell.rb +46 -0
  92. data/lib/berkeley_library/util/ods/xml/table/table_column.rb +43 -0
  93. data/lib/berkeley_library/util/ods/xml/table/table_row.rb +136 -0
  94. data/lib/berkeley_library/util/ods/xml/text/p.rb +118 -0
  95. data/lib/berkeley_library/util/paths.rb +111 -0
  96. data/lib/berkeley_library/util/stringios.rb +30 -0
  97. data/lib/berkeley_library/util/strings.rb +42 -0
  98. data/lib/berkeley_library/util/sys_exits.rb +15 -0
  99. data/lib/berkeley_library/util/times.rb +22 -0
  100. data/lib/berkeley_library/util/uris.rb +44 -0
  101. data/lib/berkeley_library/util/uris/appender.rb +162 -0
  102. data/lib/berkeley_library/util/uris/requester.rb +62 -0
  103. data/lib/berkeley_library/util/uris/validator.rb +32 -0
  104. data/rakelib/bundle.rake +8 -0
  105. data/rakelib/coverage.rake +11 -0
  106. data/rakelib/gem.rake +54 -0
  107. data/rakelib/rubocop.rake +18 -0
  108. data/rakelib/spec.rake +2 -0
  109. data/spec/.rubocop.yml +40 -0
  110. data/spec/berkeley_library/tind/api/api_exception_spec.rb +91 -0
  111. data/spec/berkeley_library/tind/api/api_spec.rb +143 -0
  112. data/spec/berkeley_library/tind/api/collection_spec.rb +74 -0
  113. data/spec/berkeley_library/tind/api/date_range_spec.rb +110 -0
  114. data/spec/berkeley_library/tind/api/format_spec.rb +54 -0
  115. data/spec/berkeley_library/tind/api/search_spec.rb +364 -0
  116. data/spec/berkeley_library/tind/config_spec.rb +86 -0
  117. data/spec/berkeley_library/tind/export/column_group_spec.rb +29 -0
  118. data/spec/berkeley_library/tind/export/column_spec.rb +43 -0
  119. data/spec/berkeley_library/tind/export/config_spec.rb +206 -0
  120. data/spec/berkeley_library/tind/export/export_command_spec.rb +169 -0
  121. data/spec/berkeley_library/tind/export/export_format_spec.rb +59 -0
  122. data/spec/berkeley_library/tind/export/export_matcher.rb +112 -0
  123. data/spec/berkeley_library/tind/export/export_spec.rb +150 -0
  124. data/spec/berkeley_library/tind/export/exporter_spec.rb +125 -0
  125. data/spec/berkeley_library/tind/export/row_spec.rb +118 -0
  126. data/spec/berkeley_library/tind/export/table_spec.rb +322 -0
  127. data/spec/berkeley_library/tind/marc/xml_reader_spec.rb +93 -0
  128. data/spec/berkeley_library/util/arrays_spec.rb +340 -0
  129. data/spec/berkeley_library/util/ods/spreadsheet_spec.rb +124 -0
  130. data/spec/berkeley_library/util/ods/xml/content_doc_spec.rb +121 -0
  131. data/spec/berkeley_library/util/ods/xml/manifest/file_entry_spec.rb +27 -0
  132. data/spec/berkeley_library/util/ods/xml/manifest/manifest_spec.rb +33 -0
  133. data/spec/berkeley_library/util/ods/xml/office/document_content_spec.rb +60 -0
  134. data/spec/berkeley_library/util/ods/xml/style/automatic_styles_spec.rb +37 -0
  135. data/spec/berkeley_library/util/ods/xml/style/family_spec.rb +57 -0
  136. data/spec/berkeley_library/util/ods/xml/table/table_row_spec.rb +179 -0
  137. data/spec/berkeley_library/util/ods/xml/table/table_spec.rb +218 -0
  138. data/spec/berkeley_library/util/paths_spec.rb +90 -0
  139. data/spec/berkeley_library/util/stringios_spec.rb +34 -0
  140. data/spec/berkeley_library/util/strings_spec.rb +27 -0
  141. data/spec/berkeley_library/util/times_spec.rb +39 -0
  142. data/spec/berkeley_library/util/uris_spec.rb +118 -0
  143. data/spec/data/collection-names.txt +438 -0
  144. data/spec/data/collections.json +4827 -0
  145. data/spec/data/disjoint-records.xml +187 -0
  146. data/spec/data/record-184453.xml +58 -0
  147. data/spec/data/record-184458.xml +63 -0
  148. data/spec/data/record-187888.xml +78 -0
  149. data/spec/data/records-api-search-cjk-p1.xml +6381 -0
  150. data/spec/data/records-api-search-cjk-p2.xml +5 -0
  151. data/spec/data/records-api-search-p1.xml +4506 -0
  152. data/spec/data/records-api-search-p2.xml +4509 -0
  153. data/spec/data/records-api-search-p3.xml +4506 -0
  154. data/spec/data/records-api-search-p4.xml +4509 -0
  155. data/spec/data/records-api-search-p5.xml +4506 -0
  156. data/spec/data/records-api-search-p6.xml +2436 -0
  157. data/spec/data/records-api-search-p7.xml +5 -0
  158. data/spec/data/records-api-search.xml +234 -0
  159. data/spec/data/records-manual-search.xml +547 -0
  160. data/spec/spec_helper.rb +30 -0
  161. data/test/profile/table_from_records_profile.rb +46 -0
  162. metadata +585 -0
@@ -0,0 +1,193 @@
1
+ require 'berkeley_library/util/ods/xml/element_node'
2
+ require 'berkeley_library/util/ods/xml/loext/table_protection'
3
+ require 'berkeley_library/util/ods/xml/style/column_style'
4
+ require 'berkeley_library/util/ods/xml/table/table_column'
5
+ require 'berkeley_library/util/ods/xml/table/table_row'
6
+
7
+ module BerkeleyLibrary
8
+ module Util
9
+ module ODS
10
+ module XML
11
+ module Table
12
+ class Table < XML::ElementNode
13
+ # ------------------------------------------------------------
14
+ # Constants
15
+
16
+ MIN_COLUMNS = 1024
17
+ MIN_ROWS = 1_048_576
18
+
19
+ # ------------------------------------------------------------
20
+ # Accessors
21
+
22
+ attr_reader :table_name
23
+
24
+ attr_reader :table_style
25
+
26
+ # @return [XML::Office::AutomaticStyles] the document styles
27
+ attr_reader :styles
28
+
29
+ # ------------------------------------------------------------
30
+ # Initializers
31
+
32
+ # Initializes a new table
33
+ #
34
+ # @param name [String] the table name
35
+ # @param style [XML::Style::TableStyle] the table style, if other than default
36
+ # @param styles [XML::Office::AutomaticStyles] the document styles
37
+ # @param protected [Boolean] whether the table is protected
38
+ def initialize(table_name, table_style = nil, styles:, protected: true)
39
+ super(:table, 'table', doc: styles.doc)
40
+
41
+ @table_name = table_name
42
+ @table_style = table_style || styles.default_style(:table)
43
+ @styles = styles
44
+
45
+ set_attribute('name', self.table_name)
46
+ set_attribute('style-name', self.table_style.style_name)
47
+
48
+ protect! if protected
49
+ end
50
+
51
+ # ------------------------------------------------------------
52
+ # Accessors and utility methods
53
+
54
+ def column_count
55
+ @column_count ||= 0
56
+ end
57
+
58
+ def row_count
59
+ @row_count ||= 0
60
+ end
61
+
62
+ def get_value_at(row_index, column_index)
63
+ (row = rows[row_index]) && row.get_value_at(column_index)
64
+ end
65
+
66
+ def add_column(header, width = nil, protected: false)
67
+ add_column_with_styles(
68
+ header,
69
+ column_style: styles.find_or_create_column_style(width),
70
+ default_cell_style: styles.find_or_create_cell_style(protected)
71
+ )
72
+ end
73
+
74
+ def add_column_with_styles(header, column_style:, default_cell_style: nil, header_cell_style: nil)
75
+ cell_style = default_cell_style || styles.find_or_create_cell_style
76
+ add_or_repeat_column(column_style, cell_style).tap do
77
+ header_row = rows[0] || add_row
78
+ header_row.set_value_at(column_count, header, header_cell_style)
79
+ self.column_count += 1
80
+ end
81
+ end
82
+
83
+ def add_empty_columns(number_repeated, width = nil, protected: false)
84
+ column_style = styles.find_or_create_column_style(width)
85
+ default_cell_style = styles.find_or_create_cell_style(protected)
86
+
87
+ TableColumn.new(column_style, default_cell_style, number_repeated, table: self).tap do |col|
88
+ columns << col
89
+ self.column_count += number_repeated
90
+ end
91
+ end
92
+
93
+ # Adds a new row with the specified height.
94
+ # @param height [String] the row height. Defaults to {XML::Style::RowStyle::DEFAULT_HEIGHT}.
95
+ # @param number_repeated [Integer] the number of identical rows to repeat
96
+ # @return [TableRow] the new row
97
+ def add_row(height = nil, number_repeated = 1)
98
+ row_style = styles.find_or_create_row_style(height)
99
+ TableRow.new(row_style, number_repeated, table: self).tap do |row|
100
+ rows << row
101
+ self.row_count += number_repeated
102
+ end
103
+ end
104
+
105
+ # ------------------------------------------------------------
106
+ # Public XML::ElementNode overrides
107
+
108
+ def add_child(child)
109
+ return child.tap { |column| columns << column } if child.is_a?(TableColumn)
110
+ return child.tap { |row| rows << row } if child.is_a?(TableRow)
111
+
112
+ child.tap { |c| other_children << c }
113
+ end
114
+
115
+ # ------------------------------------------------------------
116
+ # Protected methods
117
+
118
+ protected
119
+
120
+ # ----------------------------------------
121
+ # Protected XML::ElementNode overrides
122
+
123
+ def children
124
+ [other_children, columns, rows].flatten
125
+ end
126
+
127
+ def create_element
128
+ ensure_empty_columns!
129
+ ensure_empty_rows!
130
+
131
+ super
132
+ end
133
+
134
+ # ------------------------------------------------------------
135
+ # Private methods
136
+
137
+ private
138
+
139
+ # ------------------------------
140
+ # Private writers
141
+
142
+ attr_writer :column_count
143
+
144
+ attr_writer :row_count
145
+
146
+ # ------------------------------
147
+ # Private readers
148
+
149
+ def columns
150
+ @columns ||= []
151
+ end
152
+
153
+ def rows
154
+ @rows ||= []
155
+ end
156
+
157
+ def other_children
158
+ @other_children ||= []
159
+ end
160
+
161
+ # ------------------------------
162
+ # Private utility methods
163
+
164
+ def protect!
165
+ set_attribute('protected', 'true')
166
+ add_child(LOExt::TableProtection.new(doc: doc))
167
+ end
168
+
169
+ def add_or_repeat_column(column_style, default_cell_style)
170
+ if (last_column = columns.last).nil? || !last_column.has_styles?(column_style, default_cell_style)
171
+ TableColumn.new(column_style, default_cell_style, table: self).tap { |c| columns << c }
172
+ else
173
+ last_column.tap(&:increment_repeats!)
174
+ end
175
+ end
176
+
177
+ # TODO: do we really need to use the LibreOffice default width/height?
178
+
179
+ def ensure_empty_columns!
180
+ empty_required = MIN_COLUMNS - column_count
181
+ add_empty_columns(empty_required, '0.889in') if empty_required > 0
182
+ end
183
+
184
+ def ensure_empty_rows!
185
+ empty_required = MIN_ROWS - row_count
186
+ add_row('0.178in', empty_required) if empty_required > 0
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,46 @@
1
+ require 'berkeley_library/util/ods/xml/table/repeatable'
2
+ require 'berkeley_library/util/ods/xml/text/p'
3
+
4
+ module BerkeleyLibrary
5
+ module Util
6
+ module ODS
7
+ module XML
8
+ module Table
9
+ class TableCell < Repeatable
10
+ attr_reader :value
11
+ attr_reader :cell_style
12
+
13
+ def initialize(value = nil, cell_style = nil, number_repeated = 1, table:)
14
+ super('table-cell', 'number-columns-repeated', number_repeated, table: table)
15
+
16
+ @value = value
17
+ @cell_style = cell_style
18
+
19
+ set_default_attributes!
20
+ add_default_children!
21
+ end
22
+
23
+ class << self
24
+ def repeat_empty(number_repeated, cell_style = nil, table:)
25
+ TableCell.new(nil, cell_style, number_repeated, table: table)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def set_default_attributes!
32
+ set_attribute('style-name', cell_style.style_name) if cell_style
33
+ set_attribute(:office, 'value-type', 'string') if value
34
+ set_attribute(:calcext, 'value-type', 'string') if value
35
+ end
36
+
37
+ def add_default_children!
38
+ children << XML::Text::P.new(value, doc: doc) if value
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,43 @@
1
+ require 'berkeley_library/util/ods/xml/element_node'
2
+ require 'berkeley_library/util/ods/xml/table/repeatable'
3
+
4
+ module BerkeleyLibrary
5
+ module Util
6
+ module ODS
7
+ module XML
8
+ module Table
9
+ class TableColumn < Repeatable
10
+
11
+ attr_reader :column_style
12
+ attr_reader :default_cell_style
13
+
14
+ # Initializes a new column
15
+ #
16
+ # @param column_style [XML::Style::ColumnStyle] the column style
17
+ # @param default_cell_style [XML::Style::CellStyle] the default cell style for this column
18
+ def initialize(column_style, default_cell_style, number_repeated = 1, table:)
19
+ super('table-column', 'number-columns-repeated', number_repeated, table: table)
20
+ @column_style = column_style
21
+ @default_cell_style = default_cell_style
22
+
23
+ set_default_attributes!
24
+ end
25
+
26
+ # rubocop:disable Naming/PredicateName
27
+ def has_styles?(column_style, default_cell_style)
28
+ self.column_style == column_style && self.default_cell_style == default_cell_style
29
+ end
30
+ # rubocop:enable Naming/PredicateName
31
+
32
+ private
33
+
34
+ def set_default_attributes!
35
+ set_attribute('style-name', column_style.style_name)
36
+ set_attribute('default-cell-style-name', default_cell_style.style_name)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,136 @@
1
+ require 'berkeley_library/util/arrays'
2
+ require 'berkeley_library/util/ods/xml/element_node'
3
+ require 'berkeley_library/util/ods/xml/table/repeatable'
4
+ require 'berkeley_library/util/ods/xml/table/table_cell'
5
+
6
+ module BerkeleyLibrary
7
+ module Util
8
+ module ODS
9
+ module XML
10
+ module Table
11
+ class TableRow < Repeatable
12
+
13
+ # ------------------------------------------------------------
14
+ # Accessors
15
+
16
+ attr_reader :row_style
17
+ attr_reader :default_cell_style
18
+
19
+ # ------------------------------------------------------------
20
+ # Initializer
21
+
22
+ # @param table [Table] the table
23
+ # @param default_cell_style [Style::CellStyle] the default cell style
24
+ def initialize(row_style, number_repeated = 1, table:, default_cell_style: nil)
25
+ super('table-row', 'number-rows-repeated', number_repeated, table: table)
26
+ @row_style = row_style
27
+ @default_cell_style = default_cell_style
28
+
29
+ set_default_attributes!
30
+ end
31
+
32
+ # ------------------------------------------------------------
33
+ # Public utility methods
34
+
35
+ def set_value_at(column_index, value = nil, cell_style = nil)
36
+ explicit_cells[column_index] = TableCell.new(value, cell_style || default_cell_style, table: table)
37
+ end
38
+
39
+ def get_value_at(column_index)
40
+ (cell = explicit_cells[column_index]) && cell.value
41
+ end
42
+
43
+ # ------------------------------------------------------------
44
+ # Public XML::ElementNode overrides
45
+
46
+ def add_child(child)
47
+ return add_table_cell(child) if child.is_a?(TableCell)
48
+
49
+ child.tap { |c| other_children << c }
50
+ end
51
+
52
+ # ------------------------------------------------------------
53
+ # Protected methods
54
+
55
+ protected
56
+
57
+ # ----------------------------------------
58
+ # Protected utility methods
59
+
60
+ def add_table_cell(cell)
61
+ return cell.tap { |c| explicit_cells << c } if explicit_cell_count < table.column_count
62
+
63
+ raise ArgumentError, "Can't add cell at column index #{explicit_cell_count} to table with only #{table.column_count} columns"
64
+ end
65
+
66
+ # ----------------------------------------
67
+ # Protected XML::ElementNode overrides
68
+
69
+ def children
70
+ [].tap do |cc|
71
+ each_cell { |c| cc << c }
72
+ cc.concat(other_children)
73
+ end
74
+ end
75
+
76
+ # ------------------------------------------------------------
77
+ # Private methods
78
+
79
+ private
80
+
81
+ def column_count_actual
82
+ [table.column_count, Table::MIN_COLUMNS].max
83
+ end
84
+
85
+ def explicit_cells
86
+ @explicit_cells ||= []
87
+ end
88
+
89
+ def set_default_attributes!
90
+ set_attribute('style-name', row_style.style_name)
91
+ end
92
+
93
+ def other_children
94
+ @other_children ||= []
95
+ end
96
+
97
+ def explicit_cell_count
98
+ explicit_cells.size
99
+ end
100
+
101
+ def each_cell(columns_yielded = 0, remaining = explicit_cells, &block)
102
+ columns_yielded, remaining = yield_while_non_nil(columns_yielded, remaining, &block)
103
+ columns_yielded, remaining = yield_while_nil(columns_yielded, remaining, &block)
104
+ each_cell(columns_yielded, remaining, &block) unless remaining.empty?
105
+ end
106
+
107
+ def yield_while_non_nil(columns_yielded, remaining, &block)
108
+ non_nil_cells = remaining.take_while { |c| !c.nil? }
109
+ non_nil_cells.each(&block)
110
+ non_nil_cell_count = non_nil_cells.size
111
+
112
+ [columns_yielded + non_nil_cell_count, remaining[non_nil_cell_count..]]
113
+ end
114
+
115
+ def yield_while_nil(columns_yielded, remaining, &block)
116
+ nil_cell_count = Arrays.count_while(values: remaining, &:nil?)
117
+ remaining = remaining[nil_cell_count..]
118
+
119
+ empty_required = remaining.empty? ? (column_count_actual - columns_yielded) : nil_cell_count
120
+ yield_repeat_empty(empty_required, &block)
121
+ columns_yielded += empty_required
122
+
123
+ [columns_yielded, remaining]
124
+ end
125
+
126
+ def yield_repeat_empty(num_repeats, &block)
127
+ empty_cell = TableCell.repeat_empty(num_repeats, default_cell_style, table: table)
128
+ block.call(empty_cell)
129
+ end
130
+
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,118 @@
1
+ require 'berkeley_library/util/ods/xml/element_node'
2
+
3
+ module BerkeleyLibrary
4
+ module Util
5
+ module ODS
6
+ module XML
7
+ module Text
8
+ class P < XML::ElementNode
9
+
10
+ # ------------------------------------------------------------
11
+ # Constant
12
+
13
+ ESCAPABLE = ["\t", "\n", ' '].freeze
14
+
15
+ # ------------------------------------------------------------
16
+ # Accessors
17
+
18
+ attr_reader :text
19
+
20
+ # ------------------------------------------------------------
21
+ # Initializer
22
+
23
+ def initialize(text, doc:)
24
+ super(:text, 'p', doc: doc)
25
+
26
+ @text = text
27
+ add_default_children!
28
+ end
29
+
30
+ # ------------------------------------------------------------
31
+ # Private methods
32
+
33
+ private
34
+
35
+ def add_default_children!
36
+ each_child_element_or_string { |c| children << c }
37
+ end
38
+
39
+ def each_child_element_or_string(last_char = nil, text_remaining = text, &block)
40
+ last_char, text_remaining = yield_while_escaped(last_char, text_remaining, &block)
41
+ last_char, text_remaining = yield_while_unescaped(last_char, text_remaining, &block)
42
+ each_child_element_or_string(last_char, text_remaining, &block) unless text_remaining.empty?
43
+ end
44
+
45
+ def yield_while_unescaped(last_char, text_remaining, &block)
46
+ unescaped, last_char = take_while_unescaped(last_char, text_remaining)
47
+ unless unescaped.empty?
48
+ block.call(unescaped)
49
+ text_remaining = text_remaining[unescaped.size..]
50
+ end
51
+
52
+ [last_char, text_remaining]
53
+ end
54
+
55
+ def yield_while_escaped(last_char, text_remaining, &block)
56
+ escaped_char_count = 0
57
+ text_remaining.each_char do |c|
58
+ break unless escape?(c, last_char)
59
+
60
+ # TODO: collapse contiguous spaces with attribute 'text:c'
61
+ # https://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#attribute-text_c
62
+ block.call(escape_element_for(c))
63
+ escaped_char_count += 1
64
+ last_char = c
65
+ end
66
+ text_remaining = text_remaining[escaped_char_count..] unless escaped_char_count == 0
67
+
68
+ [last_char, text_remaining]
69
+ end
70
+
71
+ def take_while_unescaped(last_char, text_remaining)
72
+ unescaped = text_remaining.each_char.with_object('') do |c, result|
73
+ break result if escape?(c, last_char)
74
+
75
+ result << c
76
+ last_char = c
77
+ end
78
+ [unescaped, last_char]
79
+ end
80
+
81
+ def escape?(c, last_char)
82
+ ESCAPABLE.include?(c) && (last_char == ' ' || c != ' ')
83
+ end
84
+
85
+ def escape_element_for(c)
86
+ raise ArgumentError, "Not an escapable character: #{c}" unless ESCAPABLE.include?(c)
87
+
88
+ return S.new(doc: doc) if c == ' '
89
+ return Tab.new(doc: doc) if c == "\t"
90
+ return LineBreak.new(doc: doc) if c == "\n"
91
+ end
92
+ end
93
+
94
+ # ------------------------------------------------------------
95
+ # Helper classes
96
+
97
+ class S < XML::ElementNode
98
+ def initialize(doc:)
99
+ super(:text, 's', doc: doc)
100
+ end
101
+ end
102
+
103
+ class Tab < XML::ElementNode
104
+ def initialize(doc:)
105
+ super(:text, 'tab', doc: doc)
106
+ end
107
+ end
108
+
109
+ class LineBreak < XML::ElementNode
110
+ def initialize(doc:)
111
+ super(:text, 'line-break', doc: doc)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end