syobocal 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ module Syobocal
2
+ module Comment
3
+ module Element
4
+ class Text
5
+ attr_reader :str
6
+
7
+ def initialize(str)
8
+ @str = str
9
+ end
10
+
11
+ def ==(other)
12
+ other.instance_of?(self.class) && other.str == str
13
+ end
14
+
15
+ def self.create(text)
16
+ new(text)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,55 @@
1
+ module Syobocal
2
+ module Comment
3
+ module Element
4
+ class TextNode
5
+ attr_reader :text_elements
6
+
7
+ SUBJECT_SEPARATOR = "、"
8
+ ENCODED_SEPARATOR = "\t"
9
+
10
+ def initialize(text_elements)
11
+ @text_elements = text_elements
12
+ end
13
+
14
+ def inner_text
15
+ text_elements.map(&:str).join("")
16
+ end
17
+
18
+ def ==(other)
19
+ other.instance_of?(self.class) && other.text_elements == text_elements
20
+ end
21
+
22
+ def split
23
+ buffer = ""
24
+
25
+ text_elements.each do |node|
26
+ case node
27
+ when Element::Text
28
+ buffer << node.str.gsub(SUBJECT_SEPARATOR, ENCODED_SEPARATOR)
29
+ when Element::Link
30
+ buffer << node.str
31
+ end
32
+ end
33
+
34
+ buffer.scan(/[^#{ENCODED_SEPARATOR}\(]+(?:\(.*?\))?/).map { |str| str.gsub(ENCODED_SEPARATOR, SUBJECT_SEPARATOR).strip }
35
+ end
36
+
37
+ def self.match?(line)
38
+ true
39
+ end
40
+
41
+ def self.parse(text)
42
+ elements = text.split(/(\[\[[^\[\]]*?\]\])/).select { |s| !s.empty? }.map do |s|
43
+ if s.match(/\A\[\[[^\[\]]*?\]\]\Z/)
44
+ Link.create(s)
45
+ else
46
+ Text.create(s)
47
+ end
48
+ end
49
+
50
+ Element::TextNode.new(elements)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,15 @@
1
+ module Syobocal
2
+ module Comment
3
+ class Music
4
+ attr_reader :title, :category, :data_list
5
+
6
+ def initialize(title, category, data_list)
7
+ @title, @category, @data_list = title, category, data_list
8
+ end
9
+
10
+ def ==(other)
11
+ other.instance_of?(self.class) && other.title == title && other.category == category && other.data_list == data_list
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Syobocal
2
+ module Comment
3
+ class MusicData
4
+ attr_reader :attr, :value
5
+
6
+ def initialize(attr, value)
7
+ @attr, @value = attr, value
8
+ end
9
+
10
+ def ==(other)
11
+ other.instance_of?(self.class) && other.attr == attr && other.value == value
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,103 @@
1
+ module Syobocal
2
+ module Comment
3
+ class Parser
4
+ ELEMENT_CLASSES = [
5
+ Element::Header2,
6
+ Element::Header1,
7
+ Element::List,
8
+ Element::Row,
9
+ Element::Blank,
10
+ Element::TextNode, # NOTE Sentinel
11
+ ]
12
+
13
+ def initialize(comment)
14
+ @comment = comment
15
+ end
16
+
17
+ def parse
18
+ return @parse if defined? @parse
19
+
20
+ return @parse = Element::Root.new([]) unless @comment
21
+
22
+ elements = @comment.each_line.map do |line|
23
+ line.chomp!
24
+ ELEMENT_CLASSES.find { |clazz| clazz.match?(line) }.parse(line)
25
+ end
26
+
27
+ @parse = Element::Root.new(elements)
28
+ end
29
+
30
+ def staffs
31
+ return @staffs if defined? @staffs
32
+
33
+ rows = sections.find { |section| section.staff_section? }&.rows || []
34
+
35
+ @staffs = create_staff_list(rows)
36
+ end
37
+
38
+ def casts
39
+ return @casts if defined? @casts
40
+
41
+ rows = sections.find { |section| section.cast_section? }&.rows || []
42
+
43
+ @casts = create_cast_list(rows)
44
+ end
45
+
46
+ def musics
47
+ return @musics if defined? @musics
48
+
49
+ @musics = create_musics(all_sections)
50
+ end
51
+
52
+ def links
53
+ return @links if defined? @links
54
+
55
+ @links = sections.find { |section| section.link_section? }&.links || []
56
+ end
57
+
58
+ def sections
59
+ return @sections if defined? @sections
60
+
61
+ @sections = Section.create_sections(parse.elements)
62
+ end
63
+
64
+ def all_sections
65
+ return enum_for(:all_sections) unless block_given?
66
+
67
+ sections.each do |section|
68
+ yield section
69
+
70
+ section.sub_sections.each do |sub_section|
71
+ yield sub_section
72
+ end
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def create_staff_list(rows)
79
+ rows.map do |row|
80
+ role = row.attr_node.inner_text
81
+ people = row.value_node.split.map { |str| Person.parse(str) }
82
+
83
+ Staff.new(role, people)
84
+ end
85
+ end
86
+
87
+ def create_cast_list(rows)
88
+ rows.map do |row|
89
+ character = row.attr_node.inner_text
90
+ people = row.value_node.split.map { |str| Person.parse(str) }
91
+
92
+ Cast.new(character, people)
93
+ end
94
+ end
95
+
96
+ def create_musics(sections)
97
+ sections.each_with_object([]) do |section, musics|
98
+ musics << section.to_music if section.music_section?
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,24 @@
1
+ module Syobocal
2
+ module Comment
3
+ class Person
4
+ PERSON_SEPARATOR = "、"
5
+ ENCODED_SEPARATOR = "\t"
6
+
7
+ attr_reader :name, :note
8
+
9
+ def initialize(name, note)
10
+ @name, @note = name, note
11
+ end
12
+
13
+ def ==(other)
14
+ other.instance_of?(self.class) && other.name == name && other.note == note
15
+ end
16
+
17
+ def self.parse(str)
18
+ _, name, note = *(str.match(/\A([^\(\)]+?)(?:\((.*?)\))?\Z/).to_a)
19
+
20
+ Person.new(name, note)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,123 @@
1
+ module Syobocal
2
+ module Comment
3
+ class Section
4
+ attr_reader :header, :elements, :sub_sections
5
+
6
+ class RestrictedOperation < StandardError; end
7
+
8
+ def initialize(header, elements)
9
+ @header, @elements = header, elements
10
+ @sub_sections = []
11
+ end
12
+
13
+ def sub_section?
14
+ @header.instance_of? Element::Header2
15
+ end
16
+
17
+ def add_subsection(sub_section)
18
+ raise RestrictedOperation if sub_section?
19
+ raise RestrictedOperation unless sub_section.sub_section?
20
+
21
+ @sub_sections << sub_section
22
+ end
23
+
24
+ STAFF_WORD = "スタッフ"
25
+ CAST_WORD = "キャスト"
26
+ LINK_WORD = "リンク"
27
+ MUSIC_WORDS = %w(テーマ ソング 歌 曲)
28
+ MUSIC_TITLE_REGEXP = /\A(.*)「(.+?)」\Z/
29
+
30
+ def staff_section?
31
+ header.text_node.inner_text.include?(STAFF_WORD)
32
+ end
33
+
34
+ def cast_section?
35
+ header.text_node.inner_text.include?(CAST_WORD)
36
+ end
37
+
38
+ def link_section?
39
+ header.text_node.inner_text.include?(LINK_WORD)
40
+ end
41
+
42
+ def music_section?
43
+ str = header.text_node.inner_text
44
+
45
+ MUSIC_WORDS.any? { |keyword| str.include?(keyword) } && str.match(MUSIC_TITLE_REGEXP)
46
+ end
47
+
48
+ def to_music
49
+ m = header.text_node.inner_text.match(MUSIC_TITLE_REGEXP)
50
+ title = m[2]
51
+ category = m[1]
52
+
53
+ data_list = rows.map do |row|
54
+ attr = row.attr_node.inner_text
55
+ value = row.value_node.inner_text
56
+ MusicData.new(attr, value)
57
+ end
58
+
59
+ Music.new(title, category, data_list)
60
+ end
61
+
62
+ def rows
63
+ elements.select { |element| element.is_a? Element::Row }
64
+ end
65
+
66
+ def links
67
+ elements.each_with_object([]) { |element, links|
68
+ case element
69
+ when Element::List
70
+ links << element.text_node.text_elements.select { |elm| elm.instance_of? Element::Link }
71
+ when Element::TextNode
72
+ links << element.text_elements.select { |elm| elm.instance_of? Element::Link }
73
+ end
74
+ }.flatten
75
+ end
76
+
77
+ def self.create_sections(elements)
78
+ sections = []
79
+ current_header = nil
80
+ buffered_elements = []
81
+
82
+ elements.each do |element|
83
+ case element
84
+ when Element::Header1, Element::Header2
85
+ if current_header
86
+ sections << Section.new(current_header, buffered_elements)
87
+ buffered_elements = []
88
+ end
89
+
90
+ current_header = element
91
+ else
92
+ buffered_elements << element if current_header
93
+ end
94
+ end
95
+
96
+ if current_header
97
+ sections << Section.new(current_header, buffered_elements)
98
+ end
99
+
100
+ structure_sections(sections)
101
+ end
102
+
103
+ def self.structure_sections(sections)
104
+ root_sections = []
105
+ parent_section = nil
106
+
107
+ sections.each do |section|
108
+ if section.sub_section?
109
+ # NOTE parent_sectionがないパターンは不正なので無視する
110
+ parent_section.add_subsection(section) if parent_section
111
+ else
112
+ root_sections << section
113
+ parent_section = section
114
+ end
115
+ end
116
+
117
+ root_sections
118
+ end
119
+
120
+ private_class_method :structure_sections
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,15 @@
1
+ module Syobocal
2
+ module Comment
3
+ class Staff
4
+ attr_reader :role, :people
5
+
6
+ def initialize(role, people)
7
+ @role, @people = role, people
8
+ end
9
+
10
+ def ==(other)
11
+ other.instance_of?(self.class) && other.role == role && other.people == people
12
+ end
13
+ end
14
+ end
15
+ end
@@ -3,14 +3,14 @@ module Syobocal
3
3
  module Mapper
4
4
  def map(elm)
5
5
  result = {}
6
- elm.each_element{|child|
7
- set(result, to_snake(child.name).to_sym, child, @map[child.name])
6
+ elm.each_element { |child|
7
+ set(result, to_snake(child.name).to_sym, child, @map[child.name])
8
8
  }
9
9
  result
10
10
  end
11
11
 
12
12
  def to_snake(name)
13
- name.gsub(/([a-z])([A-Z])/){ $1 + '_' + $2 }.downcase
13
+ name.gsub(/([a-z])([A-Z])/) { $1 + "_" + $2 }.downcase
14
14
  end
15
15
 
16
16
  def set(hash, key, elm, type)
@@ -40,7 +40,7 @@ module Syobocal
40
40
  end
41
41
 
42
42
  def url(params)
43
- 'http://cal.syoboi.jp/db.php?Command=TitleLookup' + Syobocal::Util.format_params_amp(params)
43
+ "http://cal.syoboi.jp/db.php?Command=TitleLookup" + Syobocal::Util.format_params_amp(params)
44
44
  end
45
45
 
46
46
  def parse(xml)
@@ -48,10 +48,10 @@ module Syobocal
48
48
 
49
49
  result = LookupResult.new
50
50
 
51
- result.code = xml.elements['TitleLookupResponse/Result/Code'].text.to_i
52
- result.message = xml.elements['TitleLookupResponse/Result/Message'].text
51
+ result.code = xml.elements["TitleLookupResponse/Result/Code"].text.to_i
52
+ result.message = xml.elements["TitleLookupResponse/Result/Message"].text
53
53
 
54
- xml.elements.each('TitleLookupResponse/TitleItems/TitleItem'){|item|
54
+ xml.elements.each("TitleLookupResponse/TitleItems/TitleItem") { |item|
55
55
  mapper = Mapper.new
56
56
  result << mapper.map(item)
57
57
  }
@@ -95,7 +95,7 @@ module Syobocal
95
95
  end
96
96
 
97
97
  def url(params)
98
- 'http://cal.syoboi.jp/db.php?Command=ProgLookup' + Syobocal::Util.format_params_amp(params)
98
+ "http://cal.syoboi.jp/db.php?Command=ProgLookup" + Syobocal::Util.format_params_amp(params)
99
99
  end
100
100
 
101
101
  def parse(xml)
@@ -103,10 +103,10 @@ module Syobocal
103
103
 
104
104
  result = LookupResult.new
105
105
 
106
- result.code = xml.elements['ProgLookupResponse/Result/Code'].text.to_i
107
- result.message = xml.elements['ProgLookupResponse/Result/Message'].text
106
+ result.code = xml.elements["ProgLookupResponse/Result/Code"].text.to_i
107
+ result.message = xml.elements["ProgLookupResponse/Result/Message"].text
108
108
 
109
- xml.elements.each('ProgLookupResponse/ProgItems/ProgItem'){|item|
109
+ xml.elements.each("ProgLookupResponse/ProgItems/ProgItem") { |item|
110
110
  mapper = Mapper.new
111
111
  result << mapper.map(item)
112
112
  }
@@ -147,7 +147,7 @@ module Syobocal
147
147
  end
148
148
 
149
149
  def url(params)
150
- 'http://cal.syoboi.jp/db.php?Command=ChLookup' + Syobocal::Util.format_params_amp(params)
150
+ "http://cal.syoboi.jp/db.php?Command=ChLookup" + Syobocal::Util.format_params_amp(params)
151
151
  end
152
152
 
153
153
  def parse(xml)
@@ -155,10 +155,10 @@ module Syobocal
155
155
 
156
156
  result = LookupResult.new
157
157
 
158
- result.code = xml.elements['ChLookupResponse/Result/Code'].text.to_i
159
- result.message = xml.elements['ChLookupResponse/Result/Message'].text
158
+ result.code = xml.elements["ChLookupResponse/Result/Code"].text.to_i
159
+ result.message = xml.elements["ChLookupResponse/Result/Message"].text
160
160
 
161
- xml.elements.each('ChLookupResponse/ChItems/ChItem'){|item|
161
+ xml.elements.each("ChLookupResponse/ChItems/ChItem") { |item|
162
162
  mapper = Mapper.new
163
163
  result << mapper.map(item)
164
164
  }
@@ -193,7 +193,7 @@ module Syobocal
193
193
  end
194
194
 
195
195
  def url(params)
196
- 'http://cal.syoboi.jp/db.php?Command=ChGroupLookup' + Syobocal::Util.format_params_amp(params)
196
+ "http://cal.syoboi.jp/db.php?Command=ChGroupLookup" + Syobocal::Util.format_params_amp(params)
197
197
  end
198
198
 
199
199
  def parse(xml)
@@ -201,10 +201,10 @@ module Syobocal
201
201
 
202
202
  result = LookupResult.new
203
203
 
204
- result.code = xml.elements['ChGroupLookupResponse/Result/Code'].text.to_i
205
- result.message = xml.elements['ChGroupLookupResponse/Result/Message'].text
204
+ result.code = xml.elements["ChGroupLookupResponse/Result/Code"].text.to_i
205
+ result.message = xml.elements["ChGroupLookupResponse/Result/Message"].text
206
206
 
207
- xml.elements.each('ChGroupLookupResponse/ChGroupItems/ChGroupItem'){|item|
207
+ xml.elements.each("ChGroupLookupResponse/ChGroupItems/ChGroupItem") { |item|
208
208
  mapper = Mapper.new
209
209
  result << mapper.map(item)
210
210
  }
@@ -235,7 +235,7 @@ module Syobocal
235
235
  end
236
236
 
237
237
  def url(params)
238
- 'http://cal.syoboi.jp/db.php?Command=TitleViewCount' + Syobocal::Util.format_params_amp(params)
238
+ "http://cal.syoboi.jp/db.php?Command=TitleViewCount" + Syobocal::Util.format_params_amp(params)
239
239
  end
240
240
 
241
241
  def parse(xml)
@@ -251,7 +251,7 @@ module Syobocal
251
251
  end
252
252
 
253
253
  def url(params)
254
- 'http://cal.syoboi.jp/db.php?Command=TitleRankHistory' + Syobocal::Util.format_params_amp(params)
254
+ "http://cal.syoboi.jp/db.php?Command=TitleRankHistory" + Syobocal::Util.format_params_amp(params)
255
255
  end
256
256
 
257
257
  def parse(xml)
@@ -267,7 +267,7 @@ module Syobocal
267
267
  end
268
268
 
269
269
  def url(params)
270
- 'http://cal.syoboi.jp/db.php?Command=TitlePointHistory' + Syobocal::Util.format_params_amp(params)
270
+ "http://cal.syoboi.jp/db.php?Command=TitlePointHistory" + Syobocal::Util.format_params_amp(params)
271
271
  end
272
272
 
273
273
  def parse(xml)
@@ -283,7 +283,7 @@ module Syobocal
283
283
  end
284
284
 
285
285
  def url(params)
286
- 'http://cal.syoboi.jp/db.php?Command=TitlePointTop' + Syobocal::Util.format_params_amp(params)
286
+ "http://cal.syoboi.jp/db.php?Command=TitlePointTop" + Syobocal::Util.format_params_amp(params)
287
287
  end
288
288
 
289
289
  def parse(xml)
@@ -298,21 +298,21 @@ module Syobocal
298
298
 
299
299
  result = TableResult.new
300
300
 
301
- result.code = xml.elements['TableData/Result/Code'].text.to_i
302
- result.message = xml.elements['TableData/Result/Message'].text
301
+ result.code = xml.elements["TableData/Result/Code"].text.to_i
302
+ result.message = xml.elements["TableData/Result/Message"].text
303
303
 
304
304
  result.columns = []
305
305
  if result.code == 200
306
- result.title = xml.elements['TableData/Title'].text
307
- result.type = xml.elements['TableData/Type'].text
306
+ result.title = xml.elements["TableData/Title"].text
307
+ result.type = xml.elements["TableData/Type"].text
308
308
 
309
- xml.elements.each('TableData/Columns/Value'){|item|
309
+ xml.elements.each("TableData/Columns/Value") { |item|
310
310
  result.columns << item.text
311
311
  }
312
312
 
313
- xml.elements.each('TableData/Line'){|line|
313
+ xml.elements.each("TableData/Line") { |line|
314
314
  line_data = {}
315
- line.elements.each_with_index{|value, index|
315
+ line.elements.each_with_index { |value, index|
316
316
  key = result.columns[index]
317
317
  if key == "Date"
318
318
  line_data[key] = Date.parse(value.text)