redmine_api_helper 0.3.24

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.

Potentially problematic release.


This version of redmine_api_helper might be problematic. Click here for more details.

Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +2 -0
  3. data/.gitignore +11 -0
  4. data/CODE_OF_CONDUCT.md +74 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE +339 -0
  7. data/README.md +30 -0
  8. data/Rakefile +2 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/lib/date_helper/date.rb +311 -0
  12. data/lib/odf_writer/bookmark.rb +110 -0
  13. data/lib/odf_writer/bookmark_reader.rb +77 -0
  14. data/lib/odf_writer/document.rb +372 -0
  15. data/lib/odf_writer/field.rb +174 -0
  16. data/lib/odf_writer/field_reader.rb +78 -0
  17. data/lib/odf_writer/image.rb +158 -0
  18. data/lib/odf_writer/image_reader.rb +76 -0
  19. data/lib/odf_writer/images.rb +89 -0
  20. data/lib/odf_writer/list_style.rb +331 -0
  21. data/lib/odf_writer/nested.rb +156 -0
  22. data/lib/odf_writer/odf_helper.rb +56 -0
  23. data/lib/odf_writer/parser/default.rb +685 -0
  24. data/lib/odf_writer/path_finder.rb +114 -0
  25. data/lib/odf_writer/section.rb +120 -0
  26. data/lib/odf_writer/section_reader.rb +61 -0
  27. data/lib/odf_writer/style.rb +417 -0
  28. data/lib/odf_writer/table.rb +135 -0
  29. data/lib/odf_writer/table_reader.rb +61 -0
  30. data/lib/odf_writer/template.rb +222 -0
  31. data/lib/odf_writer/text.rb +97 -0
  32. data/lib/odf_writer/text_reader.rb +77 -0
  33. data/lib/odf_writer/version.rb +29 -0
  34. data/lib/redmine_api_helper/api_helper.rb +333 -0
  35. data/lib/redmine_api_helper/args_helper.rb +106 -0
  36. data/lib/redmine_api_helper/attachments_api_helper.rb +52 -0
  37. data/lib/redmine_api_helper/define_api_helpers.rb +78 -0
  38. data/lib/redmine_api_helper/document_categories_api_helper.rb +38 -0
  39. data/lib/redmine_api_helper/groups_api_helper.rb +80 -0
  40. data/lib/redmine_api_helper/helpers.rb +50 -0
  41. data/lib/redmine_api_helper/issue_priorities_api_helper.rb +38 -0
  42. data/lib/redmine_api_helper/issue_relations_api_helper.rb +66 -0
  43. data/lib/redmine_api_helper/issue_statuses_api_helper.rb +36 -0
  44. data/lib/redmine_api_helper/issues_api_helper.rb +124 -0
  45. data/lib/redmine_api_helper/my_account_api_helper.rb +45 -0
  46. data/lib/redmine_api_helper/news_api_helper.rb +73 -0
  47. data/lib/redmine_api_helper/project_memberships_api_helper.rb +77 -0
  48. data/lib/redmine_api_helper/projects_api_helper.rb +73 -0
  49. data/lib/redmine_api_helper/roles_api_helper.rb +52 -0
  50. data/lib/redmine_api_helper/scripts_api_helper.rb +87 -0
  51. data/lib/redmine_api_helper/search_api_helper.rb +38 -0
  52. data/lib/redmine_api_helper/time_entries_api_helper.rb +73 -0
  53. data/lib/redmine_api_helper/time_entry_activities_api_helper.rb +38 -0
  54. data/lib/redmine_api_helper/trackers_api_helper.rb +38 -0
  55. data/lib/redmine_api_helper/users_api_helper.rb +73 -0
  56. data/lib/redmine_api_helper/version.rb +24 -0
  57. data/lib/redmine_api_helper/wiki_pages_api_helper.rb +66 -0
  58. data/lib/redmine_api_helper.rb +88 -0
  59. data/redmine_api_helper.gemspec +35 -0
  60. metadata +148 -0
@@ -0,0 +1,77 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Ruby Gem to create a self populating Open Document Format (.odf) text file.
4
+ #
5
+ # Copyright 2021 Stephan Wenzel <stephan.wenzel@drwpatent.de>
6
+ #
7
+ # This program is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU General Public License
9
+ # as published by the Free Software Foundation; either version 2
10
+ # of the License, or (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
+ #
21
+
22
+ module ODFWriter
23
+
24
+ ########################################################################################
25
+ #
26
+ # BookmarkReader: find all bookmarks and set name
27
+ #
28
+ ########################################################################################
29
+ class BookmarkReader
30
+
31
+ attr_accessor :name
32
+
33
+ ######################################################################################
34
+ #
35
+ # initialize
36
+ #
37
+ ######################################################################################
38
+ def initialize(opts={})
39
+ @name = opts[:name]
40
+ end #def
41
+
42
+ ######################################################################################
43
+ #
44
+ # get_paths: limit to paths with ancestors 'text '(content.xml) and master-styles (styles.xml)
45
+ #
46
+ ######################################################################################
47
+ def paths( root, doc)
48
+
49
+ # find nodes with matching field elements matching [BOOKMARK] pattern
50
+ nodes = doc.xpath("//*[self::text:bookmark or self::text:bookmark-start]").select{|node| scan(node).present? }
51
+
52
+ # find path for each field
53
+ paths = nil
54
+ nodes.each do |node|
55
+ leaf = {:bookmarks => scan(node)}
56
+ paths = PathFinder.trail(node, leaf, :root => root, :paths => paths)
57
+ end #each
58
+ paths.to_h
59
+
60
+ end #def
61
+
62
+ ######################################################################################
63
+ # private
64
+ ######################################################################################
65
+
66
+ private
67
+
68
+ def scan(node)
69
+ if name
70
+ node.attr("text:name") == name.upcase ? [node.attr("text:name")] : []
71
+ else
72
+ [node.attr("text:name")]
73
+ end
74
+ end #def
75
+
76
+ end #class
77
+ end #module
@@ -0,0 +1,372 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Ruby Gem to create a self populating Open Document Format (.odf) text file.
4
+ #
5
+ # Copyright 2021 Stephan Wenzel <stephan.wenzel@drwpatent.de>
6
+ #
7
+ # This program is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU General Public License
9
+ # as published by the Free Software Foundation; either version 2
10
+ # of the License, or (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
+ #
21
+
22
+ module ODFWriter
23
+
24
+ ########################################################################################
25
+ #
26
+ # Document: create document from template
27
+ #
28
+ ########################################################################################
29
+ class Document
30
+ include Images
31
+
32
+ ######################################################################################
33
+ #
34
+ # initialize
35
+ #
36
+ ######################################################################################
37
+ def initialize(path = nil, zip_stream: nil, &block)
38
+
39
+ @template = ODFWriter::Template.new(path, :zip_stream => zip_stream)
40
+
41
+ @fields = {}
42
+ @field_readers = {}
43
+
44
+ @texts = {}
45
+ @text_readers = {}
46
+
47
+ @bookmarks = {}
48
+ @bookmark_readers = {}
49
+
50
+ @images = {}
51
+ @image_readers = {}
52
+
53
+ @tables = {}
54
+ @table_readers = {}
55
+
56
+ @sections = {}
57
+ @section_readers = {}
58
+
59
+ @styles = {}
60
+ @list_styles = {}
61
+
62
+ if block_given?
63
+ instance_eval(&block)
64
+ end
65
+
66
+ end #def
67
+
68
+ ######################################################################################
69
+ #
70
+ # add_readers
71
+ #
72
+ ######################################################################################
73
+ def add_readers(files = ODFWriter::Template::CONTENT_FILES.keys)
74
+ files.each do |file|
75
+ add_bookmark_reader(file)
76
+ add_field_reader(file)
77
+ add_text_reader(file)
78
+ add_image_reader(file)
79
+ add_section_reader(file)
80
+ add_table_reader(file)
81
+ end
82
+ end #def
83
+
84
+ ######################################################################################
85
+ #
86
+ # add_field
87
+ #
88
+ ######################################################################################
89
+ def add_field(file, name, opts={}, &block)
90
+ opts.merge!(:name => name)
91
+ @fields[file] ||= []; @fields[file] << Field.new(opts, &block)
92
+ end #def
93
+
94
+ ######################################################################################
95
+ #
96
+ # add_field_reader
97
+ #
98
+ ######################################################################################
99
+ def add_field_reader(file, name=nil)
100
+ @field_readers[file] ||= []; @field_readers[file] << FieldReader.new(:name => name)
101
+ end #def
102
+
103
+ ######################################################################################
104
+ #
105
+ # add_text
106
+ #
107
+ ######################################################################################
108
+ def add_text(file, name, opts={}, &block)
109
+ opts.merge!(:name => name)
110
+ @texts[file] ||= []; @texts[file] << Text.new(opts, &block)
111
+ end #def
112
+
113
+ ######################################################################################
114
+ #
115
+ # add_text_reader
116
+ #
117
+ ######################################################################################
118
+ def add_text_reader(file, name=nil)
119
+ @text_readers[file] ||= []; @text_readers[file] << TextReader.new(:name => name)
120
+ end #def
121
+
122
+ ######################################################################################
123
+ #
124
+ # add_bookmark
125
+ #
126
+ ######################################################################################
127
+ def add_bookmark(file, name, opts={}, &block)
128
+ opts.merge!(:name => name)
129
+ @bookmarks[file] ||= []; @bookmarks[file] << Bookmark.new(opts, &block)
130
+ end #def
131
+
132
+ ######################################################################################
133
+ #
134
+ # add_bookmark_reader
135
+ #
136
+ ######################################################################################
137
+ def add_bookmark_reader(file, name=nil)
138
+ @bookmark_readers[file] ||= []; @bookmark_readers[file] << BookmarkReader.new(:name => name)
139
+ end #def
140
+
141
+ ######################################################################################
142
+ #
143
+ # add_image
144
+ #
145
+ ######################################################################################
146
+ def add_image(file, name, opts={}, &block)
147
+ opts.merge!(:name => name)
148
+ @images[file] ||= []; @images[file] << Image.new(opts, &block)
149
+ end #def
150
+
151
+ ######################################################################################
152
+ #
153
+ # add_image_reader
154
+ #
155
+ ######################################################################################
156
+ def add_image_reader(file, name=nil)
157
+ @image_readers[file] ||= []; @image_readers[file] << ImageReader.new(:name => name)
158
+ end #def
159
+
160
+ ######################################################################################
161
+ #
162
+ # add_table
163
+ #
164
+ ######################################################################################
165
+ def add_table(file, name, collection, opts={})
166
+ opts.merge!(:name => name, :collection => collection)
167
+ table = Table.new(opts)
168
+ @tables[file] ||= []; @tables[file] << table
169
+ yield(table)
170
+ end #def
171
+
172
+ ######################################################################################
173
+ #
174
+ # add_table_reader
175
+ #
176
+ ######################################################################################
177
+ def add_table_reader(file, name=nil)
178
+ @table_readers[file] ||= []; @table_readers[file] << TableReader.new(:name => name)
179
+ end #def
180
+
181
+ ######################################################################################
182
+ #
183
+ # add_section
184
+ #
185
+ ######################################################################################
186
+ def add_section(file, name, collection, opts={})
187
+ opts.merge!(:name => name, :collection => collection)
188
+ section = Section.new(opts)
189
+ @sections[file] ||= []; @sections[file] << section
190
+ yield(section)
191
+ end #def
192
+
193
+ ######################################################################################
194
+ #
195
+ # add_section_reader
196
+ #
197
+ ######################################################################################
198
+ def add_section_reader(file, name=nil)
199
+ @section_readers[file] ||= []; @section_readers[file] << SectionReader.new(:name => name)
200
+ end #def
201
+
202
+ ######################################################################################
203
+ #
204
+ # add_style
205
+ #
206
+ ######################################################################################
207
+ def add_style(root, *styles )
208
+ @styles[root] ||= []; @styles[root] << Style.new( *styles )
209
+ end #def
210
+
211
+ ######################################################################################
212
+ #
213
+ # add_list_style
214
+ #
215
+ ######################################################################################
216
+ def add_list_style(root, *list_styles )
217
+ list_styles.each do |list_style|
218
+ @list_styles[root] ||= []; @list_styles[root] << ListStyle.new( list_style )
219
+ end
220
+ end #def
221
+
222
+ ######################################################################################
223
+ #
224
+ # tree
225
+ #
226
+ ######################################################################################
227
+ def tree
228
+ results = {}
229
+ @template.content do |file, doc|
230
+ #results.deep_merge!( leafs( file, doc)) # requires Rails
231
+ results = deep_merge(results, leafs( file, doc))
232
+ end
233
+ results
234
+ end #def
235
+
236
+ ######################################################################################
237
+ #
238
+ # leafs
239
+ #
240
+ ######################################################################################
241
+ def leafs( file, doc)
242
+ results={}
243
+ # requires Rails
244
+ # results.deep_merge! @bookmark_readers[file].map { |r| r.paths(file, doc) }.inject{|m,n| m.deep_merge(n){|k, v1,v2| v1 + v2}} if @bookmark_readers[file]
245
+ # results.deep_merge! @field_readers[file].map { |r| r.paths(file, doc) }.inject{|m,n| m.deep_merge(n){|k, v1,v2| v1 + v2}} if @field_readers[file]
246
+ # results.deep_merge! @text_readers[file].map { |r| r.paths(file, doc) }.inject{|m,n| m.deep_merge(n){|k, v1,v2| v1 + v2}} if @text_readers[file]
247
+ # results.deep_merge! @image_readers[file].map { |r| r.paths(file, doc) }.inject{|m,n| m.deep_merge(n){|k, v1,v2| v1 + v2}} if @image_readers[file]
248
+
249
+ results = deep_merge( results, @bookmark_readers[file].map { |r| r.paths(file, doc) }.inject{|m,n| deep_merge(m, n)} ) if @bookmark_readers[file]
250
+ results = deep_merge( results, @field_readers[file].map { |r| r.paths(file, doc) }.inject{|m,n| deep_merge(m, n)} ) if @field_readers[file]
251
+ results = deep_merge( results, @text_readers[file].map { |r| r.paths(file, doc) }.inject{|m,n| deep_merge(m, n)} ) if @text_readers[file]
252
+ results = deep_merge( results, @image_readers[file].map { |r| r.paths(file, doc) }.inject{|m,n| deep_merge(m, n)} ) if @image_readers[file]
253
+ results
254
+ end #def
255
+
256
+ ######################################################################################
257
+ #
258
+ # populate
259
+ #
260
+ ######################################################################################
261
+ def populate(object, options={})
262
+
263
+ file = options[:file] || :content
264
+ coll = options[:coll] || []
265
+ tree = options[:tree] || self.tree
266
+ prok = options[:proc]
267
+ list = options[:list]; list = Array(list).compact
268
+
269
+ tree.each do |key, names|
270
+ case key
271
+ when :fields
272
+ names.each do |name|
273
+ add_field(file, name, :value => prok ? prok.call(object, name) : object.try(name.downcase.to_sym))
274
+ end #def
275
+ when :texts
276
+ names.each do |name|
277
+ add_text(file, name, :value => prok ? prok.call(object, name) : object.try(name.downcase.to_sym))
278
+ end #def
279
+ when :bookmarks
280
+ names.each do |name|
281
+ add_bookmark(file, name, :value => prok ? prok.call(object, name) : object.try(name.downcase.to_sym))
282
+ end #def
283
+ when :images
284
+ names.each do |name|
285
+ add_image(file, name, :value => (prok ? prok.call(object, name) : object.try(name.downcase.to_sym)))
286
+ end #def
287
+ when :tables
288
+ names.each do |name, table_tree|
289
+ if list.include?(name)
290
+ add_table(file, name, coll, options){|table| table.populate(table_tree, options)}
291
+ elsif object.respond_to?(name.underscore.to_sym)
292
+ add_table(file, name, arrify(object.send(name.underscore.to_sym)), options){|table| table.populate(table_tree, options)}
293
+ end
294
+ end #def
295
+ when :sections
296
+ names.each do |name, section_tree|
297
+ if list.include?(name)
298
+ add_section(file, name, coll, options){|section| section.populate(section_tree, options)} if list.include?(name)
299
+ elsif object.respond_to?(name.underscore.to_sym)
300
+ add_section(file, name, arrify(object.send(name.underscore.to_sym)), options){|section| section.populate(section_tree, options)}
301
+ end
302
+ end #def
303
+ when :files
304
+ names.each do |file, file_tree|
305
+ populate(object, options.merge(:file => file, :tree => file_tree))
306
+ end
307
+ end #case
308
+ end #each
309
+ end #def
310
+
311
+ ######################################################################################
312
+ #
313
+ # write
314
+ #
315
+ ######################################################################################
316
+ def write(dest = nil)
317
+
318
+ @template.update_content do |template|
319
+
320
+ template.update_files do |file, doc, manifest|
321
+
322
+ @styles[file].to_a.each { |s| s.add_style(doc) }
323
+ @list_styles[file].to_a.each { |s| s.add_list_style(doc) }
324
+
325
+ @sections[file].to_a.each { |s| s.replace!(doc, manifest, template) }
326
+ @tables[file].to_a.each { |t| t.replace!(doc, manifest, template) }
327
+
328
+ @texts[file].to_a.each { |t| t.replace!(doc) }
329
+ @fields[file].to_a.each { |f| f.replace!(doc) }
330
+
331
+ @bookmarks[file].to_a.each { |b| b.replace!(doc) }
332
+ @images[file].to_a.each { |i| i.replace!(doc, manifest, template) }
333
+
334
+ Image.unique_image_names( doc ) if @images.present?
335
+ end
336
+
337
+ end
338
+
339
+ if dest
340
+ ::File.open(dest, "wb") {|f| f.write(@template.data) }
341
+ else
342
+ @template.data
343
+ end
344
+
345
+ end #def
346
+
347
+ ######################################################################################
348
+ #
349
+ # private
350
+ #
351
+ ######################################################################################
352
+ private
353
+
354
+ # deep_merge without using Rails
355
+ def deep_merge(left, right)
356
+ merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : [:undefined, nil, :nil].include?(v2) ? v1 : v1 + v2 }
357
+ left.merge(right, &merger)
358
+ end #def
359
+
360
+ def arrify(obj)
361
+ case obj
362
+ when Array
363
+ obj
364
+ when Hash
365
+ [obj]
366
+ else
367
+ obj.respond_to?(:to_a) ? obj.to_a : [obj]
368
+ end
369
+ end #def
370
+
371
+ end #class
372
+ end #module
@@ -0,0 +1,174 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Ruby Gem to create a self populating Open Document Format (.odf) text file.
4
+ #
5
+ # Copyright 2021 Stephan Wenzel <stephan.wenzel@drwpatent.de>
6
+ #
7
+ # This program is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU General Public License
9
+ # as published by the Free Software Foundation; either version 2
10
+ # of the License, or (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
+ #
21
+
22
+ module ODFWriter
23
+
24
+ ########################################################################################
25
+ #
26
+ # Field: replace fields
27
+ #
28
+ ########################################################################################
29
+ class Field
30
+
31
+ ######################################################################################
32
+ #
33
+ # constants
34
+ #
35
+ ######################################################################################
36
+ DELIMITERS = %w([ ])
37
+
38
+ ######################################################################################
39
+ #
40
+ # constants
41
+ #
42
+ ######################################################################################
43
+ attr_accessor :name
44
+
45
+ ######################################################################################
46
+ #
47
+ # initialize
48
+ #
49
+ ######################################################################################
50
+ def initialize(options, &block)
51
+
52
+ @name = options[:name]
53
+ @value = options[:value]
54
+ @field = options[:field]
55
+ @key = @field || @name
56
+ @proc = options[:proc]
57
+
58
+ @remove_classes = options[:remove_classes]
59
+ @remove_class_prefix = options[:remove_class_prefix]
60
+ @remove_class_suffix = options[:remove_class_suffix]
61
+
62
+ @value ||= @proc
63
+
64
+ unless @value
65
+ if block_given?
66
+ @value = block
67
+ else
68
+ @value = lambda { |item, key| field(item, key) }
69
+ end
70
+ end
71
+ end #def
72
+
73
+ ######################################################################################
74
+ #
75
+ # replace!
76
+ #
77
+ ######################################################################################
78
+ def replace!(content, item = nil)
79
+ txt = content.inner_html
80
+ txt.gsub!(placeholder, sanitize(value(item)))
81
+ content.inner_html = txt
82
+ end #def
83
+
84
+ ######################################################################################
85
+ #
86
+ # value
87
+ #
88
+ ######################################################################################
89
+ def value(item = nil)
90
+ @value.is_a?(Proc) ? @value.call(item, @key) : @value
91
+ end #def
92
+
93
+ ######################################################################################
94
+ #
95
+ # field
96
+ #
97
+ ######################################################################################
98
+ def field(item, key)
99
+ case item
100
+ when NilClass
101
+ key
102
+ when Hash
103
+ hash_value(item, key)
104
+ else
105
+ item_field(item, key)
106
+ end
107
+ end #def
108
+
109
+ ######################################################################################
110
+ #
111
+ # private
112
+ #
113
+ ######################################################################################
114
+ private
115
+
116
+ ######################################################################################
117
+ # hash_value
118
+ ######################################################################################
119
+ def hash_value(hash, key)
120
+ hash[key.to_s] || hash[key.to_sym] ||
121
+ hash[key.to_s.underscore] || hash[key.to_s.underscore.to_sym]
122
+ end #def
123
+
124
+ ######################################################################################
125
+ # item_field
126
+ ######################################################################################
127
+ def item_field(item, field)
128
+ item.try(field.to_s.to_sym) ||
129
+ item.try(field.to_s.underscore.to_sym)
130
+ end #def
131
+
132
+ ######################################################################################
133
+ # placeholder
134
+ ######################################################################################
135
+ def placeholder
136
+ "#{DELIMITERS[0]}#{@name.to_s.upcase}#{DELIMITERS[1]}"
137
+ end #def
138
+
139
+ ######################################################################################
140
+ # sanitize
141
+ ######################################################################################
142
+ def sanitize(text)
143
+ # if we get some object, which is not a string, Numeric or the like
144
+ # f.i. a Hash or an Arry or a CollectionProxy or an image then return @key to avoid
145
+ # uggly errors
146
+ return @key.to_s if text.respond_to?(:each)
147
+ text = html_escape(text)
148
+ text = odf_linebreak(text)
149
+ text
150
+ end #def
151
+
152
+ HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;' }
153
+
154
+ def html_escape(s)
155
+ return "" unless s
156
+ s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }
157
+ end #def
158
+
159
+ def odf_linebreak(s)
160
+ return "" unless s
161
+ s = s.encode(universal_newline: true)
162
+ s.to_s.gsub("\n", "<text:line-break/>").gsub("<br.*?>", "<text:line-break/>")
163
+ end #def
164
+
165
+ def deep_fields(fs)
166
+ fs.split(/\./)
167
+ end #def
168
+
169
+ def deep_try(item, f)
170
+ deep_fields(f).inject(item) {|obj,f| obj.try(f.to_s.underscore.to_sym)}
171
+ end #def
172
+
173
+ end #class
174
+ end #module