micromicro 1.1.0 → 3.0.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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -1
  3. data/CONTRIBUTING.md +3 -3
  4. data/README.md +9 -102
  5. data/lib/micro_micro/collectible.rb +2 -0
  6. data/lib/micro_micro/collections/base_collection.rb +8 -1
  7. data/lib/micro_micro/collections/items_collection.rb +84 -1
  8. data/lib/micro_micro/collections/properties_collection.rb +111 -0
  9. data/lib/micro_micro/collections/relationships_collection.rb +85 -6
  10. data/lib/micro_micro/document.rb +21 -103
  11. data/lib/micro_micro/helpers.rb +94 -0
  12. data/lib/micro_micro/implied_property.rb +15 -0
  13. data/lib/micro_micro/item.rb +93 -79
  14. data/lib/micro_micro/parsers/base_implied_property_parser.rb +29 -0
  15. data/lib/micro_micro/parsers/base_property_parser.rb +6 -12
  16. data/lib/micro_micro/parsers/date_time_parser.rb +61 -25
  17. data/lib/micro_micro/parsers/date_time_property_parser.rb +10 -6
  18. data/lib/micro_micro/parsers/embedded_markup_property_parser.rb +4 -2
  19. data/lib/micro_micro/parsers/implied_name_property_parser.rb +15 -16
  20. data/lib/micro_micro/parsers/implied_photo_property_parser.rb +21 -43
  21. data/lib/micro_micro/parsers/implied_url_property_parser.rb +12 -30
  22. data/lib/micro_micro/parsers/plain_text_property_parser.rb +4 -1
  23. data/lib/micro_micro/parsers/url_property_parser.rb +22 -12
  24. data/lib/micro_micro/parsers/value_class_pattern_parser.rb +29 -42
  25. data/lib/micro_micro/property.rb +126 -56
  26. data/lib/micro_micro/relationship.rb +38 -13
  27. data/lib/micro_micro/version.rb +3 -1
  28. data/lib/micromicro.rb +32 -26
  29. data/micromicro.gemspec +11 -6
  30. metadata +22 -19
@@ -1,78 +1,114 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  module Parsers
3
5
  class DateTimeParser
4
- # @see https://microformats.org/wiki/value-class-pattern#Date_and_time_parsing
5
- #
6
6
  # Regexp pattern matching YYYY-MM-DD and YYY-DDD
7
- DATE_REGEXP_PATTERN = '(?<year>\d{4})-((?<ordinal>3[0-6]{2}|[0-2]\d{2})|(?<month>0\d|1[0-2])-(?<day>3[0-1]|[0-2]\d))'.freeze
8
- # Regexp pattern matching HH:MM and HH:MM:SS
9
- TIME_REGEXP_PATTERN = '(?<hours>2[0-3]|[0-1]?\d)(?::(?<minutes>[0-5]\d))?(?::(?<seconds>[0-5]\d))?(?:\s*?(?<abbreviation>[apPP]\.?[mM]\.?))?'.freeze
10
- # Regexp pattern matching +/-(XX:YY|XXYY|XX) or the literal string Z
11
- TIMEZONE_REGEXP_PATTERN = '(?<zulu>Z)|(?<offset>(?:\+|-)(?:1[0-2]|0?\d)(?::?[0-5]\d)?)'.freeze
7
+ DATE_REGEXP_PATTERN = '(?<year>\d{4})-' \
8
+ '((?<ordinal>3[0-6]{2}|[0-2]\d{2})|(?<month>0\d|1[0-2])-' \
9
+ '(?<day>3[0-1]|[0-2]\d))'
12
10
 
13
- CAPTURE_NAMES = [:year, :ordinal, :month, :day, :hours, :minutes, :seconds, :abbreviation, :zulu, :offset].freeze
11
+ # Regexp pattern matching HH:MM and HH:MM:SS
12
+ TIME_REGEXP_PATTERN = '(?<hours>2[0-3]|[0-1]?\d)' \
13
+ '(?::(?<minutes>[0-5]\d))?' \
14
+ '(?::(?<seconds>[0-5]\d))?' \
15
+ '(?:\s*?(?<abbreviation>[apPP]\.?[mM]\.?))?'
14
16
 
15
- # @param string [String]
17
+ # Regexp pattern matching +/-(XX:YY|XXYY|XX) or the literal string Z
18
+ TIMEZONE_REGEXP_PATTERN = '(?<zulu>Z)|(?<offset>(?:\+|-)(?:1[0-2]|0?\d)(?::?[0-5]\d)?)'
19
+
20
+ # Regexp for extracting named captures from a datetime-esque String.
21
+ DATE_TIME_TIMEZONE_REGEXP = /
22
+ \A
23
+ (?=.)
24
+ (?:#{DATE_REGEXP_PATTERN})?
25
+ (?:\s?#{TIME_REGEXP_PATTERN}(?:#{TIMEZONE_REGEXP_PATTERN})?)?
26
+ \z
27
+ /x.freeze
28
+
29
+ # Parse a string for date and/or time values according to the Microformats
30
+ # Value Class Pattern date and time parsing specification.
31
+ #
32
+ # @see https://microformats.org/wiki/value-class-pattern#Date_and_time_parsing
33
+ # microformats.org: Value Class Pattern § Date and time parsing
34
+ #
35
+ # @param string [String, #to_s]
16
36
  def initialize(string)
17
- @string = string
37
+ @string = string.to_s
18
38
  end
19
39
 
20
- CAPTURE_NAMES.each do |name|
40
+ # Define getter and predicate methods for all possible named captures
41
+ # returned by the DATE_TIME_TIMEZONE_REGEXP regular expression.
42
+ [
43
+ :year, :ordinal, :month, :day,
44
+ :hours, :minutes, :seconds,
45
+ :abbreviation, :zulu, :offset
46
+ ].each do |name|
21
47
  define_method(name) { values[name] }
22
48
  define_method("#{name}?") { public_send(name).present? }
23
49
  end
24
50
 
51
+ # @return [String, nil]
25
52
  def normalized_calendar_date
26
53
  @normalized_calendar_date ||= "#{year}-#{month}-#{day}" if year? && month? && day?
27
54
  end
28
55
 
56
+ # @return [String, nil]
29
57
  def normalized_date
30
58
  @normalized_date ||= normalized_calendar_date || normalized_ordinal_date
31
59
  end
32
60
 
61
+ # @return [String, nil]
33
62
  def normalized_hours
34
- @normalized_hours ||= begin
35
- return unless hours?
36
- return (hours.to_i + 12).to_s if abbreviation&.tr('.', '')&.downcase == 'pm'
63
+ @normalized_hours ||=
64
+ if hours?
65
+ return (hours.to_i + 12).to_s if abbreviation&.tr('.', '')&.downcase == 'pm'
37
66
 
38
- format('%<hours>02d', hours: hours)
39
- end
67
+ format('%<hours>02d', hours: hours)
68
+ end
40
69
  end
41
70
 
71
+ # @return [String]
42
72
  def normalized_minutes
43
73
  @normalized_minutes ||= minutes || '00'
44
74
  end
45
75
 
76
+ # @return [String, nil]
46
77
  def normalized_ordinal_date
47
78
  @normalized_ordinal_date ||= "#{year}-#{ordinal}" if year? && ordinal?
48
79
  end
49
80
 
81
+ # @return [String, nil]
50
82
  def normalized_time
51
83
  @normalized_time ||= [normalized_hours, normalized_minutes, seconds].compact.join(':') if normalized_hours
52
84
  end
53
85
 
86
+ # @return [String, nil]
54
87
  def normalized_timezone
55
88
  @normalized_timezone ||= zulu || offset&.tr(':', '')
56
89
  end
57
90
 
58
- # @return [String]
91
+ # @return [String, nil]
59
92
  def value
60
- @value ||= "#{normalized_date} #{normalized_time}#{normalized_timezone}".strip if normalized_date || normalized_time || normalized_timezone
93
+ @value ||=
94
+ if normalized_date || normalized_time || normalized_timezone
95
+ "#{normalized_date} #{normalized_time}#{normalized_timezone}".strip
96
+ end
61
97
  end
62
98
 
63
99
  # @return [Hash{Symbol => String, nil}]
64
100
  def values
65
- @values ||= self.class.values_from(string)
66
- end
67
-
68
- # @param string [String]
69
- # @return [Hash{Symbol => String, nil}]
70
- def self.values_from(string)
71
- string&.match(/^(?:#{DATE_REGEXP_PATTERN})?(?:\s?#{TIME_REGEXP_PATTERN}(?:#{TIMEZONE_REGEXP_PATTERN})?)?$/)&.named_captures.to_h.symbolize_keys
101
+ @values ||=
102
+ if string.match?(DATE_TIME_TIMEZONE_REGEXP)
103
+ string.match(DATE_TIME_TIMEZONE_REGEXP).named_captures.symbolize_keys
104
+ else
105
+ {}
106
+ end
72
107
  end
73
108
 
74
109
  private
75
110
 
111
+ # @return [String]
76
112
  attr_reader :string
77
113
  end
78
114
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  module Parsers
3
5
  class DateTimePropertyParser < BasePropertyParser
@@ -8,6 +10,7 @@ module MicroMicro
8
10
  }.freeze
9
11
 
10
12
  # @see https://microformats.org/wiki/microformats2-parsing#parsing_a_dt-_property
13
+ # microformats.org: microformats2 parsing specification § Parsing a +dt-+ property
11
14
  #
12
15
  # @return [String]
13
16
  def value
@@ -17,19 +20,19 @@ module MicroMicro
17
20
  private
18
21
 
19
22
  # @see https://microformats.org/wiki/value-class-pattern#microformats2_parsers_implied_date
23
+ # microformats.org: Value Class Pattern § microformats2 parsers implied date
20
24
  #
21
25
  # @return [MicroMicro::Parsers::DateTimeParser, nil]
22
26
  def adopted_date_time_parser
23
- @adopted_date_time_parser ||= begin
24
- date_time_siblings = (property.prev_all.reverse + property.next_all).select { |prop| prop.prefix == 'dt' }
25
-
26
- date_time_siblings.map { |prop| DateTimeParser.new(prop.value) }.find(&:normalized_date)
27
- end
27
+ @adopted_date_time_parser ||=
28
+ (property.prev_all.reverse + property.next_all).filter_map do |prop|
29
+ DateTimeParser.new(prop.value) if prop.date_time_property?
30
+ end.find(&:normalized_date)
28
31
  end
29
32
 
30
33
  # @return [String, nil]
31
34
  def attribute_value
32
- self.class.attribute_value_from(node, HTML_ATTRIBUTES_MAP)
35
+ Helpers.attribute_value_from(node, HTML_ATTRIBUTES_MAP)
33
36
  end
34
37
 
35
38
  # @return [MicroMicro::Parsers::DateTimeParser]
@@ -38,6 +41,7 @@ module MicroMicro
38
41
  end
39
42
 
40
43
  # @see https://microformats.org/wiki/value-class-pattern#microformats2_parsers_implied_date
44
+ # microformats.org: Value Class Pattern § microformats2 parsers implied date
41
45
  #
42
46
  # @return [Boolean]
43
47
  def imply_date?
@@ -1,16 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  module Parsers
3
5
  class EmbeddedMarkupPropertyParser < BasePropertyParser
4
6
  # @see https://microformats.org/wiki/microformats2-parsing#parsing_an_e-_property
7
+ # microformats.org: microformats2 parsing specification § Parsing an +e-+ property
5
8
  #
6
9
  # @return [Hash{Symbol => String}]
7
10
  def value
8
- @value ||= begin
11
+ @value ||=
9
12
  {
10
13
  html: node.inner_html.strip,
11
14
  value: super
12
15
  }
13
- end
14
16
  end
15
17
  end
16
18
  end
@@ -1,12 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  module Parsers
3
- class ImpliedNamePropertyParser < BasePropertyParser
4
- HTML_ATTRIBUTES_MAP = {
5
- 'alt' => %w[area img],
6
- 'title' => %w[abbr]
5
+ class ImpliedNamePropertyParser < BaseImpliedPropertyParser
6
+ HTML_ELEMENTS_MAP = {
7
+ 'img' => 'alt',
8
+ 'area' => 'alt',
9
+ 'abbr' => 'title'
7
10
  }.freeze
8
11
 
9
12
  # @see https://microformats.org/wiki/microformats2-parsing#parsing_for_implied_properties
13
+ # microformats.org: microformats2 parsing specification § Parsing for implied properties
10
14
  #
11
15
  # @return [String]
12
16
  def value
@@ -15,24 +19,19 @@ module MicroMicro
15
19
 
16
20
  private
17
21
 
18
- # @return [Nokogiri::XML::NodeSet]
19
- def candidate_nodes
20
- @candidate_nodes ||= Nokogiri::XML::NodeSet.new(node.document, child_nodes.unshift(node))
21
- end
22
-
23
22
  # @return [Array]
24
23
  def child_nodes
25
- [node.at_css('> :only-child'), node.at_css('> :only-child > :only-child')].compact.reject { |child_node| Item.item_node?(child_node) }
26
- end
27
-
28
- # @return [String, nil]
29
- def attribute_value
30
- candidate_nodes.map { |node| self.class.attribute_value_from(node, HTML_ATTRIBUTES_MAP) }.compact.first
24
+ [
25
+ node.at_css('> :only-child'),
26
+ node.at_css('> :only-child > :only-child')
27
+ ].compact.reject { |child_node| Helpers.item_node?(child_node) }
31
28
  end
32
29
 
33
30
  # @return [String]
34
31
  def text_content
35
- @text_content ||= Document.text_content_from(node) { |context| context.css('img').each { |img| img.content = img['alt'] } }
32
+ Helpers.text_content_from(node) do |context|
33
+ context.css('img').each { |img| img.content = img['alt'] }
34
+ end
36
35
  end
37
36
  end
38
37
  end
@@ -1,64 +1,42 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  module Parsers
3
- class ImpliedPhotoPropertyParser < BasePropertyParser
5
+ class ImpliedPhotoPropertyParser < BaseImpliedPropertyParser
6
+ CSS_SELECTORS_ARRAY = ['> img[src]:only-of-type', '> object[data]:only-of-type'].freeze
7
+
4
8
  HTML_ELEMENTS_MAP = {
5
9
  'img' => 'src',
6
10
  'object' => 'data'
7
11
  }.freeze
8
12
 
9
13
  # @see https://microformats.org/wiki/microformats2-parsing#parsing_for_implied_properties
14
+ # microformats.org: microformats2 parsing specification § Parsing for implied properties
10
15
  # @see https://microformats.org/wiki/microformats2-parsing#parse_an_img_element_for_src_and_alt
16
+ # microformats.org: microformats2 parsing specification § Parse an img element for src and alt
11
17
  #
12
18
  # @return [String, Hash{Symbol => String}, nil]
13
19
  def value
14
- @value ||= begin
15
- return unless resolved_value
16
- return resolved_value unless value_node.matches?('img[alt]')
17
-
18
- {
19
- value: resolved_value,
20
- alt: value_node['alt'].strip
21
- }
22
- end
20
+ @value ||=
21
+ if attribute_value
22
+ return attribute_value unless candidate_node.matches?('img[alt]')
23
+
24
+ {
25
+ value: attribute_value,
26
+ alt: candidate_node['alt'].strip
27
+ }
28
+ end
23
29
  end
24
30
 
25
31
  private
26
32
 
27
- # @return [Array<String>]
28
- def attribute_values
29
- @attribute_values ||= begin
30
- HTML_ELEMENTS_MAP.map do |element, attribute|
31
- node if node.matches?("#{element}[#{attribute}]")
32
- end.compact
33
- end
34
- end
35
-
36
- # @return [String, nil]
37
- def resolved_value
38
- @resolved_value ||= value_node[HTML_ELEMENTS_MAP[value_node.name]] if value_node
39
- end
33
+ # @return [Array]
34
+ def child_nodes
35
+ nodes = [node.at_css(*CSS_SELECTORS_ARRAY)]
40
36
 
41
- # @return [Nokogiri::XML::Element, nil]
42
- def value_node
43
- @value_node ||= begin
44
- return attribute_values.first if attribute_values.any?
45
-
46
- HTML_ELEMENTS_MAP.each do |element, attribute|
47
- child_node = node.at_css("> #{element}[#{attribute}]:only-of-type")
48
-
49
- return child_node if child_node && !Item.item_node?(child_node) && element == child_node.name && child_node[attribute]
50
- end
51
-
52
- if node.element_children.one? && !Item.item_node?(node.first_element_child)
53
- HTML_ELEMENTS_MAP.each do |element, attribute|
54
- child_node = node.first_element_child.at_css("> #{element}[#{attribute}]:only-of-type")
55
-
56
- return child_node if child_node && !Item.item_node?(child_node) && element == child_node.name && child_node[attribute]
57
- end
58
- end
37
+ nodes << node.first_element_child.at_css(*CSS_SELECTORS_ARRAY) if node.element_children.one?
59
38
 
60
- nil
61
- end
39
+ nodes.compact.reject { |child_node| Helpers.item_node?(child_node) }
62
40
  end
63
41
  end
64
42
  end
@@ -1,50 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  module Parsers
3
- class ImpliedUrlPropertyParser < BasePropertyParser
5
+ class ImpliedUrlPropertyParser < BaseImpliedPropertyParser
6
+ CSS_SELECTORS_ARRAY = ['> a[href]:only-of-type', '> area[href]:only-of-type'].freeze
7
+
4
8
  HTML_ELEMENTS_MAP = {
5
9
  'a' => 'href',
6
10
  'area' => 'href'
7
11
  }.freeze
8
12
 
9
13
  # @see https://microformats.org/wiki/microformats2-parsing#parsing_for_implied_properties
14
+ # microformats.org: microformats2 parsing specification § Parsing for implied properties
10
15
  #
11
16
  # @return [String, nil]
12
17
  def value
13
- @value ||= value_node[HTML_ELEMENTS_MAP[value_node.name]] if value_node
18
+ @value ||= attribute_value
14
19
  end
15
20
 
16
21
  private
17
22
 
18
- # @return [Array<String>]
19
- def attribute_values
20
- @attribute_values ||= begin
21
- HTML_ELEMENTS_MAP.map do |element, attribute|
22
- node if node.matches?("#{element}[#{attribute}]")
23
- end.compact
24
- end
25
- end
26
-
27
- # @return [Nokogiri::XML::Element, nil]
28
- def value_node
29
- @value_node ||= begin
30
- return attribute_values.first if attribute_values.any?
31
-
32
- HTML_ELEMENTS_MAP.each do |element, attribute|
33
- child_node = node.at_css("> #{element}[#{attribute}]:only-of-type")
34
-
35
- return child_node if child_node && !Item.item_node?(child_node) && element == child_node.name && child_node[attribute]
36
- end
37
-
38
- if node.element_children.one? && !Item.item_node?(node.first_element_child)
39
- HTML_ELEMENTS_MAP.each do |element, attribute|
40
- child_node = node.first_element_child.at_css("> #{element}[#{attribute}]:only-of-type")
23
+ # @return [Array]
24
+ def child_nodes
25
+ nodes = [node.at_css(*CSS_SELECTORS_ARRAY)]
41
26
 
42
- return child_node if child_node && !Item.item_node?(child_node) && element == child_node.name && child_node[attribute]
43
- end
44
- end
27
+ nodes << node.first_element_child.at_css(*CSS_SELECTORS_ARRAY) if node.element_children.one?
45
28
 
46
- nil
47
- end
29
+ nodes.compact.reject { |child_node| Helpers.item_node?(child_node) }
48
30
  end
49
31
  end
50
32
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  module Parsers
3
5
  class PlainTextPropertyParser < BasePropertyParser
@@ -8,6 +10,7 @@ module MicroMicro
8
10
  }.freeze
9
11
 
10
12
  # @see https://microformats.org/wiki/microformats2-parsing#parsing_a_p-_property
13
+ # microformats.org: microformats2 parsing specification § Parsing a +p-+ property
11
14
  #
12
15
  # @return [String]
13
16
  def value
@@ -18,7 +21,7 @@ module MicroMicro
18
21
 
19
22
  # @return [String, nil]
20
23
  def attribute_value
21
- self.class.attribute_value_from(node, HTML_ATTRIBUTES_MAP)
24
+ Helpers.attribute_value_from(node, HTML_ATTRIBUTES_MAP)
22
25
  end
23
26
 
24
27
  # @return [String, nil]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  module Parsers
3
5
  class UrlPropertyParser < BasePropertyParser
@@ -14,40 +16,48 @@ module MicroMicro
14
16
  }.freeze
15
17
 
16
18
  # @see https://microformats.org/wiki/microformats2-parsing#parsing_a_u-_property
19
+ # microformats.org: microformats2 parsing specification § Parsing a +u-+ property
17
20
  # @see https://microformats.org/wiki/microformats2-parsing#parse_an_img_element_for_src_and_alt
21
+ # microformats.org: microformats2 parsing specification § Parse an img element for src and alt
18
22
  #
19
23
  # @return [String, Hash{Symbol => String}]
20
24
  def value
21
- @value ||= begin
22
- return resolved_value unless node.matches?('img[alt]')
23
-
24
- {
25
- value: resolved_value,
26
- alt: node['alt'].strip
27
- }
28
- end
25
+ @value ||=
26
+ if node.matches?('img[alt]')
27
+ {
28
+ value: resolved_value,
29
+ alt: node['alt'].strip
30
+ }
31
+ else
32
+ resolved_value
33
+ end
29
34
  end
30
35
 
31
36
  private
32
37
 
33
38
  # @return [String, nil]
34
39
  def attribute_value
35
- self.class.attribute_value_from(node, HTML_ATTRIBUTES_MAP)
40
+ Helpers.attribute_value_from(node, HTML_ATTRIBUTES_MAP)
36
41
  end
37
42
 
38
43
  # @return [String, nil]
39
44
  def extended_attribute_value
40
- self.class.attribute_value_from(node, EXTENDED_HTML_ATTRIBUTES_MAP)
45
+ Helpers.attribute_value_from(node, EXTENDED_HTML_ATTRIBUTES_MAP)
41
46
  end
42
47
 
43
48
  # @return [String]
44
49
  def resolved_value
45
- @resolved_value ||= Addressable::URI.join(node.document.url, unresolved_value.strip).to_s
50
+ @resolved_value ||= node.document.resolve_relative_url(unresolved_value.strip)
51
+ end
52
+
53
+ # @return [String]
54
+ def text_content
55
+ Helpers.text_content_from(node)
46
56
  end
47
57
 
48
58
  # @return [String]
49
59
  def unresolved_value
50
- attribute_value || value_class_pattern_value || extended_attribute_value || Document.text_content_from(node)
60
+ attribute_value || value_class_pattern_value || extended_attribute_value || text_content
51
61
  end
52
62
 
53
63
  # @return [String, nil]
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  module Parsers
3
5
  class ValueClassPatternParser
4
6
  # @see https://microformats.org/wiki/value-class-pattern#Basic_Parsing
7
+ # microformats.org: Value Class Pattern § Basic Parsing
5
8
  # @see https://microformats.org/wiki/value-class-pattern#Date_and_time_values
9
+ # microformats.org: Value Class Pattern § Date and time values
6
10
  HTML_ATTRIBUTES_MAP = {
7
11
  'alt' => %w[area img],
8
12
  'value' => %w[data],
@@ -10,72 +14,55 @@ module MicroMicro
10
14
  'datetime' => %w[del ins time]
11
15
  }.freeze
12
16
 
13
- # @param context [Nokogiri::XML::Element]
14
- # @param separator [String]
15
- def initialize(node, separator = '')
16
- @node = node
17
- @separator = separator
18
- end
19
-
20
- # @return [String, nil]
21
- def value
22
- @value ||= values.join(separator).strip if values.any?
23
- end
24
-
25
- # @return [Array<String>]
26
- def values
27
- @values ||= value_nodes.map { |value_node| self.class.value_from(value_node) }.select(&:present?)
28
- end
29
-
30
17
  # @param context [Nokogiri::XML::NodeSet, Nokogiri::XML::Element]
31
18
  # @param node_set [Nokogiri::XML::NodeSet]
32
19
  # @return [Nokogiri::XML::NodeSet]
33
- def self.nodes_from(context, node_set = Nokogiri::XML::NodeSet.new(context.document, []))
34
- context.each { |node| nodes_from(node, node_set) } if context.is_a?(Nokogiri::XML::NodeSet)
20
+ def self.node_set_from(context, node_set = Nokogiri::XML::NodeSet.new(context.document, []))
21
+ context.each { |node| node_set_from(node, node_set) } if context.is_a?(Nokogiri::XML::NodeSet)
35
22
 
36
- if context.is_a?(Nokogiri::XML::Element) && !Document.ignore_node?(context)
37
- if value_class_node?(context) || value_title_node?(context)
23
+ if context.is_a?(Nokogiri::XML::Element) && !Helpers.ignore_node?(context)
24
+ if Helpers.value_class_node?(context) || Helpers.value_title_node?(context)
38
25
  node_set << context
39
26
  else
40
- nodes_from(context.element_children, node_set)
27
+ node_set_from(context.element_children, node_set)
41
28
  end
42
29
  end
43
30
 
44
31
  node_set
45
32
  end
46
33
 
47
- # @param node [Nokogiri::XML::Element]
48
- # @return [Boolean]
49
- def self.value_class_node?(node)
50
- node.classes.include?('value')
51
- end
52
-
53
34
  # @param node [Nokogiri::XML::Element]
54
35
  # @return [String, nil]
55
36
  def self.value_from(node)
56
- return node['title'] if value_title_node?(node)
37
+ return node['title'] if Helpers.value_title_node?(node)
57
38
 
58
- HTML_ATTRIBUTES_MAP.each do |attribute, names|
59
- return node[attribute] if names.include?(node.name) && node[attribute]
60
- end
61
-
62
- node.text
39
+ Helpers.attribute_value_from(node, HTML_ATTRIBUTES_MAP) || node.text
63
40
  end
64
41
 
65
42
  # @param node [Nokogiri::XML::Element]
66
- # @return [Boolean]
67
- def self.value_title_node?(node)
68
- node.classes.include?('value-title')
43
+ # @param separator [String]
44
+ def initialize(node, separator = '')
45
+ @node = node
46
+ @separator = separator
47
+ end
48
+
49
+ # @return [String, nil]
50
+ def value
51
+ @value ||= values.join(separator).strip if values.any?
52
+ end
53
+
54
+ # @return [Array<String>]
55
+ def values
56
+ @values ||=
57
+ self.class
58
+ .node_set_from(node)
59
+ .map { |value_node| self.class.value_from(value_node) }
60
+ .select(&:present?)
69
61
  end
70
62
 
71
63
  private
72
64
 
73
65
  attr_reader :node, :separator
74
-
75
- # @return [Nokogiri::XML::NodeSet]
76
- def value_nodes
77
- @value_nodes ||= self.class.nodes_from(node)
78
- end
79
66
  end
80
67
  end
81
68
  end