hatemile 2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CODE_OF_CONDUCT.md +46 -0
- data/Gemfile +9 -0
- data/LICENSE +202 -0
- data/Rakefile +64 -0
- data/hatemile.gemspec +37 -0
- data/lib/hatemile/accessible_association.rb +62 -0
- data/lib/hatemile/accessible_css.rb +43 -0
- data/lib/hatemile/accessible_display.rb +178 -0
- data/lib/hatemile/accessible_event.rb +95 -0
- data/lib/hatemile/accessible_form.rb +101 -0
- data/lib/hatemile/accessible_navigation.rb +82 -0
- data/lib/hatemile/helper.rb +58 -0
- data/lib/hatemile/implementation/accessible_association_implementation.rb +346 -0
- data/lib/hatemile/implementation/accessible_css_implementation.rb +772 -0
- data/lib/hatemile/implementation/accessible_display_implementation.rb +1362 -0
- data/lib/hatemile/implementation/accessible_event_implementation.rb +278 -0
- data/lib/hatemile/implementation/accessible_form_implementation.rb +386 -0
- data/lib/hatemile/implementation/accessible_navigation_implementation.rb +561 -0
- data/lib/hatemile/util/common_functions.rb +106 -0
- data/lib/hatemile/util/configure.rb +92 -0
- data/lib/hatemile/util/css/rcp/rcp_declaration.rb +77 -0
- data/lib/hatemile/util/css/rcp/rcp_parser.rb +115 -0
- data/lib/hatemile/util/css/rcp/rcp_rule.rb +86 -0
- data/lib/hatemile/util/css/style_sheet_declaration.rb +59 -0
- data/lib/hatemile/util/css/style_sheet_parser.rb +43 -0
- data/lib/hatemile/util/css/style_sheet_rule.rb +73 -0
- data/lib/hatemile/util/html/html_dom_element.rb +234 -0
- data/lib/hatemile/util/html/html_dom_node.rb +131 -0
- data/lib/hatemile/util/html/html_dom_parser.rb +150 -0
- data/lib/hatemile/util/html/html_dom_text_node.rb +43 -0
- data/lib/hatemile/util/html/nokogiri/nokogiri_html_dom_element.rb +302 -0
- data/lib/hatemile/util/html/nokogiri/nokogiri_html_dom_node.rb +112 -0
- data/lib/hatemile/util/html/nokogiri/nokogiri_html_dom_parser.rb +208 -0
- data/lib/hatemile/util/html/nokogiri/nokogiri_html_dom_text_node.rb +83 -0
- data/lib/hatemile/util/id_generator.rb +53 -0
- data/lib/js/common.js +98 -0
- data/lib/js/eventlistener.js +36 -0
- data/lib/js/include.js +292 -0
- data/lib/js/scriptlist_validation_fields.js +13 -0
- data/lib/js/validation.js +205 -0
- data/lib/locale/en-US.yml +388 -0
- data/lib/locale/pt-BR.yml +389 -0
- data/lib/skippers.xml +6 -0
- data/lib/symbols.xml +40 -0
- data/test/locale/en-US.yml +5 -0
- data/test/locale/pt-BR.yml +4 -0
- data/test/test_accessible_association_implementation.rb +258 -0
- data/test/test_accessible_css_implementation.rb +518 -0
- data/test/test_accessible_display_implementation.rb +873 -0
- data/test/test_accessible_form_implementation.rb +283 -0
- data/test/test_accessible_navigation_implementation.rb +228 -0
- data/test/test_common_functions.rb +128 -0
- data/test/test_configure.rb +73 -0
- data/test/test_nokogiri_html_dom_element.rb +586 -0
- data/test/test_nokogiri_html_dom_parser.rb +363 -0
- data/test/test_nokogiri_html_dom_text_node.rb +225 -0
- data/test/test_rcp_declaration.rb +103 -0
- data/test/test_rcp_parser.rb +86 -0
- data/test/test_rcp_rule.rb +89 -0
- metadata +199 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
# you may not use this file except in compliance with the License.
|
3
|
+
# You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
|
13
|
+
##
|
14
|
+
# The Hatemile module contains the interfaces with the acessibility solutions.
|
15
|
+
module Hatemile
|
16
|
+
##
|
17
|
+
# Helper methods of HaTeMiLe for Ruby.
|
18
|
+
module Helper
|
19
|
+
##
|
20
|
+
# Error class for nil arguments.
|
21
|
+
class NotNilError < StandardError; end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Checks that the specified objects references is not nil and throws a
|
25
|
+
# TypeError if it is.
|
26
|
+
#
|
27
|
+
# @param values [Array<Object>] The objects.
|
28
|
+
# @return [void]
|
29
|
+
def self.require_not_nil(*values)
|
30
|
+
values.each do |value|
|
31
|
+
if value.nil?
|
32
|
+
raise NotNilError.new('The value of parameter not be nil.')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Checks that the specified object reference is instance of classes and
|
39
|
+
# TypeError
|
40
|
+
#
|
41
|
+
# @param value [Object] The object.
|
42
|
+
# @param classes [Array<Class>] The classes.
|
43
|
+
# @return [void]
|
44
|
+
def self.require_valid_type(value, *classes)
|
45
|
+
return if value.nil?
|
46
|
+
|
47
|
+
valid = false
|
48
|
+
classes.each do |auxiliar_class|
|
49
|
+
if value.is_a?(auxiliar_class)
|
50
|
+
valid = true
|
51
|
+
break
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
raise TypeError.new('Wrong type of argument.') unless valid
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,346 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
# you may not use this file except in compliance with the License.
|
3
|
+
# You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
|
13
|
+
require File.join(
|
14
|
+
File.dirname(File.dirname(__FILE__)),
|
15
|
+
'accessible_association'
|
16
|
+
)
|
17
|
+
require File.join(File.dirname(File.dirname(__FILE__)), 'helper')
|
18
|
+
require File.join(
|
19
|
+
File.dirname(File.dirname(__FILE__)),
|
20
|
+
'util',
|
21
|
+
'common_functions'
|
22
|
+
)
|
23
|
+
require File.join(File.dirname(File.dirname(__FILE__)), 'util', 'id_generator')
|
24
|
+
require File.join(
|
25
|
+
File.dirname(File.dirname(__FILE__)),
|
26
|
+
'util',
|
27
|
+
'html',
|
28
|
+
'html_dom_parser'
|
29
|
+
)
|
30
|
+
|
31
|
+
##
|
32
|
+
# The Hatemile module contains the interfaces with the acessibility solutions.
|
33
|
+
module Hatemile
|
34
|
+
##
|
35
|
+
# The Hatemile::Implementation module contains the official implementation of
|
36
|
+
# interfaces solutions.
|
37
|
+
module Implementation
|
38
|
+
##
|
39
|
+
# The AccessibleAssociationImplementation class is official implementation
|
40
|
+
# of AccessibleAssociation.
|
41
|
+
class AccessibleAssociationImplementation < AccessibleAssociation
|
42
|
+
public_class_method :new
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
##
|
47
|
+
# Returns a list that represents the table.
|
48
|
+
#
|
49
|
+
# @param part [Hatemile::Util::Html::HTMLDOMElement] The table header,
|
50
|
+
# table footer or table body.
|
51
|
+
# @return [Array<Array<Hatemile::Util::Html::HTMLDOMElement>>] The list
|
52
|
+
# that represents the table.
|
53
|
+
def get_model_table(part)
|
54
|
+
rows = @parser.find(part).find_children('tr').list_results
|
55
|
+
table = []
|
56
|
+
rows.each do |row|
|
57
|
+
table.push(
|
58
|
+
get_model_row(@parser.find(row).find_children('td,th').list_results)
|
59
|
+
)
|
60
|
+
end
|
61
|
+
get_valid_model_table(table)
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Returns a list that represents the table with the rowspans.
|
66
|
+
#
|
67
|
+
# @param table [Array<Array<Hatemile::Util::Html::HTMLDOMElement>>] The
|
68
|
+
# list that represents the table without the rowspans.
|
69
|
+
# @return [Array<Array<Hatemile::Util::Html::HTMLDOMElement>>] The list
|
70
|
+
# that represents the table with the rowspans.
|
71
|
+
def get_valid_model_table(table)
|
72
|
+
new_table = []
|
73
|
+
unless table.empty?
|
74
|
+
length_table = table.size
|
75
|
+
(0..length_table - 1).each do |row_index|
|
76
|
+
cells_added = 0
|
77
|
+
original_row = [].concat(table[row_index])
|
78
|
+
new_table[row_index] = [] if new_table.size <= row_index
|
79
|
+
length_row = original_row.size
|
80
|
+
(0..length_row - 1).each do |cell_index|
|
81
|
+
cell = original_row[cell_index]
|
82
|
+
new_cell_index = cell_index + cells_added
|
83
|
+
new_row = new_table[row_index]
|
84
|
+
until new_row[new_cell_index].nil?
|
85
|
+
cells_added += 1
|
86
|
+
new_cell_index = cell_index + cells_added
|
87
|
+
end
|
88
|
+
new_row[new_cell_index] = cell
|
89
|
+
|
90
|
+
next unless cell.has_attribute?('rowspan')
|
91
|
+
|
92
|
+
rowspan = cell.get_attribute('rowspan').to_i
|
93
|
+
|
94
|
+
next unless rowspan > 1
|
95
|
+
|
96
|
+
(1..rowspan - 1).each do |rowspan_index|
|
97
|
+
new_row_index = row_index + rowspan_index
|
98
|
+
new_table[new_row_index] = [] if new_table[new_row_index].nil?
|
99
|
+
new_table[new_row_index][new_cell_index] = cell
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
new_table
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# Returns a list that represents the line of table with the colspans.
|
109
|
+
#
|
110
|
+
# @param row [Array<Hatemile::Util::Html::HTMLDOMElement>] The list that
|
111
|
+
# represents the line of table without the colspans.
|
112
|
+
# @return [Array<Hatemile::Util::Html::HTMLDOMElement>] The list that
|
113
|
+
# represents the line of table with the colspans.
|
114
|
+
def get_model_row(row)
|
115
|
+
new_row = [].concat(row)
|
116
|
+
size = row.size
|
117
|
+
(0..size - 1).each do |i|
|
118
|
+
cell = row[i]
|
119
|
+
|
120
|
+
next unless cell.has_attribute?('colspan')
|
121
|
+
|
122
|
+
colspan = cell.get_attribute('colspan').to_i
|
123
|
+
|
124
|
+
next unless colspan > 1
|
125
|
+
|
126
|
+
(1..colspan - 1).each do |j|
|
127
|
+
new_row.insert(i + j, cell)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
new_row
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# Validate the list that represents the table header.
|
135
|
+
#
|
136
|
+
# @param header [Array<Array<Hatemile::Util::Html::HTMLDOMElement>>] The
|
137
|
+
# list that represents the table header.
|
138
|
+
# @return [Boolean] True if the table header is valid or false if the
|
139
|
+
# table header is not valid.
|
140
|
+
def valid_header?(header)
|
141
|
+
return false if header.empty?
|
142
|
+
length = -1
|
143
|
+
header.each do |row|
|
144
|
+
return false if row.empty?
|
145
|
+
length = row.size if length == -1
|
146
|
+
return false if row.size != length
|
147
|
+
end
|
148
|
+
true
|
149
|
+
end
|
150
|
+
|
151
|
+
##
|
152
|
+
# Returns a list with ids of rows of same column.
|
153
|
+
#
|
154
|
+
# @param header [Array<Array<Hatemile::Util::Html::HTMLDOMElement>>] The
|
155
|
+
# list that represents the table header.
|
156
|
+
# @param index [Integer] The index of columns.
|
157
|
+
# @return [Array<String>] The list with ids of rows of same column.
|
158
|
+
def get_cells_headers_ids(header, index)
|
159
|
+
ids = []
|
160
|
+
header.each do |row|
|
161
|
+
if row[index].has_attribute?('scope') &&
|
162
|
+
row[index].get_attribute('scope') == 'col'
|
163
|
+
ids.push(row[index].get_attribute('id'))
|
164
|
+
end
|
165
|
+
end
|
166
|
+
ids
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# Associate the data cell with header cell of row.
|
171
|
+
#
|
172
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The table body or
|
173
|
+
# table footer.
|
174
|
+
# @return [void]
|
175
|
+
def associate_data_cells_with_header_cells_of_row(element)
|
176
|
+
table = get_model_table(element)
|
177
|
+
headers_ids = []
|
178
|
+
table.each do |row|
|
179
|
+
headers_ids.clear
|
180
|
+
row.each do |cell|
|
181
|
+
next unless cell.get_tag_name == 'TH' &&
|
182
|
+
Hatemile::Util::CommonFunctions.is_valid_element?(cell)
|
183
|
+
|
184
|
+
@id_generator.generate_id(cell)
|
185
|
+
headers_ids.push(cell.get_attribute('id'))
|
186
|
+
cell.set_attribute('scope', 'row')
|
187
|
+
end
|
188
|
+
|
189
|
+
next if headers_ids.empty?
|
190
|
+
|
191
|
+
row.each do |cell|
|
192
|
+
next unless cell.get_tag_name == 'TD' &&
|
193
|
+
Hatemile::Util::CommonFunctions.is_valid_element?(cell)
|
194
|
+
|
195
|
+
headers = cell.get_attribute('headers')
|
196
|
+
headers_ids.each do |header_id|
|
197
|
+
headers = Hatemile::Util::CommonFunctions.increase_in_list(
|
198
|
+
headers,
|
199
|
+
header_id
|
200
|
+
)
|
201
|
+
end
|
202
|
+
cell.set_attribute('headers', headers)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
##
|
208
|
+
# Set the scope of header cells of table header.
|
209
|
+
#
|
210
|
+
# @param table_header [Hatemile::Util::Html::HTMLDOMElement] The table
|
211
|
+
# header.
|
212
|
+
# @return [void]
|
213
|
+
def prepare_header_cells(table_header)
|
214
|
+
cells = @parser.find(table_header).find_children('tr').find_children(
|
215
|
+
'th'
|
216
|
+
).list_results
|
217
|
+
cells.each do |cell|
|
218
|
+
next unless Hatemile::Util::CommonFunctions.is_valid_element?(cell)
|
219
|
+
|
220
|
+
@id_generator.generate_id(cell)
|
221
|
+
cell.set_attribute('scope', 'col')
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
public
|
226
|
+
|
227
|
+
##
|
228
|
+
# Initializes a new object that improve the accessibility of associations
|
229
|
+
# of parser.
|
230
|
+
#
|
231
|
+
# @param parser [Hatemile::Util::Html::HTMLDOMParser] The HTML parser.
|
232
|
+
def initialize(parser)
|
233
|
+
Hatemile::Helper.require_not_nil(parser)
|
234
|
+
Hatemile::Helper.require_valid_type(
|
235
|
+
parser,
|
236
|
+
Hatemile::Util::Html::HTMLDOMParser
|
237
|
+
)
|
238
|
+
|
239
|
+
@parser = parser
|
240
|
+
@id_generator = Hatemile::Util::IDGenerator.new('association')
|
241
|
+
end
|
242
|
+
|
243
|
+
##
|
244
|
+
# @see Hatemile::AccessibleAssociation#associate_data_cells_with_header_cells
|
245
|
+
def associate_data_cells_with_header_cells(table)
|
246
|
+
header = @parser.find(table).find_children('thead').first_result
|
247
|
+
body = @parser.find(table).find_children('tbody').first_result
|
248
|
+
footer = @parser.find(table).find_children('tfoot').first_result
|
249
|
+
unless header.nil?
|
250
|
+
prepare_header_cells(header)
|
251
|
+
|
252
|
+
header_rows = get_model_table(header)
|
253
|
+
if !body.nil? && valid_header?(header_rows)
|
254
|
+
length_header = header_rows.first.size
|
255
|
+
fake_table = get_model_table(body)
|
256
|
+
unless footer.nil?
|
257
|
+
fake_table = fake_table.concat(get_model_table(footer))
|
258
|
+
end
|
259
|
+
fake_table.each do |row|
|
260
|
+
next unless row.size == length_header
|
261
|
+
|
262
|
+
row.each_with_index do |cell, index|
|
263
|
+
unless Hatemile::Util::CommonFunctions.is_valid_element?(cell)
|
264
|
+
next
|
265
|
+
end
|
266
|
+
|
267
|
+
headers_ids = get_cells_headers_ids(header_rows, index)
|
268
|
+
headers = cell.get_attribute('headers')
|
269
|
+
headers_ids.each do |headers_id|
|
270
|
+
headers = Hatemile::Util::CommonFunctions.increase_in_list(
|
271
|
+
headers,
|
272
|
+
headers_id
|
273
|
+
)
|
274
|
+
end
|
275
|
+
cell.set_attribute('headers', headers)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
associate_data_cells_with_header_cells_of_row(body) unless body.nil?
|
281
|
+
associate_data_cells_with_header_cells_of_row(footer) unless footer.nil?
|
282
|
+
end
|
283
|
+
|
284
|
+
##
|
285
|
+
# @see Hatemile::AccessibleAssociation#associate_all_data_cells_with_header_cells
|
286
|
+
def associate_all_data_cells_with_header_cells
|
287
|
+
tables = @parser.find('table').list_results
|
288
|
+
tables.each do |table|
|
289
|
+
if Hatemile::Util::CommonFunctions.is_valid_element?(table)
|
290
|
+
associate_data_cells_with_header_cells(table)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
##
|
296
|
+
# @see Hatemile::AccessibleAssociation#associate_label_with_field
|
297
|
+
def associate_label_with_field(label)
|
298
|
+
return unless label.get_tag_name == 'LABEL'
|
299
|
+
|
300
|
+
if label.has_attribute?('for')
|
301
|
+
field = @parser.find("##{label.get_attribute('for')}").first_result
|
302
|
+
else
|
303
|
+
field = @parser.find(label).find_descendants(
|
304
|
+
'input,select,textarea'
|
305
|
+
).first_result
|
306
|
+
|
307
|
+
unless field.nil? ||
|
308
|
+
!Hatemile::Util::CommonFunctions.is_valid_element?(field)
|
309
|
+
@id_generator.generate_id(field)
|
310
|
+
label.set_attribute('for', field.get_attribute('id'))
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
return if field.nil?
|
315
|
+
return unless Hatemile::Util::CommonFunctions.is_valid_element?(field)
|
316
|
+
|
317
|
+
unless field.has_attribute?('aria-label')
|
318
|
+
field.set_attribute(
|
319
|
+
'aria-label',
|
320
|
+
label.get_text_content.gsub(/[ \n\r\t]+/, ' ').strip
|
321
|
+
)
|
322
|
+
end
|
323
|
+
|
324
|
+
@id_generator.generate_id(label)
|
325
|
+
field.set_attribute(
|
326
|
+
'aria-labelledby',
|
327
|
+
Hatemile::Util::CommonFunctions.increase_in_list(
|
328
|
+
field.get_attribute('aria-labelledby'),
|
329
|
+
label.get_attribute('id')
|
330
|
+
)
|
331
|
+
)
|
332
|
+
end
|
333
|
+
|
334
|
+
##
|
335
|
+
# @see Hatemile::AccessibleAssociation#associate_all_labels_with_fields
|
336
|
+
def associate_all_labels_with_fields
|
337
|
+
labels = @parser.find('label').list_results
|
338
|
+
labels.each do |label|
|
339
|
+
if Hatemile::Util::CommonFunctions.is_valid_element?(label)
|
340
|
+
associate_label_with_field(label)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
@@ -0,0 +1,772 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
# you may not use this file except in compliance with the License.
|
3
|
+
# You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
|
13
|
+
require File.join(File.dirname(File.dirname(__FILE__)), 'accessible_css')
|
14
|
+
require File.join(File.dirname(File.dirname(__FILE__)), 'helper')
|
15
|
+
require File.join(
|
16
|
+
File.dirname(File.dirname(__FILE__)),
|
17
|
+
'util',
|
18
|
+
'common_functions'
|
19
|
+
)
|
20
|
+
require File.join(File.dirname(File.dirname(__FILE__)), 'util', 'configure')
|
21
|
+
require File.join(File.dirname(File.dirname(__FILE__)), 'util', 'id_generator')
|
22
|
+
require File.join(
|
23
|
+
File.dirname(File.dirname(__FILE__)),
|
24
|
+
'util',
|
25
|
+
'css',
|
26
|
+
'style_sheet_parser'
|
27
|
+
)
|
28
|
+
require File.join(
|
29
|
+
File.dirname(File.dirname(__FILE__)),
|
30
|
+
'util',
|
31
|
+
'html',
|
32
|
+
'html_dom_parser'
|
33
|
+
)
|
34
|
+
require File.join(
|
35
|
+
File.dirname(File.dirname(__FILE__)),
|
36
|
+
'util',
|
37
|
+
'html',
|
38
|
+
'html_dom_text_node'
|
39
|
+
)
|
40
|
+
|
41
|
+
##
|
42
|
+
# The Hatemile module contains the interfaces with the acessibility solutions.
|
43
|
+
module Hatemile
|
44
|
+
##
|
45
|
+
# The Hatemile::Implementation module contains the official implementation of
|
46
|
+
# interfaces solutions.
|
47
|
+
module Implementation
|
48
|
+
##
|
49
|
+
# The AccessibleCSSImplementation class is official implementation of
|
50
|
+
# AccessibleCSS.
|
51
|
+
class AccessibleCSSImplementation < AccessibleCSS
|
52
|
+
public_class_method :new
|
53
|
+
|
54
|
+
##
|
55
|
+
# The name of attribute for identify isolator elements.
|
56
|
+
DATA_ISOLATOR_ELEMENT = 'data-auxiliarspan'.freeze
|
57
|
+
|
58
|
+
##
|
59
|
+
# The name of attribute for identify the element created or modified to
|
60
|
+
# support speak property.
|
61
|
+
DATA_SPEAK = 'data-cssspeak'.freeze
|
62
|
+
|
63
|
+
##
|
64
|
+
# The name of attribute for identify the element created or modified to
|
65
|
+
# support speak-as property.
|
66
|
+
DATA_SPEAK_AS = 'data-cssspeakas'.freeze
|
67
|
+
|
68
|
+
##
|
69
|
+
# The valid element tags for inherit the speak and speak-as properties.
|
70
|
+
VALID_INHERIT_TAGS = %w[
|
71
|
+
SPAN A RT DFN ABBR Q CITE EM TIME VAR SAMP I B SUB SUP SMALL STRONG
|
72
|
+
MARK RUBY INS DEL KBD BDO CODE P FIGCAPTION FIGURE PRE DIV OL UL LI
|
73
|
+
BLOCKQUOTE DL DT DD FIELDSET LEGEND LABEL FORM BODY ASIDE ADDRESS H1 H2
|
74
|
+
H3 H4 H5 H6 SECTION HEADER NAV ARTICLE FOOTER HGROUP CAPTION SUMMARY
|
75
|
+
DETAILS TABLE TR TD TH TBODY THEAD TFOOT
|
76
|
+
].freeze
|
77
|
+
|
78
|
+
##
|
79
|
+
# The valid element tags for speak and speak-as properties.
|
80
|
+
VALID_TAGS = %w[
|
81
|
+
SPAN A RT DFN ABBR Q CITE EM TIME VAR SAMP I B SUB SUP SMALL STRONG MARK
|
82
|
+
RUBY INS DEL KBD BDO CODE P FIGCAPTION FIGURE PRE DIV LI BLOCKQUOTE DT
|
83
|
+
DD FIELDSET LEGEND LABEL FORM BODY ASIDE ADDRESS H1 H2 H3 H4 H5 H6
|
84
|
+
SECTION HEADER NAV ARTICLE FOOTER CAPTION SUMMARY DETAILS TD TH
|
85
|
+
].freeze
|
86
|
+
|
87
|
+
protected
|
88
|
+
|
89
|
+
##
|
90
|
+
# The operation method of _speak_as method for spell-out.
|
91
|
+
#
|
92
|
+
# @param content [String] The text content of element.
|
93
|
+
# @param index [Integer] The index of pattern in text content of element.
|
94
|
+
# @param children [Array<Hatemile::Util::Html::HTMLDOMElement>] The
|
95
|
+
# children of element.
|
96
|
+
# @return [Array<Hatemile::Util::Html::HTMLDOMElement>] The new children
|
97
|
+
# of element.
|
98
|
+
def operation_speak_as_spell_out(content, index, children)
|
99
|
+
children.push(
|
100
|
+
create_content_element(content[0..index], 'spell-out')
|
101
|
+
)
|
102
|
+
|
103
|
+
children.push(create_aural_content_element(' ', 'spell-out'))
|
104
|
+
|
105
|
+
children
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# The operation method of _speak_as method for literal-punctuation.
|
110
|
+
#
|
111
|
+
# @param content [String] The text content of element.
|
112
|
+
# @param index [Integer] The index of pattern in text content of element.
|
113
|
+
# @param children [Array<Hatemile::Util::Html::HTMLDOMElement>] The
|
114
|
+
# children of element.
|
115
|
+
# @return [Array<Hatemile::Util::Html::HTMLDOMElement>] The new children
|
116
|
+
# of element.
|
117
|
+
def operation_speak_as_literal_punctuation(content, index, children)
|
118
|
+
data_property_value = 'literal-punctuation'
|
119
|
+
unless index.zero?
|
120
|
+
children.push(
|
121
|
+
create_content_element(content[0..(index - 1)], data_property_value)
|
122
|
+
)
|
123
|
+
end
|
124
|
+
children.push(
|
125
|
+
create_aural_content_element(
|
126
|
+
" #{get_description_of_symbol(content[index..index])} ",
|
127
|
+
data_property_value
|
128
|
+
)
|
129
|
+
)
|
130
|
+
|
131
|
+
children.push(
|
132
|
+
create_visual_content_element(
|
133
|
+
content[index..index],
|
134
|
+
data_property_value
|
135
|
+
)
|
136
|
+
)
|
137
|
+
|
138
|
+
children
|
139
|
+
end
|
140
|
+
|
141
|
+
##
|
142
|
+
# The operation method of _speak_as method for no-punctuation.
|
143
|
+
#
|
144
|
+
# @param content [String] The text content of element.
|
145
|
+
# @param index [Integer] The index of pattern in text content of element.
|
146
|
+
# @param children [Array<Hatemile::Util::Html::HTMLDOMElement>] The
|
147
|
+
# children of element.
|
148
|
+
# @return [Array<Hatemile::Util::Html::HTMLDOMElement>] The new children
|
149
|
+
# of element.
|
150
|
+
def operation_speak_as_no_punctuation(content, index, children)
|
151
|
+
unless index.zero?
|
152
|
+
children.push(
|
153
|
+
create_content_element(content[0..(index - 1)], 'no-punctuation')
|
154
|
+
)
|
155
|
+
end
|
156
|
+
children.push(
|
157
|
+
create_visual_content_element(
|
158
|
+
content[index..index],
|
159
|
+
'no-punctuation'
|
160
|
+
)
|
161
|
+
)
|
162
|
+
|
163
|
+
children
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# The operation method of _speak_as method for digits.
|
168
|
+
#
|
169
|
+
# @param content [String] The text content of element.
|
170
|
+
# @param index [Integer] The index of pattern in text content of element.
|
171
|
+
# @param children [Array<Hatemile::Util::Html::HTMLDOMElement>] The
|
172
|
+
# children of element.
|
173
|
+
# @return [Array<Hatemile::Util::Html::HTMLDOMElement>] The new children
|
174
|
+
# of element.
|
175
|
+
def operation_speak_as_digits(content, index, children)
|
176
|
+
data_property_value = 'digits'
|
177
|
+
unless index.zero?
|
178
|
+
children.push(
|
179
|
+
create_content_element(content[0..(index - 1)], data_property_value)
|
180
|
+
)
|
181
|
+
end
|
182
|
+
children.push(create_aural_content_element(' ', data_property_value))
|
183
|
+
|
184
|
+
children.push(
|
185
|
+
create_content_element(
|
186
|
+
content[index..index],
|
187
|
+
data_property_value
|
188
|
+
)
|
189
|
+
)
|
190
|
+
|
191
|
+
children
|
192
|
+
end
|
193
|
+
|
194
|
+
##
|
195
|
+
# Load the symbols with configuration.
|
196
|
+
#
|
197
|
+
# @param file_name [String] The file path of symbol configuration.
|
198
|
+
def set_symbols(file_name)
|
199
|
+
@symbols = []
|
200
|
+
if file_name.nil?
|
201
|
+
file_name = File.join(
|
202
|
+
File.dirname(File.dirname(File.dirname(__FILE__))),
|
203
|
+
'symbols.xml'
|
204
|
+
)
|
205
|
+
end
|
206
|
+
document = REXML::Document.new(File.read(file_name))
|
207
|
+
document.elements.each('symbols/symbol') do |symbol_xml|
|
208
|
+
symbol = {}
|
209
|
+
symbol[:symbol] = symbol_xml.attribute('symbol').value
|
210
|
+
symbol[:description] = @configure.get_parameter(
|
211
|
+
symbol_xml.attribute('description').value
|
212
|
+
)
|
213
|
+
@symbols.push(symbol)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
##
|
218
|
+
# Returns the description of symbol.
|
219
|
+
#
|
220
|
+
# @param symbol [String] The symbol.
|
221
|
+
# @return [String] The description of symbol.
|
222
|
+
def get_description_of_symbol(symbol)
|
223
|
+
@symbols.each do |dict_symbol|
|
224
|
+
return dict_symbol[:description] if dict_symbol[:symbol] == symbol
|
225
|
+
end
|
226
|
+
nil
|
227
|
+
end
|
228
|
+
|
229
|
+
##
|
230
|
+
# Returns the regular expression to search all symbols.
|
231
|
+
#
|
232
|
+
# @return [String] The regular expression to search all symbols.
|
233
|
+
def get_regular_expression_of_symbols
|
234
|
+
regular_expression = nil
|
235
|
+
@symbols.each do |symbol|
|
236
|
+
formated_symbol = Regexp.escape(symbol[:symbol])
|
237
|
+
regular_expression = if regular_expression.nil?
|
238
|
+
"(#{formated_symbol})"
|
239
|
+
else
|
240
|
+
"#{regular_expression}|(#{formated_symbol})"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
Regexp.new(regular_expression)
|
244
|
+
end
|
245
|
+
|
246
|
+
##
|
247
|
+
# Check that the children of element can be manipulated to apply the CSS
|
248
|
+
# properties.
|
249
|
+
#
|
250
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
251
|
+
# @return [Boolean] True if the children of element can be manipulated to
|
252
|
+
# apply the CSS properties or false if the children of element cannot be
|
253
|
+
# manipulated to apply the CSS properties.
|
254
|
+
def is_valid_inherit_element?(element)
|
255
|
+
VALID_INHERIT_TAGS.include?(element.get_tag_name) &&
|
256
|
+
Hatemile::Util::CommonFunctions.is_valid_element?(element)
|
257
|
+
end
|
258
|
+
|
259
|
+
##
|
260
|
+
# Check that the element can be manipulated to apply the CSS properties.
|
261
|
+
#
|
262
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
263
|
+
# @return [Boolean] True if the element can be manipulated to apply the
|
264
|
+
# CSS properties or False if the element cannot be manipulated to apply
|
265
|
+
# the CSS properties.
|
266
|
+
def is_valid_element?(element)
|
267
|
+
VALID_TAGS.include?(element.get_tag_name)
|
268
|
+
end
|
269
|
+
|
270
|
+
##
|
271
|
+
# Isolate text nodes of element nodes.
|
272
|
+
#
|
273
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
274
|
+
def isolate_text_node(element)
|
275
|
+
unless element.has_children_elements? && is_valid_element?(element)
|
276
|
+
return
|
277
|
+
end
|
278
|
+
|
279
|
+
element.get_children.each do |child_node|
|
280
|
+
next unless child_node.is_a?(Hatemile::Util::Html::HTMLDOMTextNode)
|
281
|
+
|
282
|
+
span = @html_parser.create_element('span')
|
283
|
+
span.set_attribute(DATA_ISOLATOR_ELEMENT, 'true')
|
284
|
+
span.append_text(child_node.get_text_content)
|
285
|
+
|
286
|
+
child_node.replace_node(span)
|
287
|
+
end
|
288
|
+
element.get_children_elements.each do |child|
|
289
|
+
isolate_text_node(child)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
##
|
294
|
+
# Replace the element by own text content.
|
295
|
+
#
|
296
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
297
|
+
def replace_element_by_own_content(element)
|
298
|
+
if element.has_children_elements?
|
299
|
+
element.get_children_elements.each do |child|
|
300
|
+
element.insert_before(child)
|
301
|
+
end
|
302
|
+
element.remove_node
|
303
|
+
elsif element.has_children?
|
304
|
+
element.replace_node(element.get_first_node_child)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
##
|
309
|
+
# Visit and execute a operation in element and descendants.
|
310
|
+
#
|
311
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
312
|
+
# @param operation [Method] The operation to be executed.
|
313
|
+
def visit(element, operation)
|
314
|
+
return unless is_valid_inherit_element?(element)
|
315
|
+
|
316
|
+
if element.has_children_elements?
|
317
|
+
element.get_children_elements.each do |child|
|
318
|
+
visit(child, operation)
|
319
|
+
end
|
320
|
+
elsif is_valid_element?(element)
|
321
|
+
operation.call(element)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
##
|
326
|
+
# Create a element to show the content.
|
327
|
+
#
|
328
|
+
# @param content [String] The text content of element.
|
329
|
+
# @param data_property_value [String] The value of custom attribute used
|
330
|
+
# to identify the fix.
|
331
|
+
# @return [Hatemile::Util::Html::HTMLDOMElement] The element to show the
|
332
|
+
# content.
|
333
|
+
def create_content_element(content, data_property_value)
|
334
|
+
content_element = @html_parser.create_element('span')
|
335
|
+
content_element.set_attribute(DATA_ISOLATOR_ELEMENT, 'true')
|
336
|
+
content_element.set_attribute(DATA_SPEAK_AS, data_property_value)
|
337
|
+
content_element.append_text(content)
|
338
|
+
content_element
|
339
|
+
end
|
340
|
+
|
341
|
+
##
|
342
|
+
# Create a element to show the content, only to aural displays.
|
343
|
+
#
|
344
|
+
# @param content [String] The text content of element.
|
345
|
+
# @param data_property_value [String] The value of custom attribute used
|
346
|
+
# to identify the fix.
|
347
|
+
# @return [Hatemile::Util::Html::HTMLDOMElement] The element to show the
|
348
|
+
# content.
|
349
|
+
def create_aural_content_element(content, data_property_value)
|
350
|
+
content_element = create_content_element(content, data_property_value)
|
351
|
+
content_element.set_attribute('unselectable', 'on')
|
352
|
+
content_element.set_attribute('class', 'screen-reader-only')
|
353
|
+
content_element
|
354
|
+
end
|
355
|
+
|
356
|
+
##
|
357
|
+
# Create a element to show the content, only to visual displays.
|
358
|
+
#
|
359
|
+
# @param content [String] The text content of element.
|
360
|
+
# @param data_property_value [String] The value of custom attribute used
|
361
|
+
# to identify the fix.
|
362
|
+
# @return [Hatemile::Util::Html::HTMLDOMElement] The element to show the
|
363
|
+
# content.
|
364
|
+
def create_visual_content_element(content, data_property_value)
|
365
|
+
content_element = create_content_element(content, data_property_value)
|
366
|
+
content_element.set_attribute('aria-hidden', 'true')
|
367
|
+
content_element.set_attribute('role', 'presentation')
|
368
|
+
content_element
|
369
|
+
end
|
370
|
+
|
371
|
+
##
|
372
|
+
# Speak the content of element only.
|
373
|
+
#
|
374
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
375
|
+
def speak_normal(element)
|
376
|
+
return unless element.has_attribute?(DATA_SPEAK)
|
377
|
+
|
378
|
+
if element.get_attribute(DATA_SPEAK) == 'none' &&
|
379
|
+
!element.has_attribute?(DATA_ISOLATOR_ELEMENT)
|
380
|
+
element.remove_attribute('role')
|
381
|
+
element.remove_attribute('aria-hidden')
|
382
|
+
element.remove_attribute(DATA_SPEAK)
|
383
|
+
else
|
384
|
+
replace_element_by_own_content(element)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
##
|
389
|
+
# Speak the content of element and descendants.
|
390
|
+
#
|
391
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
392
|
+
def speak_normal_inherit(element)
|
393
|
+
visit(element, method(:speak_normal))
|
394
|
+
|
395
|
+
element.normalize
|
396
|
+
end
|
397
|
+
|
398
|
+
##
|
399
|
+
# No speak any content of element only.
|
400
|
+
#
|
401
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
402
|
+
def speak_none(element)
|
403
|
+
element.set_attribute('role', 'presentation')
|
404
|
+
element.set_attribute('aria-hidden', 'true')
|
405
|
+
element.set_attribute(DATA_SPEAK, 'none')
|
406
|
+
end
|
407
|
+
|
408
|
+
##
|
409
|
+
# No speak any content of element and descendants.
|
410
|
+
#
|
411
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
412
|
+
def speak_none_inherit(element)
|
413
|
+
isolate_text_node(element)
|
414
|
+
|
415
|
+
visit(element, method(:speak_none))
|
416
|
+
end
|
417
|
+
|
418
|
+
##
|
419
|
+
# Execute a operation by regular expression for element only.
|
420
|
+
#
|
421
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
422
|
+
# @param regular_expression [Regexp] The regular expression.
|
423
|
+
# @param data_property_value [String] The value of custom attribute used
|
424
|
+
# to identify the fix.
|
425
|
+
# @param operation [Method] The operation to be executed.
|
426
|
+
def speak_as(element, regular_expression, data_property_value, operation)
|
427
|
+
children = []
|
428
|
+
content = element.get_text_content
|
429
|
+
until content.empty?
|
430
|
+
index = content.index(regular_expression)
|
431
|
+
|
432
|
+
break if index.nil?
|
433
|
+
|
434
|
+
children = operation.call(content, index, children)
|
435
|
+
|
436
|
+
new_index = index + 1
|
437
|
+
content = content[new_index..-1]
|
438
|
+
end
|
439
|
+
|
440
|
+
return if children.empty?
|
441
|
+
|
442
|
+
unless content.empty?
|
443
|
+
children.push(create_content_element(content, data_property_value))
|
444
|
+
end
|
445
|
+
element.get_first_node_child.remove_node while element.has_children?
|
446
|
+
children.each do |child|
|
447
|
+
element.append_element(child)
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
##
|
452
|
+
# Revert changes of a speak_as method for element and descendants.
|
453
|
+
#
|
454
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
455
|
+
# @param data_property_value [String] The value of custom attribute used
|
456
|
+
# to identify the fix.
|
457
|
+
def reverse_speak_as(element, data_property_value)
|
458
|
+
data_property = "[#{DATA_SPEAK_AS}=\"#{data_property_value}\"]"
|
459
|
+
|
460
|
+
auxiliar_elements = @html_parser.find(element).find_descendants(
|
461
|
+
"#{data_property}[unselectable=\"on\"]"
|
462
|
+
).list_results
|
463
|
+
auxiliar_elements.each(&:remove_node)
|
464
|
+
|
465
|
+
content_elements = @html_parser.find(element).find_descendants(
|
466
|
+
"#{data_property}[#{DATA_ISOLATOR_ELEMENT}=\"true\"],
|
467
|
+
#{data_property}[aria-hidden]"
|
468
|
+
).list_results
|
469
|
+
content_elements.each do |content_element|
|
470
|
+
replace_element_by_own_content(content_element)
|
471
|
+
end
|
472
|
+
element.normalize
|
473
|
+
end
|
474
|
+
|
475
|
+
##
|
476
|
+
# Use the default speak configuration of user agent for element and
|
477
|
+
# descendants.
|
478
|
+
#
|
479
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
480
|
+
def speak_as_normal(element)
|
481
|
+
reverse_speak_as(element, 'spell-out')
|
482
|
+
reverse_speak_as(element, 'literal-punctuation')
|
483
|
+
reverse_speak_as(element, 'no-punctuation')
|
484
|
+
reverse_speak_as(element, 'digits')
|
485
|
+
end
|
486
|
+
|
487
|
+
##
|
488
|
+
# Speak one letter at a time for each word for element only.
|
489
|
+
#
|
490
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
491
|
+
def speak_as_spell_out(element)
|
492
|
+
speak_as(
|
493
|
+
element,
|
494
|
+
/[a-zA-Z]/,
|
495
|
+
'spell-out',
|
496
|
+
method(:operation_speak_as_spell_out)
|
497
|
+
)
|
498
|
+
end
|
499
|
+
|
500
|
+
##
|
501
|
+
# Speak one letter at a time for each word for elements and descendants.
|
502
|
+
#
|
503
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
504
|
+
def speak_as_spell_out_inherit(element)
|
505
|
+
reverse_speak_as(element, 'spell-out')
|
506
|
+
|
507
|
+
isolate_text_node(element)
|
508
|
+
|
509
|
+
visit(element, method(:speak_as_spell_out))
|
510
|
+
end
|
511
|
+
|
512
|
+
##
|
513
|
+
# Speak the punctuation for elements only.
|
514
|
+
#
|
515
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
516
|
+
def speak_as_literal_punctuation(element)
|
517
|
+
speak_as(
|
518
|
+
element,
|
519
|
+
get_regular_expression_of_symbols,
|
520
|
+
'literal-punctuation',
|
521
|
+
method(:operation_speak_as_literal_punctuation)
|
522
|
+
)
|
523
|
+
end
|
524
|
+
|
525
|
+
##
|
526
|
+
# Speak the punctuation for elements and descendants.
|
527
|
+
#
|
528
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
529
|
+
def speak_as_literal_punctuation_inherit(element)
|
530
|
+
reverse_speak_as(element, 'literal-punctuation')
|
531
|
+
reverse_speak_as(element, 'no-punctuation')
|
532
|
+
|
533
|
+
isolate_text_node(element)
|
534
|
+
|
535
|
+
visit(element, method(:speak_as_literal_punctuation))
|
536
|
+
end
|
537
|
+
|
538
|
+
##
|
539
|
+
# No speak the punctuation for element only.
|
540
|
+
#
|
541
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
542
|
+
def speak_as_no_punctuation(element)
|
543
|
+
escape_punctuation = Regexp.escape('!"#$%&\'()*+,-./:;<=>?@[]^_`{|}~\\')
|
544
|
+
speak_as(
|
545
|
+
element,
|
546
|
+
Regexp.new("[#{escape_punctuation}]"),
|
547
|
+
'no-punctuation',
|
548
|
+
method(:operation_speak_as_no_punctuation)
|
549
|
+
)
|
550
|
+
end
|
551
|
+
|
552
|
+
##
|
553
|
+
# No speak the punctuation for element and descendants.
|
554
|
+
#
|
555
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
556
|
+
def speak_as_no_punctuation_inherit(element)
|
557
|
+
reverse_speak_as(element, 'literal-punctuation')
|
558
|
+
reverse_speak_as(element, 'no-punctuation')
|
559
|
+
|
560
|
+
isolate_text_node(element)
|
561
|
+
|
562
|
+
visit(element, method(:speak_as_no_punctuation))
|
563
|
+
end
|
564
|
+
|
565
|
+
##
|
566
|
+
# Speak the digit at a time for each number for element only.
|
567
|
+
#
|
568
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
569
|
+
def speak_as_digits(element)
|
570
|
+
speak_as(element, /[0-9]/, 'digits', method(:operation_speak_as_digits))
|
571
|
+
end
|
572
|
+
|
573
|
+
##
|
574
|
+
# Speak the digit at a time for each number for element and descendants.
|
575
|
+
#
|
576
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
577
|
+
def speak_as_digits_inherit(element)
|
578
|
+
reverse_speak_as(element, 'digits')
|
579
|
+
|
580
|
+
isolate_text_node(element)
|
581
|
+
|
582
|
+
visit(element, method(:speak_as_digits))
|
583
|
+
end
|
584
|
+
|
585
|
+
##
|
586
|
+
# Speaks the numbers for element and descendants as a word number.
|
587
|
+
#
|
588
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
589
|
+
def speak_as_continuous_inherit(element)
|
590
|
+
reverse_speak_as(element, 'digits')
|
591
|
+
end
|
592
|
+
|
593
|
+
##
|
594
|
+
# The cells headers will be spoken for every data cell for element and
|
595
|
+
# descendants.
|
596
|
+
#
|
597
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
598
|
+
def speak_header_always_inherit(element)
|
599
|
+
speak_header_once_inherit(element)
|
600
|
+
|
601
|
+
cell_elements = @html_parser.find(element).find_descendants(
|
602
|
+
'td[headers],th[headers]'
|
603
|
+
).list_results
|
604
|
+
accessible_display = AccessibleDisplayImplementation(
|
605
|
+
@html_parser,
|
606
|
+
@configure
|
607
|
+
)
|
608
|
+
cell_elements.each do |cell_element|
|
609
|
+
accessible_display.display_cell_header(cell_element)
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
##
|
614
|
+
# The cells headers will be spoken one time for element and descendants.
|
615
|
+
#
|
616
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
617
|
+
def speak_header_once_inherit(element)
|
618
|
+
header_elements = @html_parser.find(element).find_descendants(
|
619
|
+
"[#{DATA_ATTRIBUTE_HEADERS_OF}]"
|
620
|
+
).list_results
|
621
|
+
header_elements.each(&:remove_node)
|
622
|
+
end
|
623
|
+
|
624
|
+
##
|
625
|
+
# Provide the CSS features of speaking and speech properties in element.
|
626
|
+
#
|
627
|
+
# @param element [Hatemile::Util::Html::HTMLDOMElement] The element.
|
628
|
+
# @param rule [Hatemile::Util::Css::StyleSheetRule] The stylesheet rule.
|
629
|
+
def provide_speak_properties_with_rule(element, rule)
|
630
|
+
if rule.has_property?('speak')
|
631
|
+
declarations = rule.get_declarations('speak')
|
632
|
+
declarations.each do |declaration|
|
633
|
+
property_value = declaration.get_value
|
634
|
+
if property_value == 'none'
|
635
|
+
speak_none_inherit(element)
|
636
|
+
elsif property_value == 'normal'
|
637
|
+
speak_normal_inherit(element)
|
638
|
+
elsif property_value == 'spell-out'
|
639
|
+
speak_as_spell_out_inherit(element)
|
640
|
+
end
|
641
|
+
end
|
642
|
+
end
|
643
|
+
if rule.has_property?('speak-as')
|
644
|
+
declarations = rule.get_declarations('speak-as')
|
645
|
+
declarations.each do |declaration|
|
646
|
+
speak_as_values = declaration.get_values
|
647
|
+
speak_as_normal(element)
|
648
|
+
speak_as_values.each do |speak_as_value|
|
649
|
+
if speak_as_value == 'spell-out'
|
650
|
+
speak_as_spell_out_inherit(element)
|
651
|
+
elsif speak_as_value == 'literal-punctuation'
|
652
|
+
speak_as_literal_punctuation_inherit(element)
|
653
|
+
elsif speak_as_value == 'no-punctuation'
|
654
|
+
speak_as_no_punctuation_inherit(element)
|
655
|
+
elsif speak_as_value == 'digits'
|
656
|
+
speak_as_digits_inherit(element)
|
657
|
+
end
|
658
|
+
end
|
659
|
+
end
|
660
|
+
end
|
661
|
+
if rule.has_property?('speak-punctuation')
|
662
|
+
declarations = rule.get_declarations('speak-punctuation')
|
663
|
+
declarations.each do |declaration|
|
664
|
+
property_value = declaration.get_value
|
665
|
+
if property_value == 'code'
|
666
|
+
speak_as_literal_punctuation_inherit(element)
|
667
|
+
elsif property_value == 'none'
|
668
|
+
speak_as_no_punctuation_inherit(element)
|
669
|
+
end
|
670
|
+
end
|
671
|
+
end
|
672
|
+
if rule.has_property?('speak-numeral')
|
673
|
+
declarations = rule.get_declarations('speak-numeral')
|
674
|
+
declarations.each do |declaration|
|
675
|
+
property_value = declaration.get_value
|
676
|
+
if property_value == 'digits'
|
677
|
+
speak_as_digits_inherit(element)
|
678
|
+
elsif property_value == 'continuous'
|
679
|
+
speak_as_continuous_inherit(element)
|
680
|
+
end
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
684
|
+
return unless rule.has_property?('speak-header')
|
685
|
+
|
686
|
+
declarations = rule.get_declarations('speak-header')
|
687
|
+
declarations.each do |declaration|
|
688
|
+
property_value = declaration.get_value
|
689
|
+
if property_value == 'always'
|
690
|
+
speak_header_always_inherit(element)
|
691
|
+
elsif property_value == 'once'
|
692
|
+
speak_header_once_inherit(element)
|
693
|
+
end
|
694
|
+
end
|
695
|
+
end
|
696
|
+
|
697
|
+
public
|
698
|
+
|
699
|
+
##
|
700
|
+
# Initializes a new object that improve the accessibility of associations
|
701
|
+
# of parser.
|
702
|
+
#
|
703
|
+
# @param html_parser [Hatemile::Util::Html::HTMLDOMParser] The HTML
|
704
|
+
# parser.
|
705
|
+
# @param css_parser [Hatemile::Util::Css::StyleSheetParser] The CSS
|
706
|
+
# parser.
|
707
|
+
# @param configure [Hatemile::Util::Configure] The configuration of
|
708
|
+
# HaTeMiLe.
|
709
|
+
# @param symbol_file_name [String] The file path of symbol configuration.
|
710
|
+
def initialize(html_parser, css_parser, configure, symbol_file_name = nil)
|
711
|
+
Hatemile::Helper.require_not_nil(html_parser, css_parser, configure)
|
712
|
+
Hatemile::Helper.require_valid_type(
|
713
|
+
html_parser,
|
714
|
+
Hatemile::Util::Html::HTMLDOMParser
|
715
|
+
)
|
716
|
+
Hatemile::Helper.require_valid_type(
|
717
|
+
css_parser,
|
718
|
+
Hatemile::Util::Css::StyleSheetParser
|
719
|
+
)
|
720
|
+
Hatemile::Helper.require_valid_type(
|
721
|
+
configure,
|
722
|
+
Hatemile::Util::Configure
|
723
|
+
)
|
724
|
+
Hatemile::Helper.require_valid_type(symbol_file_name, String)
|
725
|
+
|
726
|
+
@html_parser = html_parser
|
727
|
+
@css_parser = css_parser
|
728
|
+
@configure = configure
|
729
|
+
@id_generator = Hatemile::Util::IDGenerator.new('css')
|
730
|
+
set_symbols(symbol_file_name)
|
731
|
+
end
|
732
|
+
|
733
|
+
##
|
734
|
+
# @see Hatemile::AccessibleCSS#provide_speak_properties
|
735
|
+
def provide_speak_properties(element)
|
736
|
+
rules = @css_parser.get_rules(
|
737
|
+
%w[speak speak-punctuation speak-numeral speak-header speak-as]
|
738
|
+
)
|
739
|
+
rules.each do |rule|
|
740
|
+
speak_elements = @html_parser.find(rule.get_selector).list_results
|
741
|
+
if speak_elements.include?(element)
|
742
|
+
provide_speak_properties_with_rule(element, rule)
|
743
|
+
end
|
744
|
+
end
|
745
|
+
end
|
746
|
+
|
747
|
+
##
|
748
|
+
# @see Hatemile::AccessibleCSS#provide_all_speak_properties
|
749
|
+
def provide_all_speak_properties
|
750
|
+
selector = nil
|
751
|
+
rules = @css_parser.get_rules(
|
752
|
+
%w[speak speak-punctuation speak-numeral speak-header speak-as]
|
753
|
+
)
|
754
|
+
rules.each do |rule|
|
755
|
+
selector = if selector.nil?
|
756
|
+
rule.get_selector
|
757
|
+
else
|
758
|
+
"#{selector},#{rule.get_selector}"
|
759
|
+
end
|
760
|
+
end
|
761
|
+
|
762
|
+
return if selector.nil?
|
763
|
+
|
764
|
+
@html_parser.find(selector).list_results.each do |element|
|
765
|
+
if Hatemile::Util::CommonFunctions.is_valid_element?(element)
|
766
|
+
provide_speak_properties(element)
|
767
|
+
end
|
768
|
+
end
|
769
|
+
end
|
770
|
+
end
|
771
|
+
end
|
772
|
+
end
|