meteor 0.9.12 → 0.9.13
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 +4 -4
- data/ChangeLog +5 -1
- data/Gemfile.lock +2 -2
- data/README.md +5 -3
- data/demo/html.rb +41 -38
- data/demo/html4.rb +53 -50
- data/demo/ml/sample_html.html +5 -4
- data/demo/ml/sample_html4.html +4 -4
- data/demo/ml/sample_xhtml.html +6 -5
- data/demo/ml/sample_xhtml4.html +3 -3
- data/demo/xhtml.rb +39 -36
- data/demo/xhtml4.rb +37 -34
- data/demo/xml.rb +63 -63
- data/lib/meteor/attribute.rb +33 -0
- data/lib/meteor/attribute_map.rb +146 -0
- data/lib/meteor/core/kernel.rb +2182 -0
- data/lib/meteor/core/util/pattern_cache.rb +107 -0
- data/lib/meteor/element.rb +532 -0
- data/lib/meteor/element_factory.rb +68 -0
- data/lib/meteor/exception/no_such_element_exception.rb +84 -0
- data/lib/meteor/ml/html/parser_impl.rb +142 -0
- data/lib/meteor/ml/html4/parser_impl.rb +684 -0
- data/lib/meteor/ml/xhtml/parser_impl.rb +139 -0
- data/lib/meteor/ml/xhtml4/parser_impl.rb +398 -0
- data/lib/meteor/ml/xml/parser_impl.rb +160 -0
- data/lib/meteor/parser.rb +15 -0
- data/lib/meteor/parser_factory.rb +493 -0
- data/lib/meteor/root_element.rb +24 -0
- data/lib/meteor.rb +20 -5593
- data/meteor.gemspec +3 -3
- metadata +21 -6
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
# -* coding: UTF-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Meteor
|
|
5
|
+
module Ml
|
|
6
|
+
module Html4
|
|
7
|
+
#
|
|
8
|
+
# HTML4 parser (HTMLパーサ)
|
|
9
|
+
#
|
|
10
|
+
class ParserImpl < Meteor::Core::Kernel
|
|
11
|
+
# KAIGYO_CODE = "\r?\n|\r"
|
|
12
|
+
# KAIGYO_CODE = "\r\n|\n|\r"
|
|
13
|
+
KAIGYO_CODE = ["\r\n", "\n", "\r"]
|
|
14
|
+
BR = '<br>'
|
|
15
|
+
|
|
16
|
+
# @@match_tag = "br|hr|img|input|meta|base"
|
|
17
|
+
@@match_tag = ['br', 'hr', 'img', 'input', 'meta', 'base'] #[Array] void elemets (空要素)
|
|
18
|
+
# @@match_tag_2 = "textarea|option|pre"
|
|
19
|
+
@@match_tag_2 =['textarea', 'option', 'pre'] #[Array] elements where line breaks do not need to be converted to <br> (改行を<br>に変換する必要のない要素)
|
|
20
|
+
|
|
21
|
+
@@match_tag_sng = ['texarea', 'select', 'option', 'form', 'fieldset'] #[Array] non-nestable elements (入れ子にできない要素)
|
|
22
|
+
|
|
23
|
+
@@attr_logic = ['disabled', 'readonly', 'checked', 'selected', 'multiple'] #[Array] boolean elements (論理値で指定する属性)
|
|
24
|
+
|
|
25
|
+
# DISABLE_ELEMENT = "input|textarea|select|optgroup"
|
|
26
|
+
DISABLE_ELEMENT = ['input', 'textarea', 'select', 'optgroup'] #[Array] elements with the disabled attribute (disabled属性のある要素)
|
|
27
|
+
# READONLY_TYPE = "text|password"
|
|
28
|
+
READONLY_TYPE = ['text', 'password'] #[Array] the type of an input element with a readonly attribute (readonly属性のあるinput要素のタイプ)
|
|
29
|
+
|
|
30
|
+
SELECTED_M = '\\sselected\\s|\\sselected$|\\sSELECTED\\s|\\sSELECTED$'
|
|
31
|
+
# SELECTED_M = [' selected ',' selected',' SELECTED ',' SELECTED']
|
|
32
|
+
SELECTED_R = 'selected\\s|selected$|SELECTED\\s|SELECTED$'
|
|
33
|
+
CHECKED_M = '\\schecked\\s|\\schecked$|\\sCHECKED\\s|\\sCHECKED$'
|
|
34
|
+
# CHECKED_M = [' checked ',' checked',' CHECKED ',' CHECKED']
|
|
35
|
+
CHECKED_R = 'checked\\s|checked$|CHECKED\\s|CHECKED$'
|
|
36
|
+
DISABLED_M = '\\sdisabled\\s|\\sdisabled$|\\sDISABLED\\s|\\sDISABLED$'
|
|
37
|
+
# DISABLED_M = [' disabled ',' disiabled',' DISABLED ',' DISABLED']
|
|
38
|
+
DISABLED_R = 'disabled\\s|disabled$|DISABLED\\s|DISABLED$'
|
|
39
|
+
READONLY_M = '\\sreadonly\\s|\\sreadonly$|\\sREADONLY\\s|\\sREADONLY$'
|
|
40
|
+
# READONLY_M = [' readonly ',' readonly',' READONLY ',' READONLY']
|
|
41
|
+
READONLY_R = 'readonly\\s|readonly$|READONLY\\s|READONLY$'
|
|
42
|
+
MULTIPLE_M = '\\smultiple\\s|\\smultiple$|\\sMULTIPLE\\s|\\sMULTIPLE$'
|
|
43
|
+
# MULTIPLE_M = [' multiple ',' multiple',' MULTIPLE ',' MULTIPLE']
|
|
44
|
+
MULTIPLE_R = 'multiple\\s|multiple$|MULTIPLE\\s|MULTIPLE$'
|
|
45
|
+
|
|
46
|
+
# @@pattern_true = Regexp.new("true")
|
|
47
|
+
# @@pattern_false = Regexp.new("false")
|
|
48
|
+
|
|
49
|
+
PATTERN_UNESCAPE = '&(amp|quot|apos|gt|lt|nbsp);'
|
|
50
|
+
GET_ATTRS_MAP2 = '\\s(disabled|readonly|checked|selected|multiple)'
|
|
51
|
+
|
|
52
|
+
@@pattern_selected_m = Regexp.new(SELECTED_M)
|
|
53
|
+
@@pattern_selected_r = Regexp.new(SELECTED_R)
|
|
54
|
+
@@pattern_checked_m = Regexp.new(CHECKED_M)
|
|
55
|
+
@@pattern_checked_r = Regexp.new(CHECKED_R)
|
|
56
|
+
@@pattern_disabled_m = Regexp.new(DISABLED_M)
|
|
57
|
+
@@pattern_disabled_r = Regexp.new(DISABLED_R)
|
|
58
|
+
@@pattern_readonly_m = Regexp.new(READONLY_M)
|
|
59
|
+
@@pattern_readonly_r = Regexp.new(READONLY_R)
|
|
60
|
+
@@pattern_multiple_m = Regexp.new(MULTIPLE_M)
|
|
61
|
+
@@pattern_multiple_r = Regexp.new(MULTIPLE_R)
|
|
62
|
+
|
|
63
|
+
@@pattern_unescape = Regexp.new(PATTERN_UNESCAPE)
|
|
64
|
+
@@pattern_get_attrs_map2 = Regexp.new(GET_ATTRS_MAP2)
|
|
65
|
+
|
|
66
|
+
# @@pattern_match_tag = Regexp.new(@@match_tag)
|
|
67
|
+
# @@pattern_match_tag2 = Regexp.new(@@match_tag_2)
|
|
68
|
+
|
|
69
|
+
TABLE_FOR_ESCAPE_ = {
|
|
70
|
+
'&' => '&',
|
|
71
|
+
'"' => '"',
|
|
72
|
+
'\'' => ''',
|
|
73
|
+
'<' => '<',
|
|
74
|
+
'>' => '>',
|
|
75
|
+
' ' => ' ',
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
TABLE_FOR_ESCAPE_CONTENT_ = {
|
|
79
|
+
'&' => '&',
|
|
80
|
+
'"' => '"',
|
|
81
|
+
'\'' => ''',
|
|
82
|
+
'<' => '<',
|
|
83
|
+
'>' => '>',
|
|
84
|
+
' ' => ' ',
|
|
85
|
+
"\r\n" => '<br>',
|
|
86
|
+
"\r" => '<br>',
|
|
87
|
+
"\n" => '<br>',
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
PATTERN_ESCAPE = "[&\"'<> ]"
|
|
91
|
+
PATTERN_ESCAPE_CONTENT = "[&\"'<> \\n]"
|
|
92
|
+
|
|
93
|
+
@@pattern_escape = Regexp.new(PATTERN_ESCAPE)
|
|
94
|
+
@@pattern_escape_content = Regexp.new(PATTERN_ESCAPE_CONTENT)
|
|
95
|
+
@@pattern_br_2 = Regexp.new(BR)
|
|
96
|
+
|
|
97
|
+
#
|
|
98
|
+
# initializer (イニシャライザ)
|
|
99
|
+
# @overload initialize
|
|
100
|
+
# @overload initialize(ps)
|
|
101
|
+
# @param [Meteor::Parser] ps parser (パーサ)
|
|
102
|
+
#
|
|
103
|
+
def initialize(*args)
|
|
104
|
+
super()
|
|
105
|
+
@doc_type = Parser::HTML4
|
|
106
|
+
case args.length
|
|
107
|
+
when ZERO
|
|
108
|
+
# initialize_0
|
|
109
|
+
when ONE
|
|
110
|
+
initialize_1(args[0])
|
|
111
|
+
else
|
|
112
|
+
raise ArgumentError
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
#
|
|
117
|
+
# initializer (イニシャライザ)
|
|
118
|
+
#
|
|
119
|
+
# def initialize_0
|
|
120
|
+
# end
|
|
121
|
+
#
|
|
122
|
+
# private :initialize_0
|
|
123
|
+
|
|
124
|
+
#
|
|
125
|
+
# initializer (イニシャライザ)
|
|
126
|
+
# @param [Meteor::Parser] ps paser (パーサ)
|
|
127
|
+
#
|
|
128
|
+
def initialize_1(ps)
|
|
129
|
+
@root.document = String.new(ps.document)
|
|
130
|
+
self.document_hook = String.new(ps.document_hook)
|
|
131
|
+
@root.content_type = String.new(ps.root_element.content_type)
|
|
132
|
+
@root.charset = ps.root_element.charset
|
|
133
|
+
@root.kaigyo_code = ps.root_element.kaigyo_code
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
private :initialize_1
|
|
137
|
+
|
|
138
|
+
#
|
|
139
|
+
# parse document (ドキュメントを解析する)
|
|
140
|
+
#
|
|
141
|
+
def parse
|
|
142
|
+
analyze_ml
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
#
|
|
146
|
+
# analyze document (ドキュメントをパースする)
|
|
147
|
+
#
|
|
148
|
+
def analyze_ml
|
|
149
|
+
analyze_content_type
|
|
150
|
+
analyze_kaigyo_code
|
|
151
|
+
|
|
152
|
+
@res = nil
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
private :analyze_ml
|
|
156
|
+
|
|
157
|
+
#
|
|
158
|
+
# get content type (コンテントタイプを取得する)
|
|
159
|
+
# @return [String] conent type (コンテントタイプ)
|
|
160
|
+
#
|
|
161
|
+
def content_type
|
|
162
|
+
@root.content_type
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
#
|
|
166
|
+
# analyze document , set content type (ドキュメントをパースし、コンテントタイプをセットする)
|
|
167
|
+
#
|
|
168
|
+
def analyze_content_type
|
|
169
|
+
@error_check = false
|
|
170
|
+
|
|
171
|
+
element_3('meta', 'http-equiv', 'Content-Type')
|
|
172
|
+
|
|
173
|
+
if !@elm_
|
|
174
|
+
element_3('meta', 'http-equiv', 'Content-Type')
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
@error_check = true
|
|
178
|
+
|
|
179
|
+
if @elm_
|
|
180
|
+
content = @elm_.attr('content')
|
|
181
|
+
content_arr = content&.split(';')
|
|
182
|
+
@root.content_type = content_arr&.at(0) || ''
|
|
183
|
+
@root.charset = content_arr&.at(1)&.split('=')&.at(1) || ''
|
|
184
|
+
else
|
|
185
|
+
@root.content_type = ''
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
protected :analyze_content_type
|
|
190
|
+
|
|
191
|
+
#
|
|
192
|
+
# analuze document , set newline (ドキュメントをパースし、改行コードをセットする)
|
|
193
|
+
#
|
|
194
|
+
def analyze_kaigyo_code
|
|
195
|
+
for a in KAIGYO_CODE
|
|
196
|
+
if @root.document.include?(a)
|
|
197
|
+
@root.kaigyo_code = a
|
|
198
|
+
# puts "kaigyo:" << @root.kaigyo_code
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
protected :analyze_kaigyo_code
|
|
204
|
+
|
|
205
|
+
#
|
|
206
|
+
# get element using tag name (要素のタグ名で検索し、要素を取得する)
|
|
207
|
+
# @param [String] name tag name (タグ名)
|
|
208
|
+
# @return [Meteor::Element] element (要素)
|
|
209
|
+
#
|
|
210
|
+
def element_1(name)
|
|
211
|
+
@_name = Regexp.quote(name)
|
|
212
|
+
|
|
213
|
+
# case of void element (空要素の場合(<->内容あり要素の場合))
|
|
214
|
+
if is_match(@@match_tag, name)
|
|
215
|
+
# void element search pattern (空要素検索用パターン)
|
|
216
|
+
@pattern_cc = String.new('') << "<" << @_name << '(|\\s[^<>]*)>'
|
|
217
|
+
# @pattern_cc = "<#{@_name}(|\\s[^<>]*)>"
|
|
218
|
+
@pattern = Meteor::Core::Util::PatternCache.get(@pattern_cc)
|
|
219
|
+
@res = @pattern.match(@root.document)
|
|
220
|
+
if @res
|
|
221
|
+
element_without_1(name)
|
|
222
|
+
else
|
|
223
|
+
if @error_check
|
|
224
|
+
puts Meteor::Exception::NoSuchElementException.new(name).message
|
|
225
|
+
end
|
|
226
|
+
@elm_ = nil
|
|
227
|
+
end
|
|
228
|
+
else
|
|
229
|
+
# search pattern of element with content (内容あり要素検索用パターン()
|
|
230
|
+
# @pattern_cc = String.new('') << "<" << @_name << '(|\\s[^<>]*)>(((?!(' << @_name
|
|
231
|
+
# @pattern_cc << '[^<>]*>)).)*)<\\/' << @_name << '>'
|
|
232
|
+
@pattern_cc = "<#{@_name}(|\\s[^<>]*)>(((?!(#{tag}[^<>]*>)).)*)<\\/#{@_name}>"
|
|
233
|
+
|
|
234
|
+
@pattern = Meteor::Core::Util::PatternCache.get(@pattern_cc)
|
|
235
|
+
# search of element with content (内容あり要素検索)
|
|
236
|
+
@res = @pattern.match(@root.document)
|
|
237
|
+
# case of element with content (内容あり要素の場合)
|
|
238
|
+
if @res
|
|
239
|
+
element_with_1(name)
|
|
240
|
+
else
|
|
241
|
+
if @error_check
|
|
242
|
+
puts Meteor::Exception::NoSuchElementException.new(name).message
|
|
243
|
+
end
|
|
244
|
+
@elm_ = nil
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
@elm_
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
private :element_1
|
|
252
|
+
|
|
253
|
+
def element_without_1(name)
|
|
254
|
+
@elm_ = Meteor::Element.new(name)
|
|
255
|
+
# attribute (属性)
|
|
256
|
+
@elm_.attributes = @res[1]
|
|
257
|
+
# void element search pattern (空要素検索用パターン)
|
|
258
|
+
@elm_.pattern = @pattern_cc
|
|
259
|
+
|
|
260
|
+
@elm_.document = @res[0]
|
|
261
|
+
|
|
262
|
+
@elm_.parser = self
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
private :element_without_1
|
|
266
|
+
|
|
267
|
+
#
|
|
268
|
+
# get element using tag name and attribute(name="value") (要素のタグ名、属性(属性名="属性値")で検索し、要素を取得する)
|
|
269
|
+
# @param [String] name tag name (タグ名)
|
|
270
|
+
# @param [String] attr_name attribute name (属性名)
|
|
271
|
+
# @param [String] attr_value attribute value (属性値)
|
|
272
|
+
# @param [true,false] quote quote flag (クオート・フラグ)
|
|
273
|
+
# @return [Meteor::Element] element (要素)
|
|
274
|
+
#
|
|
275
|
+
def element_3(name, attr_name, attr_value, quote = true)
|
|
276
|
+
|
|
277
|
+
element_quote_3(name, attr_name, attr_value) if quote
|
|
278
|
+
|
|
279
|
+
# case of void element (空要素の場合(<->内容あり要素の場合))
|
|
280
|
+
if is_match(@@match_tag, name)
|
|
281
|
+
# void element search pattern (空要素検索パターン)
|
|
282
|
+
# @pattern_cc = String.new('') << "<" << @_name << '(\\s[^<>]*' << @_attr_name << '="'
|
|
283
|
+
# @pattern_cc << @_attr_value << '"[^<>]*)>'
|
|
284
|
+
@pattern_cc = "<#{@_name}(\\s[^<>]*#{@_attr_name}=\"#{@_attr_value}\"[^<>]*)>"
|
|
285
|
+
|
|
286
|
+
@pattern = Meteor::Core::Util::PatternCache.get(@pattern_cc)
|
|
287
|
+
# void element search (空要素検索)
|
|
288
|
+
@res = @pattern.match(@root.document)
|
|
289
|
+
if @res
|
|
290
|
+
element_without_3(name)
|
|
291
|
+
else
|
|
292
|
+
if @error_check
|
|
293
|
+
puts Meteor::Exception::NoSuchElementException.new(name, attr_name, attr_value).message
|
|
294
|
+
end
|
|
295
|
+
@elm_ = nil
|
|
296
|
+
end
|
|
297
|
+
else
|
|
298
|
+
# search pattern of element with content (内容あり要素検索パターン)
|
|
299
|
+
# @pattern_cc = String.new('') << "<" << @_name << '(\\s[^<>]*' << @_attr_name << '="'
|
|
300
|
+
# @pattern_cc << @_attr_value << '"[^<>]*)>(((?!(' << @_name
|
|
301
|
+
# @pattern_cc << '[^<>]*>)).)*)<\\/' << @_name << '>'
|
|
302
|
+
@pattern_cc = "<#{@_name}(\\s[^<>]*#{@_attr_name}=\"#{@_attr_value}\"[^<>]*)>(((?!(#{@_name}[^<>]*>)).)*)<\\/#{@_name}>"
|
|
303
|
+
|
|
304
|
+
@pattern = Meteor::Core::Util::PatternCache.get(@pattern_cc)
|
|
305
|
+
# search of element with content (内容あり要素検索)
|
|
306
|
+
@res = @pattern.match(@root.document)
|
|
307
|
+
|
|
308
|
+
if !@res && !is_match(@@match_tag_sng, name)
|
|
309
|
+
@res = element_with_3_2
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
if @res
|
|
313
|
+
element_with_3_1(name)
|
|
314
|
+
else
|
|
315
|
+
if @error_check
|
|
316
|
+
puts Meteor::Exception::NoSuchElementException.new(name, attr_name, attr_value).message
|
|
317
|
+
end
|
|
318
|
+
@elm_ = nil
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
@elm_
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
private :element_3
|
|
326
|
+
|
|
327
|
+
def element_without_3(name)
|
|
328
|
+
element_without_3_1(name, '"[^<>]*)>')
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
private :element_without_3
|
|
332
|
+
|
|
333
|
+
#
|
|
334
|
+
# get element using attribute(name="value") (属性(属性名="属性値")で検索し、要素を取得する)
|
|
335
|
+
# @param [String] attr_name attribute name (属性名)
|
|
336
|
+
# @param [String] attr_value attribute value (属性値)
|
|
337
|
+
# @return [Meteor::Element] element (要素)
|
|
338
|
+
#
|
|
339
|
+
def element_2(attr_name, attr_value)
|
|
340
|
+
|
|
341
|
+
element_quote_2(attr_name, attr_value)
|
|
342
|
+
|
|
343
|
+
# @pattern_cc = String.new('') << '<([^<>"]*)\\s[^<>]*' << @_attr_name << '="' << @_attr_value
|
|
344
|
+
# @pattern_cc << '"[^<>]*>'
|
|
345
|
+
@pattern_cc = "<([^<>\"]*)\\s[^<>]*#{@_attr_name}=\"#{@_attr_value}\"[^<>]*>"
|
|
346
|
+
|
|
347
|
+
@pattern = Meteor::Core::Util::PatternCache.get(@pattern_cc)
|
|
348
|
+
@res = @pattern.match(@root.document)
|
|
349
|
+
|
|
350
|
+
if @res
|
|
351
|
+
element_3(@res[1], attr_name, attr_value)
|
|
352
|
+
else
|
|
353
|
+
if @error_check
|
|
354
|
+
puts Meteor::Exception::NoSuchElementException.new(attr_name, attr_value).message
|
|
355
|
+
end
|
|
356
|
+
@elm_ = nil
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
@elm_
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
private :element_2
|
|
363
|
+
|
|
364
|
+
#
|
|
365
|
+
# get element using tag name and attribute1,2(name="value") (要素のタグ名と属性1・属性2(属性名="属性値")で検索し、要素を取得する)
|
|
366
|
+
# @param [String] name tag name (タグ名)
|
|
367
|
+
# @param [String] attr_name1 attribute name1 (属性名1)
|
|
368
|
+
# @param [String] attr_value1 attribute value1 (属性値1)
|
|
369
|
+
# @param [String] attr_name2 attribute name2 (属性名2)
|
|
370
|
+
# @param [String] attr_value2 attribute value2 (属性値2)
|
|
371
|
+
# @return [Meteor::Element] element (要素)
|
|
372
|
+
#
|
|
373
|
+
def element_5(name, attr_name1, attr_value1, attr_name2, attr_value2)
|
|
374
|
+
|
|
375
|
+
element_quote_5(name, attr_name1, attr_value1, attr_name2, attr_value2)
|
|
376
|
+
|
|
377
|
+
# 空要素の場合(<->内容あり要素の場合)
|
|
378
|
+
if is_match(@@match_tag, name)
|
|
379
|
+
# void element search pattern (空要素検索パターン)
|
|
380
|
+
# @pattern_cc = String.new('') << "<" << @_name << '(\\s[^<>]*(?:' << @_attr_name1 << '="'
|
|
381
|
+
# @pattern_cc << @_attr_value1 << '"[^<>]*' << @_attr_name2 << '="'
|
|
382
|
+
# @pattern_cc << @_attr_value2 << '"|' << @_attr_name2 << '="'
|
|
383
|
+
# @pattern_cc << @_attr_value2 << '"[^<>]*' << @_attr_name1 << '="'
|
|
384
|
+
# @pattern_cc << @_attr_value1 << '")[^<>]*)>'
|
|
385
|
+
@pattern_cc = "<#{@_name}(\\s[^<>]*(?:#{@_attr_name1}=\"#{@_attr_value1}\"[^<>]*#{@_attr_name2}=\"#{@_attr_value2}\"|#{@_attr_name2}=\"#{@_attr_value2}\"[^<>]*#{@_attr_name1}=\"#{@_attr_value1}\")[^<>]*)>"
|
|
386
|
+
|
|
387
|
+
@pattern = Meteor::Core::Util::PatternCache.get(@pattern_cc)
|
|
388
|
+
# void element search (空要素検索)
|
|
389
|
+
@res = @pattern.match(@root.document)
|
|
390
|
+
|
|
391
|
+
if @res
|
|
392
|
+
element_without_5(name)
|
|
393
|
+
else
|
|
394
|
+
if @error_check
|
|
395
|
+
puts Meteor::Exception::NoSuchElementException.new(name, attr_name1, attr_value1, attr_name2, attr_value2).message
|
|
396
|
+
end
|
|
397
|
+
@elm_ = nil
|
|
398
|
+
end
|
|
399
|
+
else
|
|
400
|
+
# search pattern of element with content (内容あり要素検索パターン)
|
|
401
|
+
# @pattern_cc = String.new('') << "<" << @_name << '(\\s[^<>]*(?:' << @_attr_name1 << '="'
|
|
402
|
+
# @pattern_cc << @_attr_value1 << '"[^<>]*' << @_attr_name2 << '="'
|
|
403
|
+
# @pattern_cc << @_attr_value2 << '"|' << @_attr_name2 << '="'
|
|
404
|
+
# @pattern_cc << @_attr_value2 << '"[^<>]*' << @_attr_name1 << '="'
|
|
405
|
+
# @pattern_cc << @_attr_value1 << '")[^<>]*)>(((?!(' << @_name
|
|
406
|
+
# @pattern_cc << '[^<>]*>)).)*)<\\/' << @_name << '>'
|
|
407
|
+
@pattern_cc = "<#{@_name}(\\s[^<>]*(?:#{@_attr_name1}=\"#{@_attr_value1}\"[^<>]*#{@_attr_name2}=\"#{@_attr_value2}\"|#{@_attr_name2}=\"#{@_attr_value2}\"[^<>]*#{@_attr_name1}=\"#{@_attr_value1}\")[^<>]*)>(((?!(#{@_name}[^<>]*>)).)*)<\\/#{@_name}>"
|
|
408
|
+
|
|
409
|
+
@pattern = Meteor::Core::Util::PatternCache.get(@pattern_cc)
|
|
410
|
+
# search of element with content (内容あり要素検索)
|
|
411
|
+
@res = @pattern.match(@root.document)
|
|
412
|
+
|
|
413
|
+
if !@res && !is_match(@@match_tag_sng, tag)
|
|
414
|
+
@res = element_with_5_2
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
if @res
|
|
418
|
+
element_with_5_1(name)
|
|
419
|
+
else
|
|
420
|
+
if @error_check
|
|
421
|
+
puts Meteor::Exception::NoSuchElementException.new(name, attr_name1, attr_value1, attr_name2, attr_value2).message
|
|
422
|
+
end
|
|
423
|
+
@elm_ = nil
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
@elm_
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
private :element_5
|
|
431
|
+
|
|
432
|
+
def element_without_5(name)
|
|
433
|
+
element_without_5_1(name, '")[^<>]*)>')
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
private :element_without_5
|
|
437
|
+
|
|
438
|
+
#
|
|
439
|
+
# get element using attribute1,2(name="value") (属性1・属性2(属性名="属性値")で検索し、要素を取得する)
|
|
440
|
+
#
|
|
441
|
+
# @param [String] attr_name1 attribute name1 (属性名1)
|
|
442
|
+
# @param [String] attr_value1 attribute value1 (属性値1)
|
|
443
|
+
# @param [String] attr_name2 attribute name2 (属性名2)
|
|
444
|
+
# @param [String] attr_value2 attribute value2 (属性値2)
|
|
445
|
+
# @return [Meteor::Element] element (要素)
|
|
446
|
+
#
|
|
447
|
+
def element_4(attr_name1, attr_value1, attr_name2, attr_value2)
|
|
448
|
+
element_quote_4(attr_name1, attr_value1, attr_name2, attr_value2)
|
|
449
|
+
|
|
450
|
+
# @pattern_cc = String.new('') << '<([^<>"]*)\\s([^<>]*(' << @_attr_name1 << '="' << @_attr_value1
|
|
451
|
+
# @pattern_cc << '"[^<>]*' << @_attr_name2 << '="' << @_attr_value2
|
|
452
|
+
# @pattern_cc << '"|' << @_attr_name2 << '="' << @_attr_value2
|
|
453
|
+
# @pattern_cc << '"[^<>]*' << @_attr_name1 << '="' << @_attr_value1
|
|
454
|
+
# @pattern_cc << '")[^<>]*)>'
|
|
455
|
+
@pattern_cc = "<([^<>\"]*)\\s([^<>]*(#{@_attr_name1}=\"#{@_attr_value1}\"[^<>]*#{@_attr_name2}=\"#{@_attr_value2}\"|#{@_attr_name2}=\"#{@_attr_value2}\"[^<>]*#{@_attr_name1}=\"#{@_attr_value1}\")[^<>]*)>"
|
|
456
|
+
|
|
457
|
+
@pattern = Meteor::Core::Util::PatternCache.get(@pattern_cc)
|
|
458
|
+
|
|
459
|
+
@res = @pattern.match(@root.document)
|
|
460
|
+
|
|
461
|
+
if @res
|
|
462
|
+
element_5(@res[1], attr_name1, attr_value1, attr_name2, attr_value2)
|
|
463
|
+
else
|
|
464
|
+
if @error_check
|
|
465
|
+
puts Meteor::Exception::NoSuchElementException.new(attr_name1, attr_value1, attr_name2, attr_value2).message
|
|
466
|
+
end
|
|
467
|
+
@elm_ = nil
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
@elm_
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
private :element_4
|
|
474
|
+
|
|
475
|
+
def edit_attrs_(elm, attr_name, attr_value)
|
|
476
|
+
if is_match('selected', attr_name) && is_match('option', elm.name)
|
|
477
|
+
edit_attrs_5(elm, attr_name, attr_value, @@pattern_selected_m, @@pattern_selected_r)
|
|
478
|
+
elsif is_match('multiple', attr_name) && is_match('select', elm.name)
|
|
479
|
+
edit_attrs_5(elm, attr_name, attr_value, @@pattern_multiple_m, @@pattern_multiple_r)
|
|
480
|
+
elsif is_match('disabled', attr_name) && is_match(DISABLE_ELEMENT, elm.name)
|
|
481
|
+
edit_attrs_5(elm, attr_name, attr_value, @@pattern_disabled_m, @@pattern_disabled_r)
|
|
482
|
+
elsif is_match('checked', attr_name) && is_match('input', elm.name) && is_match('radio', get_type(elm))
|
|
483
|
+
edit_attrs_5(elm, attr_name, attr_value, @@pattern_checked_m, @@pattern_checked_r)
|
|
484
|
+
elsif is_match('readonly', attr_name) && (is_match('textarea', elm.name) || (is_match('input', elm.name) && is_match(READONLY_TYPE, get_type(elm))))
|
|
485
|
+
edit_attrs_5(elm, attr_name, attr_value, @@pattern_readonly_m, @@pattern_readonly_r)
|
|
486
|
+
else
|
|
487
|
+
super(elm, attr_name, attr_value)
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
private :edit_attrs_
|
|
492
|
+
|
|
493
|
+
def edit_attrs_5(elm, attr_name, attr_value, match_p, replace)
|
|
494
|
+
if true.equal?(attr_value) || is_match("true", attr_value)
|
|
495
|
+
@res = match_p.match(elm.attributes)
|
|
496
|
+
|
|
497
|
+
if !@res
|
|
498
|
+
if !''.eql?(elm.attributes) && !''.eql?(elm.attributes.strip)
|
|
499
|
+
elm.attributes = String.new('') << ' ' << elm.attributes.strip
|
|
500
|
+
else
|
|
501
|
+
elm.attributes = String.new('')
|
|
502
|
+
end
|
|
503
|
+
elm.attributes << ' ' << attr_name
|
|
504
|
+
# else
|
|
505
|
+
end
|
|
506
|
+
elsif false.equal?(attr_value) || is_match("false", attr_value)
|
|
507
|
+
elm.attributes.sub!(replace, '')
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
private :edit_attrs_5
|
|
512
|
+
|
|
513
|
+
def edit_document_1(elm)
|
|
514
|
+
edit_document_2(elm, '>')
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
private :edit_document_1
|
|
518
|
+
|
|
519
|
+
def get_attr_value_(elm, attr_name)
|
|
520
|
+
if is_match('selected', attr_name) && is_match('option', elm.name)
|
|
521
|
+
get_attr_value_r(elm, @@pattern_selected_m)
|
|
522
|
+
elsif is_match('multiple', attr_name) && is_match('select', elm.name)
|
|
523
|
+
get_attr_value_r(elm, @@pattern_multiple_m)
|
|
524
|
+
elsif is_match('disabled', attr_name) && is_match(DISABLE_ELEMENT, elm.name)
|
|
525
|
+
get_attr_value_r(elm, @@pattern_disabled_m)
|
|
526
|
+
elsif is_match('checked', attr_name) && is_match('input', elm.name) && is_match('radio', get_type(elm))
|
|
527
|
+
get_attr_value_r(elm, @@pattern_checked_m)
|
|
528
|
+
elsif is_match('readonly', attr_name) && (is_match('textarea', elm.name) || (is_match('input', elm.name) && is_match(READONLY_TYPE, get_type(elm))))
|
|
529
|
+
get_attr_value_r(elm, @@pattern_readonly_m)
|
|
530
|
+
else
|
|
531
|
+
super(elm, attr_name)
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
private :get_attr_value_
|
|
536
|
+
|
|
537
|
+
def get_type(elm)
|
|
538
|
+
if !elm.type_value
|
|
539
|
+
elm.type_value = get_attr_value_(elm, 'type')
|
|
540
|
+
if !elm.type_value
|
|
541
|
+
elm.type_value = get_attr_value_(elm, "TYPE")
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
elm.type_value
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
private :get_type
|
|
548
|
+
|
|
549
|
+
def get_attr_value_r(elm, match_p)
|
|
550
|
+
|
|
551
|
+
@res = match_p.match(elm.attributes)
|
|
552
|
+
|
|
553
|
+
if @res
|
|
554
|
+
"true"
|
|
555
|
+
else
|
|
556
|
+
"false"
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
private :get_attr_value_r
|
|
561
|
+
|
|
562
|
+
#
|
|
563
|
+
# get attribute map (属性マップを取得する)
|
|
564
|
+
# @param [Meteor::Element] elm element (要素)
|
|
565
|
+
# @return [Hash] attribute map (属性マップ)
|
|
566
|
+
#
|
|
567
|
+
def get_attrs(elm)
|
|
568
|
+
attrs = Hash.new
|
|
569
|
+
|
|
570
|
+
elm.attributes.scan(@@pattern_get_attrs_map) do |a, b|
|
|
571
|
+
attrs.store(a, unescape(b))
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
elm.attributes.scan(@@pattern_get_attrs_map2) do |a|
|
|
575
|
+
attrs.store(a[0], "true")
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
attrs
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
private :get_attrs
|
|
582
|
+
|
|
583
|
+
#
|
|
584
|
+
# get attribute map of element (要素の属性マップを取得する)
|
|
585
|
+
# @param [Meteor::Element] elm element (要素)
|
|
586
|
+
# @return [Meteor::AttributeMap] attribute map (属性マップ)
|
|
587
|
+
#
|
|
588
|
+
def get_attr_map(elm)
|
|
589
|
+
attrs = Meteor::AttributeMap.new
|
|
590
|
+
|
|
591
|
+
elm.attributes.scan(@@pattern_get_attrs_map) do |a, b|
|
|
592
|
+
attrs.store(a, unescape(b))
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
elm.attributes.scan(@@pattern_get_attrs_map2) do |a|
|
|
596
|
+
attrs.store(a[0], "true")
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
attrs.recordable = true
|
|
600
|
+
|
|
601
|
+
attrs
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
private :get_attr_map
|
|
605
|
+
|
|
606
|
+
def remove_attrs_(elm, attr_name)
|
|
607
|
+
if !is_match(@@attr_logic, attr_name)
|
|
608
|
+
# attribute search pattern (属性検索用パターン)
|
|
609
|
+
@pattern = Meteor::Core::Util::PatternCache.get(String.new('') << attr_name << '="[^"]*"\\s?')
|
|
610
|
+
# @pattern = Meteor::Core::Util::PatternCache.get("#{attr_name}=\"[^\"]*\"\\s?")
|
|
611
|
+
elm.attributes.sub!(@pattern, '')
|
|
612
|
+
else
|
|
613
|
+
# attribute search pattern (属性検索用パターン)
|
|
614
|
+
@pattern = Meteor::Core::Util::PatternCache.get(attr_name)
|
|
615
|
+
elm.attributes.sub!(@pattern, '')
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
private :remove_attrs_
|
|
620
|
+
|
|
621
|
+
def escape(content)
|
|
622
|
+
# 特殊文字の置換
|
|
623
|
+
content = content.gsub(@@pattern_escape, TABLE_FOR_ESCAPE_)
|
|
624
|
+
|
|
625
|
+
content
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def escape_content(content, elm)
|
|
629
|
+
# 特殊文字の置換
|
|
630
|
+
content = content.gsub(@@pattern_escape_content, TABLE_FOR_ESCAPE_CONTENT_)
|
|
631
|
+
|
|
632
|
+
content
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
private :escape
|
|
636
|
+
private :escape_content
|
|
637
|
+
|
|
638
|
+
def unescape(content)
|
|
639
|
+
# replace special character (特殊文字の置換)
|
|
640
|
+
# 「<」<-「<」
|
|
641
|
+
# 「>」<-「>」
|
|
642
|
+
# 「"」<-「"l」
|
|
643
|
+
# 「 」<-「 」
|
|
644
|
+
# 「&」<-「&」
|
|
645
|
+
content.gsub(@@pattern_unescape) do
|
|
646
|
+
case $1
|
|
647
|
+
when 'amp'
|
|
648
|
+
'&'
|
|
649
|
+
when 'quot'
|
|
650
|
+
'"'
|
|
651
|
+
when 'apos'
|
|
652
|
+
"'"
|
|
653
|
+
when 'gt'
|
|
654
|
+
'>'
|
|
655
|
+
when 'lt'
|
|
656
|
+
'<'
|
|
657
|
+
when 'nbsp'
|
|
658
|
+
' '
|
|
659
|
+
end
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
content
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
private :unescape
|
|
666
|
+
|
|
667
|
+
def unescape_content(content, elm)
|
|
668
|
+
content_ = unescape(content)
|
|
669
|
+
|
|
670
|
+
if elm.cx || !is_match(@@match_tag_2, elm.name)
|
|
671
|
+
if content.include?(BR)
|
|
672
|
+
# 「<br>」->「¥r?¥n」
|
|
673
|
+
content_.gsub!(@@pattern_br_2, @root.kaigyo_code)
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
content_
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
private :unescape_content
|
|
681
|
+
end
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
end
|