redmine_api_helper 0.3.35
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.
- checksums.yaml +7 -0
- data/.gitattributes +2 -0
- data/.gitignore +11 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE +339 -0
- data/README.md +30 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/date_helper/date.rb +311 -0
- data/lib/odf_writer/bookmark.rb +110 -0
- data/lib/odf_writer/bookmark_reader.rb +77 -0
- data/lib/odf_writer/document.rb +371 -0
- data/lib/odf_writer/field.rb +174 -0
- data/lib/odf_writer/field_reader.rb +78 -0
- data/lib/odf_writer/image.rb +176 -0
- data/lib/odf_writer/image_reader.rb +76 -0
- data/lib/odf_writer/images.rb +89 -0
- data/lib/odf_writer/list_style.rb +336 -0
- data/lib/odf_writer/nested.rb +156 -0
- data/lib/odf_writer/odf_helper.rb +57 -0
- data/lib/odf_writer/parser/default.rb +691 -0
- data/lib/odf_writer/path_finder.rb +114 -0
- data/lib/odf_writer/section.rb +120 -0
- data/lib/odf_writer/section_reader.rb +61 -0
- data/lib/odf_writer/style.rb +483 -0
- data/lib/odf_writer/table.rb +135 -0
- data/lib/odf_writer/table_reader.rb +61 -0
- data/lib/odf_writer/template.rb +234 -0
- data/lib/odf_writer/text.rb +97 -0
- data/lib/odf_writer/text_reader.rb +77 -0
- data/lib/odf_writer/version.rb +29 -0
- data/lib/redmine_api_helper/api_helper.rb +333 -0
- data/lib/redmine_api_helper/args_helper.rb +106 -0
- data/lib/redmine_api_helper/attachments_api_helper.rb +52 -0
- data/lib/redmine_api_helper/define_api_helpers.rb +78 -0
- data/lib/redmine_api_helper/document_categories_api_helper.rb +38 -0
- data/lib/redmine_api_helper/groups_api_helper.rb +80 -0
- data/lib/redmine_api_helper/helpers.rb +50 -0
- data/lib/redmine_api_helper/issue_priorities_api_helper.rb +38 -0
- data/lib/redmine_api_helper/issue_relations_api_helper.rb +66 -0
- data/lib/redmine_api_helper/issue_statuses_api_helper.rb +36 -0
- data/lib/redmine_api_helper/issues_api_helper.rb +124 -0
- data/lib/redmine_api_helper/my_account_api_helper.rb +45 -0
- data/lib/redmine_api_helper/news_api_helper.rb +73 -0
- data/lib/redmine_api_helper/project_memberships_api_helper.rb +77 -0
- data/lib/redmine_api_helper/projects_api_helper.rb +73 -0
- data/lib/redmine_api_helper/roles_api_helper.rb +52 -0
- data/lib/redmine_api_helper/scripts_api_helper.rb +87 -0
- data/lib/redmine_api_helper/search_api_helper.rb +38 -0
- data/lib/redmine_api_helper/time_entries_api_helper.rb +73 -0
- data/lib/redmine_api_helper/time_entry_activities_api_helper.rb +38 -0
- data/lib/redmine_api_helper/trackers_api_helper.rb +38 -0
- data/lib/redmine_api_helper/users_api_helper.rb +73 -0
- data/lib/redmine_api_helper/version.rb +24 -0
- data/lib/redmine_api_helper/wiki_pages_api_helper.rb +66 -0
- data/lib/redmine_api_helper.rb +88 -0
- data/redmine_api_helper.gemspec +36 -0
- metadata +162 -0
@@ -0,0 +1,691 @@
|
|
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
|
+
module Parser
|
24
|
+
|
25
|
+
######################################################################################
|
26
|
+
#
|
27
|
+
# Default: html parser and code translator
|
28
|
+
#
|
29
|
+
######################################################################################
|
30
|
+
class Default
|
31
|
+
|
32
|
+
attr_accessor :paragraphs
|
33
|
+
|
34
|
+
####################################################################################
|
35
|
+
#
|
36
|
+
# constants
|
37
|
+
#
|
38
|
+
####################################################################################
|
39
|
+
INLINE = %w( a span strong b em i ins u del strike sub sup code)
|
40
|
+
TEXTINLINE = %w(text:a text:span text:strong text:b text:em text:i text:ins text:u text:del text:strike text:sub text:sup text:code)
|
41
|
+
#SAFETAGS = %w(h1 h2 h3 h4 h5 h6 p a br div span strong b em i ins u del strike sub sup code li ul ol th td tr tbody thead tfoot table )
|
42
|
+
UNSAFETAGS = %w(script style)
|
43
|
+
|
44
|
+
#
|
45
|
+
# table: blank (white) cells, no borders, bold face,
|
46
|
+
# thrifty blank (white) cells, no borders, normal face,
|
47
|
+
# tiny blank (white) cells, no borders, tiny face,
|
48
|
+
# listmedium blank (white) cells, header rows bottom borders, medium face , footer rows top-borders
|
49
|
+
#
|
50
|
+
# list: blank (white) cells, header rows bottom borders, normal face, , footer rows top-borders
|
51
|
+
# boxes: blank (white) cells, all borders, normal face
|
52
|
+
# invoice: blank (white) cells, header rows bottom borders, header rows bold face, footer rows top-borders
|
53
|
+
# caption: blank (white) cells, bold face
|
54
|
+
#
|
55
|
+
# tc: column width
|
56
|
+
# tcs: first column width and last column width
|
57
|
+
#
|
58
|
+
TABLECLASSES = %w(table thrifty tiny list listmedium boxes invoice caption)
|
59
|
+
TABLESTYLES = {:table => {:thead => {:tr => "tr", :ths =>{:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["p", "p", "p"]}, :tds => {:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["p", "p", "p"]} },
|
60
|
+
:tbody => {:tr => "tr", :ths =>{:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["p", "p", "p"]}, :tds => {:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["p", "p", "p"]} },
|
61
|
+
:tfoot => {:tr => "tr", :ths =>{:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["p", "p", "p"]}, :tds => {:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["p", "p", "p"]} },
|
62
|
+
|
63
|
+
:tcs => ["tc", "tc", "tc"]
|
64
|
+
},
|
65
|
+
|
66
|
+
:thrifty => {:thead => {:tr => "tr", :ths =>{:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["p", "p", "p"]}, :tds => {:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["p", "p", "p"]} },
|
67
|
+
:tbody => {:tr => "tr", :ths =>{:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["p", "p", "p"]}, :tds => {:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["p", "p", "p"]} },
|
68
|
+
:tfoot => {:tr => "tr", :ths =>{:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["p", "p", "p"]}, :tds => {:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["p", "p", "p"]} },
|
69
|
+
|
70
|
+
:tcs => ["tc", "tc", "tc"]
|
71
|
+
},
|
72
|
+
|
73
|
+
:tiny => {:thead => {:tr => "tr", :ths =>{:tds => ["td", "td", "td"], :fs => ["small", "small", "small"], :ps => ["p", "p", "p"]}, :tds => {:tds => ["td", "td", "td"], :fs => ["small", "small", "small"], :ps => ["p", "p", "p"]} },
|
74
|
+
:tbody => {:tr => "tr", :ths =>{:tds => ["td", "td", "td"], :fs => ["small", "small", "small"], :ps => ["p", "p", "p"]}, :tds => {:tds => ["td", "td", "td"], :fs => ["small", "small", "small"], :ps => ["p", "p", "p"]} },
|
75
|
+
:tfoot => {:tr => "tr", :ths =>{:tds => ["td", "td", "td"], :fs => ["small", "small", "small"], :ps => ["p", "p", "p"]}, :tds => {:tds => ["td", "td", "td"], :fs => ["small", "small", "small"], :ps => ["p", "p", "p"]} },
|
76
|
+
|
77
|
+
:tcs => ["tc", "tc", "tc"]
|
78
|
+
},
|
79
|
+
|
80
|
+
:list => {:thead => {:tr => "tr", :ths =>{:tds => ["tdhead", "tdhead", "tdhead"], :fs => ["", "", ""], :ps => ["monoright", "mono", "mono"]}, :tds => {:tds => ["tdhead", "tdhead", "tdhead"], :fs => ["", "", ""], :ps => ["right", "p", "p"]} },
|
81
|
+
:tbody => {:tr => "tr", :ths =>{:tds => ["tdhead", "tdhead", "tdhead"], :fs => ["", "", ""], :ps => ["monoright", "mono", "mono"]}, :tds => {:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["right", "p", "p"]} },
|
82
|
+
:tfoot => {:tr => "tr", :ths =>{:tds => ["tdfoot", "tdfoot", "tdfoot"], :fs => ["", "", ""], :ps => ["monoright", "mono", "mono"]}, :tds => {:tds => ["tdfoot", "tdfoot", "tdfoot"], :fs => ["", "", ""], :ps => ["right", "p", "p"]} },
|
83
|
+
|
84
|
+
:tcs => ["tcnarrow", "tcfixed", "tcfixed"]
|
85
|
+
},
|
86
|
+
|
87
|
+
:listmedium => {:thead => {:tr => "tr", :ths =>{:tds => ["tdhead", "tdhead", "tdhead"], :fs => ["medium", "medium", "medium"], :ps => ["monoright", "mono", "mono"]}, :tds => {:tds => ["tdhead", "tdhead", "tdhead"], :fs => ["medium", "medium", "medium"], :ps => ["right", "p", "p"]} },
|
88
|
+
:tbody => {:tr => "tr", :ths =>{:tds => ["tdhead", "tdhead", "tdhead"], :fs => ["medium", "medium", "medium"], :ps => ["monoright", "mono", "mono"]}, :tds => {:tds => ["td", "td", "td"], :fs => ["medium", "medium", "medium"], :ps => ["right", "p", "p"]} },
|
89
|
+
:tfoot => {:tr => "tr", :ths =>{:tds => ["tdfoot", "tdfoot", "tdfoot"], :fs => ["medium", "medium", "medium"], :ps => ["monoright", "mono", "mono"]}, :tds => {:tds => ["tdfoot", "tdfoot", "tdfoot"], :fs => ["medium", "medium", "medium"], :ps => ["right", "p", "p"]} },
|
90
|
+
|
91
|
+
:tcs => ["tcnarrow", "tcfixed", "tcfixed"]
|
92
|
+
},
|
93
|
+
|
94
|
+
:boxes => {:thead => {:tr => "tr", :ths =>{:tds => ["tdbox", "tdbox", "tdbox"], :fs => ["", "", ""], :ps => ["monocenter", "monocenter", "monocenter"]}, :tds => {:tds => ["tdbox", "tdbox", "tdbox"], :fs => ["", "", ""], :ps => ["center", "center", "center"]} },
|
95
|
+
:tbody => {:tr => "tr", :ths =>{:tds => ["tdbox", "tdbox", "tdbox"], :fs => ["", "", ""], :ps => ["monocenter", "monocenter", "monocenter"]}, :tds => {:tds => ["tdbox", "tdbox", "tdbox"], :fs => ["", "", ""], :ps => ["center", "center", "center"]} },
|
96
|
+
:tfoot => {:tr => "tr", :ths =>{:tds => ["tdbox", "tdbox", "tdbox"], :fs => ["", "", ""], :ps => ["monocenter", "monocenter", "monocenter"]}, :tds => {:tds => ["tdbox", "tdbox", "tdbox"], :fs => ["", "", ""], :ps => ["center", "center", "center"]} },
|
97
|
+
|
98
|
+
:tcs => ["tc", "tc", "tc"]
|
99
|
+
},
|
100
|
+
|
101
|
+
:invoice => {:thead => {:tr => "tr", :ths =>{:tds => ["tdhead", "tdhead", "tdhead"], :fs => ["medium", "medium", "medium"], :ps => ["mono", "monoright", "monoright" ]}, :tds => {:tds => ["tdhead", "tdhead", "tdhead"], :fs => ["medium", "medium", "medium"], :ps => ["p", "right", "right" ]} },
|
102
|
+
:tbody => {:tr => "tr", :ths =>{:tds => ["tdhead", "tdhead", "tdhead"], :fs => ["medium", "medium", "medium"], :ps => ["mono", "monoright", "monoright" ]}, :tds => {:tds => ["td", "td", "td"], :fs => ["medium", "medium", "medium"], :ps => ["p", "right", "right" ]} },
|
103
|
+
:tfoot => {:tr => "tr", :ths =>{:tds => ["tdfoot", "tdfoot", "tdfoot"], :fs => ["medium", "medium", "medium"], :ps => ["mono", "monoright", "monoright" ]}, :tds => {:tds => ["tdfoot", "tdfoot", "tdfoot"], :fs => ["medium", "medium", "medium"], :ps => ["p", "right", "right" ]} },
|
104
|
+
|
105
|
+
:tcs => ["tcwide", "tcnarrow", "tcnarrow"]
|
106
|
+
},
|
107
|
+
|
108
|
+
:caption => {:thead => {:tr => "tr", :ths =>{:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["mono", "mono", "mono"]}, :tds => {:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["mono", "mono", "mono"]} },
|
109
|
+
:tbody => {:tr => "tr", :ths =>{:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["mono", "mono", "mono"]}, :tds => {:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["mono", "mono", "mono"]} },
|
110
|
+
:tfoot => {:tr => "tr", :ths =>{:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["mono", "mono", "mono"]}, :tds => {:tds => ["td", "td", "td"], :fs => ["", "", ""], :ps => ["mono", "mono", "mono"]} },
|
111
|
+
|
112
|
+
:tcs => ["tcnarrow", "tcwide", "tcwide"]
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
####################################################################################
|
117
|
+
#
|
118
|
+
# initialize
|
119
|
+
#
|
120
|
+
####################################################################################
|
121
|
+
def initialize(text, node, opts={})
|
122
|
+
@text = text
|
123
|
+
@paragraphs = []
|
124
|
+
@template_node = node
|
125
|
+
@doc = opts[:doc]
|
126
|
+
@remove_classes = opts[:remove_classes]
|
127
|
+
@remove_class_prefix = opts[:remove_class_prefix]
|
128
|
+
@remove_class_suffix = opts[:remove_class_suffix]
|
129
|
+
|
130
|
+
@styles = opts[:styles]
|
131
|
+
|
132
|
+
parse
|
133
|
+
end #def
|
134
|
+
|
135
|
+
####################################################################################
|
136
|
+
#
|
137
|
+
# parse
|
138
|
+
#
|
139
|
+
####################################################################################
|
140
|
+
def parse
|
141
|
+
|
142
|
+
#xml = @template_node.parse("<html>#{@text}</html>")
|
143
|
+
xml = "<html>#{@text}</html>"
|
144
|
+
odf = parse_formatting(xml).css("html").inner_html
|
145
|
+
@paragraphs << odf
|
146
|
+
return
|
147
|
+
end #def
|
148
|
+
|
149
|
+
|
150
|
+
####################################################################################
|
151
|
+
#
|
152
|
+
# private
|
153
|
+
#
|
154
|
+
####################################################################################
|
155
|
+
private
|
156
|
+
|
157
|
+
def parse_formatting(tag, level=0)
|
158
|
+
|
159
|
+
#
|
160
|
+
# strip superfluous control characters
|
161
|
+
#
|
162
|
+
duptag = tag.dup.to_s.gsub(/\n|\r|\t/, " ")
|
163
|
+
|
164
|
+
#
|
165
|
+
# strip superfluous whitespace
|
166
|
+
#
|
167
|
+
duptag.gsub!(/ /, " ") while duptag.match(/ /)
|
168
|
+
|
169
|
+
html = Nokogiri::XML( tag.to_s )
|
170
|
+
|
171
|
+
#
|
172
|
+
# remove unsafe tags
|
173
|
+
#
|
174
|
+
UNSAFETAGS.each do |ust|
|
175
|
+
html.xpath("//*[self::#{ust}]").each do |node|
|
176
|
+
node.remove
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
#
|
181
|
+
# divisor - just unpack
|
182
|
+
#
|
183
|
+
html.xpath("//*[self::div]").reverse.each do |node|
|
184
|
+
node.replace( node.dup.children )
|
185
|
+
end
|
186
|
+
#html.xpath("//*[self::div]").each {|node| node.replace(text_node( "p", node)) }
|
187
|
+
|
188
|
+
#
|
189
|
+
# remove requested tags with class
|
190
|
+
#
|
191
|
+
if @remove_classes.present?
|
192
|
+
case @remove_classes
|
193
|
+
when Array
|
194
|
+
contains = @remove_classes.map{|r| "contains(., '#{r}')"}.join(" or ")
|
195
|
+
else
|
196
|
+
contains = "contains(., '#{@remove_classes}')"
|
197
|
+
end
|
198
|
+
#nodes = html.xpath(".//*[@class[contains(., '#{contains}')]]")
|
199
|
+
nodes = html.xpath(".//*[@class[#{contains}]]")
|
200
|
+
nodes.each { |node| node.remove }
|
201
|
+
end
|
202
|
+
|
203
|
+
#
|
204
|
+
# remove requested class prefixes
|
205
|
+
#
|
206
|
+
if @remove_class_prefix.present?
|
207
|
+
case @remove_class_prefix
|
208
|
+
when Array
|
209
|
+
contains = @remove_class_prefix.map{|r| "contains(., '#{r}')"}.join(" or ")
|
210
|
+
else
|
211
|
+
contains = "contains(., '#{@remove_class_prefix}')"
|
212
|
+
end
|
213
|
+
#nodes = html.xpath(".//*[@class[ contains(., '#{@remove_class_prefix}')]]")
|
214
|
+
nodes = html.xpath(".//*[@class[#{contains}]]")
|
215
|
+
nodes.each do |node|
|
216
|
+
css_classes = node.attr("class").split(" ").select{|c| c.present?}
|
217
|
+
case @remove_class_prefix
|
218
|
+
when Array
|
219
|
+
@remove_class_prefix.each do |rcp|
|
220
|
+
css_classes.map!{ |css_class| css_class.gsub!(/\A#{rcp}(.*)\z/) { $1 } }
|
221
|
+
end
|
222
|
+
else
|
223
|
+
css_classes.map!{ |css_class| css_class.gsub!(/\A#{@remove_class_prefix}(.*)\z/) { $1 } }
|
224
|
+
end
|
225
|
+
node.set_attribute("class", css_classes.join(" "))
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
#
|
230
|
+
# remove requested class suffixes
|
231
|
+
#
|
232
|
+
if @remove_class_suffix.present?
|
233
|
+
case @remove_class_prefix
|
234
|
+
when Array
|
235
|
+
contains = @remove_class_suffix.map{|r| "contains(., '#{r}')"}.join(" or ")
|
236
|
+
else
|
237
|
+
contains = "contains(., '#{@remove_class_suffix}')"
|
238
|
+
end
|
239
|
+
#nodes = html.xpath(".//*[@class[ contains(., '#{@remove_class_suffix}')]]")
|
240
|
+
nodes = html.xpath(".//*[@class[#{contains}]]")
|
241
|
+
nodes.each do |node|
|
242
|
+
css_classes = node.attr("class").split(" ").select{|c| c.present?}
|
243
|
+
case @remove_class_prefix
|
244
|
+
when Array
|
245
|
+
@remove_class_suffix.each do |rcs|
|
246
|
+
css_classes.map!{ |css_class| css_class.gsub!(/\A(.*)#{rcs}\z/) { $1 } }
|
247
|
+
end
|
248
|
+
else
|
249
|
+
css_classes.map!{ |css_class| css_class.gsub!(/\A}(.*)#{@remove_class_suffix}\z/) { $1 } }
|
250
|
+
end
|
251
|
+
node.set_attribute("class", css_classes.join(" "))
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
#
|
256
|
+
# --- html nestable elements -------------------------------------------------------
|
257
|
+
#
|
258
|
+
|
259
|
+
#
|
260
|
+
# nested list items
|
261
|
+
#
|
262
|
+
html.xpath("//*[self::li]").each do |node|
|
263
|
+
|
264
|
+
li = text_node("list-item")
|
265
|
+
|
266
|
+
node.xpath("./text()").each do |text|
|
267
|
+
text.replace( blank_node("p", "", text) ) if text.text.present?
|
268
|
+
text.remove unless text.text.present?
|
269
|
+
end
|
270
|
+
|
271
|
+
node.children.each do |child|
|
272
|
+
child = child.replace( text_node("p", "") << child.dup ) if INLINE.include?(child.name)
|
273
|
+
child = child.replace( text_node("p", "") << child.dup ) if TEXTINLINE.include?(child.name)
|
274
|
+
li << parse_formatting(child, level+1).root
|
275
|
+
end
|
276
|
+
|
277
|
+
node.replace( li )
|
278
|
+
end
|
279
|
+
|
280
|
+
#
|
281
|
+
# nested unordered lists
|
282
|
+
#
|
283
|
+
html.xpath("//*[self::ul]").each do |node|
|
284
|
+
|
285
|
+
# unpack lists, which should not be encapsulated in a <p> tag,
|
286
|
+
# to be compatible with odf. Move them below the paragraph
|
287
|
+
if ["text:p", "p"].include? node.parent.name
|
288
|
+
node = node.parent.add_next_sibling( node )
|
289
|
+
end
|
290
|
+
|
291
|
+
ul = text_node("list", "ul", node)
|
292
|
+
node.replace( parse_formatting(ul, level+1).root )
|
293
|
+
end
|
294
|
+
|
295
|
+
#
|
296
|
+
# nested unordered lists
|
297
|
+
#
|
298
|
+
html.xpath("//*[self::ol]").each do |node|
|
299
|
+
|
300
|
+
# unpack lists, which should not be encapsulated in a <p> tag,
|
301
|
+
# to be compatible with odf. Move them below the paragraph
|
302
|
+
if ["text:p", "p"].include? node.parent.name
|
303
|
+
node = node.parent.add_next_sibling( node )
|
304
|
+
end
|
305
|
+
|
306
|
+
ol = text_node("list", "ol", node)
|
307
|
+
node.replace( parse_formatting(ol, level+1).root )
|
308
|
+
end
|
309
|
+
|
310
|
+
#
|
311
|
+
# --- html tables -----------------------------------------------------------------
|
312
|
+
#
|
313
|
+
|
314
|
+
#
|
315
|
+
# tables
|
316
|
+
#
|
317
|
+
html.xpath("//*[self::table]").each do |node|
|
318
|
+
|
319
|
+
# unpack tables, which should not be encapsulated in a <p> tag,
|
320
|
+
# to be compatible with odf. Move them below the paragraph
|
321
|
+
if ["text:p", "p"].include? node.parent.name
|
322
|
+
node = node.parent.add_next_sibling( node )
|
323
|
+
end
|
324
|
+
|
325
|
+
#
|
326
|
+
# use last matching css class for matching a local style
|
327
|
+
#
|
328
|
+
cssclasses = node["class"].to_s.split(/\ /)
|
329
|
+
cssc = (cssclasses & TABLECLASSES).last.presence&.to_sym || :table
|
330
|
+
|
331
|
+
table = table_node("table", cssc.to_s, node)
|
332
|
+
table["table:template-name"]= cssc.to_s.camelcase
|
333
|
+
#new_table = node.replace( parse_formatting(table, level+1).root )
|
334
|
+
new_table = node.replace( table )
|
335
|
+
|
336
|
+
max_cols = node.
|
337
|
+
xpath(".//*[local-name()='tr']").
|
338
|
+
map{|tr| tr.xpath("*[local-name()='td']")}.
|
339
|
+
map{|a| a.length}.max
|
340
|
+
|
341
|
+
max_cols.to_i.times do |col_index|
|
342
|
+
|
343
|
+
if col_index == 0 # last
|
344
|
+
tccss = TABLESTYLES.dig(cssc, :tcs ).to_a[2]
|
345
|
+
|
346
|
+
elsif col_index == (max_cols - 1) # first
|
347
|
+
tccss = TABLESTYLES.dig(cssc, :tcs ).to_a[0]
|
348
|
+
|
349
|
+
else
|
350
|
+
tccss = TABLESTYLES.dig(cssc, :tcs ).to_a[1]
|
351
|
+
end
|
352
|
+
|
353
|
+
tc = table_node("table-column", tccss)
|
354
|
+
new_table.children.first&.before( tc ) # inserted in reverse order
|
355
|
+
end
|
356
|
+
|
357
|
+
#---------------------------------------------------------------------------------
|
358
|
+
# table row groups thead, tbody, tfoot
|
359
|
+
#
|
360
|
+
|
361
|
+
# if plain table without rowgroups, then add rowgroup
|
362
|
+
rowgroups_count = 0
|
363
|
+
%i(thead tbody tfoot).each do |rowgroup|
|
364
|
+
rowgroups_count += new_table.xpath(".//*[self::#{rowgroup}]").length
|
365
|
+
end
|
366
|
+
if rowgroups_count == 0
|
367
|
+
tbody = Nokogiri::XML::Node.new("tbody", @doc)
|
368
|
+
tbody << new_table.xpath(".//*[self::tr]")
|
369
|
+
new_table.xpath(".//*[self::tr]").unlink
|
370
|
+
new_table << tbody
|
371
|
+
end
|
372
|
+
|
373
|
+
# handle all rowgroups
|
374
|
+
%i(thead tbody tfoot).each do |rowgroup|
|
375
|
+
|
376
|
+
#
|
377
|
+
# traverse thead, tbody and tfoot
|
378
|
+
#
|
379
|
+
new_table.xpath(".//*[self::#{rowgroup}]").each do |row_group_node|
|
380
|
+
case rowgroup
|
381
|
+
when :thead
|
382
|
+
trg = table_node("table-header-rows", "", row_group_node)
|
383
|
+
else
|
384
|
+
trg = table_node("table-rows", "", row_group_node)
|
385
|
+
end
|
386
|
+
#new_row_group_node = row_group_node.replace( parse_formatting(trg, level+1).root )
|
387
|
+
new_row_group_node = row_group_node.replace( trg )
|
388
|
+
|
389
|
+
#-----------------------------------------------------------------------------
|
390
|
+
# table rows
|
391
|
+
#
|
392
|
+
new_row_group_node.xpath(".//*[self::tr]").each_with_index do |tr_node, tr_index|
|
393
|
+
|
394
|
+
trcss = TABLESTYLES.dig(cssc, rowgroup, :tr )
|
395
|
+
|
396
|
+
tr = table_node("table-row", trcss, tr_node)
|
397
|
+
#new_tr_node = tr_node.replace( parse_formatting(tr).root )
|
398
|
+
new_tr_node = tr_node.replace( tr )
|
399
|
+
|
400
|
+
#---------------------------------------------------------------------------
|
401
|
+
# table body cells
|
402
|
+
#
|
403
|
+
new_tr_node.xpath(".//*[self::th or self::td]").each_with_index do |td_node, td_index|
|
404
|
+
|
405
|
+
if td_index == 0 #first column
|
406
|
+
tdcss = TABLESTYLES.dig(cssc, rowgroup, "#{td_node.name}s".to_sym, :tds ).to_a[0]
|
407
|
+
pcss = TABLESTYLES.dig(cssc, rowgroup, "#{td_node.name}s".to_sym, :ps ).to_a[0]
|
408
|
+
fcss = TABLESTYLES.dig(cssc, rowgroup, "#{td_node.name}s".to_sym, :fs ).to_a[0]
|
409
|
+
elsif td_index == (max_cols - 1) #last column
|
410
|
+
tdcss = TABLESTYLES.dig(cssc, rowgroup, "#{td_node.name}s".to_sym, :tds ).to_a[2]
|
411
|
+
pcss = TABLESTYLES.dig(cssc, rowgroup, "#{td_node.name}s".to_sym, :ps ).to_a[2]
|
412
|
+
fcss = TABLESTYLES.dig(cssc, rowgroup, "#{td_node.name}s".to_sym, :fs ).to_a[2]
|
413
|
+
else
|
414
|
+
tdcss = TABLESTYLES.dig(cssc, rowgroup, "#{td_node.name}s".to_sym, :tds ).to_a[1]
|
415
|
+
pcss = TABLESTYLES.dig(cssc, rowgroup, "#{td_node.name}s".to_sym, :ps ).to_a[1]
|
416
|
+
fcss = TABLESTYLES.dig(cssc, rowgroup, "#{td_node.name}s".to_sym, :fs ).to_a[1]
|
417
|
+
end
|
418
|
+
|
419
|
+
td = table_node("table-cell", tdcss)
|
420
|
+
|
421
|
+
# replace all free text in table cell by a p node
|
422
|
+
td_node.xpath("./text()").each do |text|
|
423
|
+
tx = blank_node( "span", fcss, text)
|
424
|
+
text.replace( text_node("p", pcss ) << tx ) if text.text.present?
|
425
|
+
text.remove unless text.text.present?
|
426
|
+
end
|
427
|
+
|
428
|
+
# encapsulate all free text in table children in spans
|
429
|
+
td_node.children.each do |child|
|
430
|
+
child.xpath("./text()").each do |text|
|
431
|
+
tx = blank_node( "span", fcss, text)
|
432
|
+
text.replace( tx ) if text.text.present? #&& child.name != "span"
|
433
|
+
text.remove unless text.text.present? #&& child.name != "span"
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
# replace all inline text in table cell
|
438
|
+
td_node.children.each do |child|
|
439
|
+
if (INLINE + TEXTINLINE).include?( child.name )
|
440
|
+
tx = blank_node( "span", fcss ) << child.dup
|
441
|
+
child = child.replace( text_node("p", pcss) << tx.dup )
|
442
|
+
end
|
443
|
+
td << parse_formatting(child).root
|
444
|
+
end
|
445
|
+
|
446
|
+
td["table:number-columns-spanned"]= td_node['colspan'] if td_node['colspan'].present?
|
447
|
+
td["table:number-rows-spanned"] = td_node['rowspan'] if td_node['rowspan'].present?
|
448
|
+
|
449
|
+
new_td_node = td_node.replace( td )
|
450
|
+
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
#
|
458
|
+
# --- html elements and entities --------------------------------------------------
|
459
|
+
#
|
460
|
+
|
461
|
+
#
|
462
|
+
# newline
|
463
|
+
#
|
464
|
+
html.xpath("//*[self::br]").each {|node| node.replace(blank_node( "line-break")) }
|
465
|
+
|
466
|
+
#
|
467
|
+
# horizontal ruler
|
468
|
+
#
|
469
|
+
html.xpath("//*[self::hr]").each {|node| node.replace(blank_node( "p")) }
|
470
|
+
|
471
|
+
|
472
|
+
#
|
473
|
+
# --- html block elements ---------------------------------------------------------
|
474
|
+
#
|
475
|
+
|
476
|
+
#
|
477
|
+
# headings
|
478
|
+
#
|
479
|
+
html.xpath("//*[self::h1]").each {|node| node.replace(text_node( "p", node)) }
|
480
|
+
html.xpath("//*[self::h2]").each {|node| node.replace(text_node( "p", node)) }
|
481
|
+
html.xpath("//*[self::h3]").each {|node| node.replace(text_node( "p", node)) }
|
482
|
+
html.xpath("//*[self::h4]").each {|node| node.replace(text_node( "p", node)) }
|
483
|
+
html.xpath("//*[self::h5]").each {|node| node.replace(text_node( "p", node)) }
|
484
|
+
html.xpath("//*[self::h6]").each {|node| node.replace(text_node( "p", node)) }
|
485
|
+
|
486
|
+
#
|
487
|
+
# paragraph
|
488
|
+
#
|
489
|
+
html.xpath("//*[self::p]").each {|node| node.replace(text_node( "p", node)) }
|
490
|
+
|
491
|
+
#
|
492
|
+
# pre
|
493
|
+
#
|
494
|
+
html.xpath("//*[self::pre]").each {|node| node.replace(text_node( "p", node)) }
|
495
|
+
|
496
|
+
#
|
497
|
+
# --- html inline elements ---------------------------------------------------------
|
498
|
+
#
|
499
|
+
|
500
|
+
#
|
501
|
+
# bold
|
502
|
+
#
|
503
|
+
html.xpath("//*[self::strong or self::b]").each {|node| node.replace(text_node( "span", "bold", node)) }
|
504
|
+
|
505
|
+
#
|
506
|
+
# italic
|
507
|
+
#
|
508
|
+
html.xpath("//*[self::em or self::i]").each {|node| node.replace(text_node( "span", "italic", node)) }
|
509
|
+
|
510
|
+
#
|
511
|
+
# underline
|
512
|
+
#
|
513
|
+
html.xpath("//*[self::ins or self::u]").each {|node| node.replace(text_node( "span", "underline", node)) }
|
514
|
+
|
515
|
+
#
|
516
|
+
# strikethrough
|
517
|
+
#
|
518
|
+
html.xpath("//*[self::del or self::strike]").each {|node| node.replace(text_node( "span", "strikethrough", node)) }
|
519
|
+
|
520
|
+
#
|
521
|
+
# superscript and subscript
|
522
|
+
#
|
523
|
+
html.xpath("//*[self::sup]").each {|node| node.replace(text_node( "span", "sup", node)) }
|
524
|
+
html.xpath("//*[self::sub]").each {|node| node.replace(text_node( "span", "sub", node)) }
|
525
|
+
|
526
|
+
#
|
527
|
+
# code
|
528
|
+
#
|
529
|
+
html.xpath("//*[self::code]").each {|node| node.replace(text_node( "span", "code", node)) }
|
530
|
+
|
531
|
+
#
|
532
|
+
# hyperlink or anchor or anchor with content
|
533
|
+
#
|
534
|
+
html.xpath("//*[self::a]").each do |node|
|
535
|
+
|
536
|
+
#
|
537
|
+
# self closing a-tag: bookmark
|
538
|
+
#
|
539
|
+
if node['href'].present?
|
540
|
+
cont = text_node("span", "a", node)
|
541
|
+
#a = office_node("a") << cont
|
542
|
+
a = text_node("a") << cont
|
543
|
+
a["xlink:href"]= node['href']
|
544
|
+
a["office:target-frame-name"]="_top"
|
545
|
+
a["xlink:show"]="replace"
|
546
|
+
else
|
547
|
+
a = text_node("bookmark")
|
548
|
+
a["text:name"]=node['name']
|
549
|
+
end
|
550
|
+
node.replace(a)
|
551
|
+
end
|
552
|
+
|
553
|
+
html
|
554
|
+
end #def
|
555
|
+
|
556
|
+
|
557
|
+
def blank_node( name, node_or_style=nil, node=nil )
|
558
|
+
p = text_node( name, node_or_style )
|
559
|
+
p.content = node.text if node
|
560
|
+
p
|
561
|
+
end #def
|
562
|
+
|
563
|
+
def office_node( name, node_or_style=nil, node=nil )
|
564
|
+
|
565
|
+
p = Nokogiri::XML::Node.new("office:#{name}", @doc)
|
566
|
+
|
567
|
+
if node_or_style.nil?
|
568
|
+
#nothing
|
569
|
+
elsif node_or_style.blank?
|
570
|
+
p << node.dup.children if node
|
571
|
+
elsif node_or_style.is_a?(String)
|
572
|
+
p['text:style-name']=node_or_style
|
573
|
+
p << node.dup.children if node
|
574
|
+
else
|
575
|
+
p['text:style-name']=check_style( node_or_style )
|
576
|
+
p << node_or_style.dup.children
|
577
|
+
end
|
578
|
+
p
|
579
|
+
|
580
|
+
end #def
|
581
|
+
|
582
|
+
def text_node( name, node_or_style=nil, node=nil )
|
583
|
+
|
584
|
+
p = Nokogiri::XML::Node.new("text:#{name}", @doc)
|
585
|
+
|
586
|
+
if node_or_style.nil?
|
587
|
+
#nothing
|
588
|
+
elsif node_or_style.blank?
|
589
|
+
p << node.dup.children if node
|
590
|
+
elsif node_or_style.is_a?(String)
|
591
|
+
p['text:style-name']=node_or_style
|
592
|
+
p << node.dup.children if node
|
593
|
+
else
|
594
|
+
p['text:style-name']=check_style( node_or_style )
|
595
|
+
p << node_or_style.dup.children
|
596
|
+
end
|
597
|
+
p
|
598
|
+
end #def
|
599
|
+
|
600
|
+
def table_node( name, node_or_style=nil, node=nil )
|
601
|
+
|
602
|
+
p = Nokogiri::XML::Node.new("table:#{name}", @doc)
|
603
|
+
|
604
|
+
if node_or_style.nil?
|
605
|
+
#nothing
|
606
|
+
elsif node_or_style.blank?
|
607
|
+
p << node.dup.children if node
|
608
|
+
elsif node_or_style.is_a?(String)
|
609
|
+
p['table:style-name']=node_or_style
|
610
|
+
p << node.dup.children if node
|
611
|
+
else
|
612
|
+
p['table:style-name']=check_style( node_or_style )
|
613
|
+
p << node_or_style.dup.children
|
614
|
+
end
|
615
|
+
p
|
616
|
+
end #def
|
617
|
+
|
618
|
+
def check_style(node)
|
619
|
+
|
620
|
+
style = ""
|
621
|
+
|
622
|
+
#
|
623
|
+
# header or
|
624
|
+
#
|
625
|
+
if node.name =~ /h(\d)/i
|
626
|
+
style = node.name.downcase
|
627
|
+
|
628
|
+
#
|
629
|
+
# quote or
|
630
|
+
#
|
631
|
+
elsif node.name == "p" && node.parent && node.parent.name == "blockquote"
|
632
|
+
style = "quote"
|
633
|
+
|
634
|
+
#
|
635
|
+
# pre
|
636
|
+
#
|
637
|
+
elsif node.name == "pre"
|
638
|
+
style = "pre"
|
639
|
+
|
640
|
+
#
|
641
|
+
# paragraph
|
642
|
+
#
|
643
|
+
elsif node.name == "p"
|
644
|
+
style = "paragraph"
|
645
|
+
|
646
|
+
end
|
647
|
+
|
648
|
+
#
|
649
|
+
# class overrides header / quote
|
650
|
+
#
|
651
|
+
if node["class"].present?
|
652
|
+
|
653
|
+
style = node["class"]
|
654
|
+
style = remove_prefixes( @remove_class_prefix, style ) if @remove_class_prefix.present?
|
655
|
+
style = remove_suffixes( @remove_class_suffix, style ) if @remove_class_suffix.present?
|
656
|
+
end
|
657
|
+
|
658
|
+
#
|
659
|
+
# style overrides class
|
660
|
+
#
|
661
|
+
case node["style"]
|
662
|
+
when /text-align:(\s*)center/
|
663
|
+
style = "center"
|
664
|
+
when /text-align:(\s*)left/
|
665
|
+
style = "left"
|
666
|
+
when /text-align:(\s*)right/
|
667
|
+
style = "right"
|
668
|
+
when /text-align:(\s*)justify/
|
669
|
+
style = "justify"
|
670
|
+
end
|
671
|
+
|
672
|
+
style ||= node.name
|
673
|
+
|
674
|
+
style
|
675
|
+
end #def
|
676
|
+
|
677
|
+
def remove_prefixes( prefix_array, classes_string)
|
678
|
+
css_classes = classes_string.split(/\s+/)
|
679
|
+
regex_raw = prefix_array.map{ |p| "\\A#{p}(.*?)\\z" }.join("|")
|
680
|
+
css_classes.map{ |css_class| (v = css_class.match(/#{regex_raw}/) { $1.to_s + $2.to_s + $3.to_s }; v.present? ? v : css_class) }.join(" ")
|
681
|
+
end #def
|
682
|
+
|
683
|
+
def remove_suffixes( prefix_array, classes_string)
|
684
|
+
css_classes = classes_string.split(/\s+/)
|
685
|
+
regex_raw = prefix_array.map{ |p| "\\A(.*?)#{p}\\z" }.join("|")
|
686
|
+
css_classes.map{ |css_class| (v = css_class.match(/#{regex_raw}/) { $1.to_s + $2.to_s + $3.to_s }; v.present? ? v : css_class) }.join(" ")
|
687
|
+
end #def
|
688
|
+
|
689
|
+
end #class
|
690
|
+
end #module
|
691
|
+
end #module
|