micromicro 1.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,35 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  class Document
3
- # A map of HTML `srcset` attributes and their associated element names
4
- #
5
- # @see https://html.spec.whatwg.org/#srcset-attributes
6
- # @see https://html.spec.whatwg.org/#attributes-3
7
- HTML_IMAGE_CANDIDATE_STRINGS_ATTRIBUTES_MAP = {
8
- 'imagesrcset' => %w[link],
9
- 'srcset' => %w[img source]
10
- }.freeze
11
-
12
- # A map of HTML URL attributes and their associated element names
13
- #
14
- # @see https://html.spec.whatwg.org/#attributes-3
15
- HTML_URL_ATTRIBUTES_MAP = {
16
- 'action' => %w[form],
17
- 'cite' => %w[blockquote del ins q],
18
- 'data' => %w[object],
19
- 'formaction' => %w[button input],
20
- 'href' => %w[a area base link],
21
- 'manifest' => %w[html],
22
- 'ping' => %w[a area],
23
- 'poster' => %w[video],
24
- 'src' => %w[audio embed iframe img input script source track video]
25
- }.freeze
26
-
27
5
  # Parse a string of HTML for microformats2-encoded data.
28
6
  #
7
+ # @example Parse a String of markup
29
8
  # MicroMicro::Document.new('<a href="/" class="h-card" rel="me">Jason Garber</a>', 'https://sixtwothree.org')
30
9
  #
31
- # Or, pull the source HTML of a page on the Web:
32
- #
10
+ # @example Parse a String of markup from a URL
33
11
  # url = 'https://tantek.com'
34
12
  # markup = Net::HTTP.get(URI.parse(url))
35
13
  #
@@ -38,34 +16,41 @@ module MicroMicro
38
16
  # @param markup [String] The HTML to parse for microformats2-encoded data.
39
17
  # @param base_url [String] The URL associated with markup. Used for relative URL resolution.
40
18
  def initialize(markup, base_url)
41
- @markup = markup
42
- @base_url = base_url
43
-
44
- resolve_relative_urls
19
+ @document = Nokogiri::HTML(markup, base_url).resolve_relative_urls!
45
20
  end
46
21
 
47
22
  # @return [String]
23
+ #
24
+ # :nocov:
48
25
  def inspect
49
- format(%(#<#{self.class.name}:%#0x items: #{items.inspect}, relationships: #{relationships.inspect}>), object_id)
26
+ "#<#{self.class}:#{format('%#0x', object_id)} " \
27
+ "items: #{items.inspect}, " \
28
+ "relationships: #{relationships.inspect}>"
50
29
  end
30
+ # :nocov:
51
31
 
52
- # A collection of items parsed from the provided markup.
32
+ # A collection of {MicroMicro::Item}s parsed from the provided markup.
53
33
  #
54
34
  # @return [MicroMicro::Collections::ItemsCollection]
55
35
  def items
56
- @items ||= Collections::ItemsCollection.new(Item.items_from(document))
36
+ @items ||= Collections::ItemsCollection.new(Item.from_context(document.element_children))
57
37
  end
58
38
 
59
- # A collection of relationships parsed from the provided markup.
39
+ # A collection of {MicroMicro::Relationship}s parsed from the provided markup.
60
40
  #
61
41
  # @return [MicroMicro::Collections::RelationshipsCollection]
62
42
  def relationships
63
- @relationships ||= Collections::RelationshipsCollection.new(Relationship.relationships_from(document))
43
+ @relationships ||= Collections::RelationshipsCollection.new(Relationship.from_context(document))
64
44
  end
65
45
 
66
46
  # Return the parsed document as a Hash.
67
47
  #
68
48
  # @see https://microformats.org/wiki/microformats2-parsing#parse_a_document_for_microformats
49
+ # microformats.org: Parse a document for microformats
50
+ #
51
+ # @see MicroMicro::Collections::ItemsCollection#to_a
52
+ # @see MicroMicro::Collections::RelationshipsCollection#group_by_rel
53
+ # @see MicroMicro::Collections::RelationshipsCollection#group_by_url
69
54
  #
70
55
  # @return [Hash{Symbol => Array, Hash}]
71
56
  def to_h
@@ -76,76 +61,9 @@ module MicroMicro
76
61
  }
77
62
  end
78
63
 
79
- # Ignore this node?
80
- #
81
- # @param node [Nokogiri::XML::Element]
82
- # @return [Boolean]
83
- def self.ignore_node?(node)
84
- ignored_node_names.include?(node.name)
85
- end
86
-
87
- # A list of HTML element names the parser should ignore.
88
- #
89
- # @return [Array<String>]
90
- def self.ignored_node_names
91
- %w[script style template]
92
- end
93
-
94
- # @see https://microformats.org/wiki/microformats2-parsing#parse_an_element_for_properties
95
- # @see https://microformats.org/wiki/microformats2-parsing#parsing_for_implied_properties
96
- #
97
- # @param context [Nokogiri::HTML::Document, Nokogiri::XML::NodeSet, Nokogiri::XML::Element]
98
- # @yield [context]
99
- # @return [String]
100
- def self.text_content_from(context)
101
- context.css(*ignored_node_names).unlink
102
-
103
- yield(context) if block_given?
104
-
105
- context.text.strip
106
- end
107
-
108
64
  private
109
65
 
110
- attr_reader :base_url, :markup
111
-
112
- # @return [Nokogiri::XML::Element, nil]
113
- def base_element
114
- @base_element ||= Nokogiri::HTML(markup).at('//base[@href]')
115
- end
116
-
117
66
  # @return [Nokogiri::HTML::Document]
118
- def document
119
- @document ||= Nokogiri::HTML(markup, resolved_base_url)
120
- end
121
-
122
- def resolve_relative_urls
123
- HTML_URL_ATTRIBUTES_MAP.each do |attribute, names|
124
- document.xpath(*names.map { |name| "//#{name}[@#{attribute}]" }).each do |node|
125
- node[attribute] = Addressable::URI.join(resolved_base_url, node[attribute].strip).normalize.to_s
126
- end
127
- end
128
-
129
- HTML_IMAGE_CANDIDATE_STRINGS_ATTRIBUTES_MAP.each do |attribute, names|
130
- document.xpath(*names.map { |name| "//#{name}[@#{attribute}]" }).each do |node|
131
- candidates = node[attribute].split(',').map(&:strip).map { |candidate| candidate.match(/^(?<url>.+?)(?<descriptor>\s+.+)?$/) }
132
-
133
- node[attribute] = candidates.map { |candidate| "#{Addressable::URI.join(resolved_base_url, candidate[:url]).normalize}#{candidate[:descriptor]}" }.join(', ')
134
- end
135
- end
136
-
137
- self
138
- end
139
-
140
- # @return [String]
141
- def resolved_base_url
142
- @resolved_base_url ||= begin
143
- if base_element
144
- Addressable::URI.join(base_url, base_element['href'].strip).normalize.to_s
145
- else
146
- base_url
147
- end
148
- end
149
- end
67
+ attr_reader :document
150
68
  end
151
69
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MicroMicro
4
+ module Helpers
5
+ IGNORED_NODE_NAMES = %w[script style template].freeze
6
+
7
+ private_constant :IGNORED_NODE_NAMES
8
+
9
+ # @param node [Nokogiri::XML::Element]
10
+ # @param attributes_map [Hash{String => Array}]
11
+ # @return [String, nil]
12
+ def self.attribute_value_from(node, attributes_map)
13
+ attributes_map.filter_map do |attribute, names|
14
+ node[attribute] if names.include?(node.name) && node[attribute]
15
+ end.first
16
+ end
17
+
18
+ # @param node [Nokogiri::XML::Element]
19
+ # @return [Boolean]
20
+ def self.ignore_node?(node)
21
+ IGNORED_NODE_NAMES.include?(node.name)
22
+ end
23
+
24
+ # @param nodes [Nokogiri::XML::NodeSet]
25
+ # @return [Boolean]
26
+ def self.ignore_nodes?(nodes)
27
+ (nodes.map(&:name) & IGNORED_NODE_NAMES).any?
28
+ end
29
+
30
+ # @param node [Nokogiri::XML::Element]
31
+ # @return [Boolean]
32
+ def self.item_node?(node)
33
+ root_class_names_from(node).any?
34
+ end
35
+
36
+ # @param nodes [Nokogiri::XML::NodeSet]
37
+ # @return [Boolean]
38
+ def self.item_nodes?(nodes)
39
+ nodes.filter_map { |node| item_node?(node) }.any?
40
+ end
41
+
42
+ # @param node [Nokogiri::XML::Element]
43
+ # @return [Array<String>]
44
+ def self.property_class_names_from(node)
45
+ node.classes.grep(/^(?:dt|e|p|u)(?:-[0-9a-z]+)?(?:-[a-z]+)+$/).uniq
46
+ end
47
+
48
+ # @param node [Nokogiri::XML::Element]
49
+ # @return [Boolean]
50
+ def self.property_node?(node)
51
+ property_class_names_from(node).any?
52
+ end
53
+
54
+ # @param node [Nokogiri::XML::Element]
55
+ # @return [Array<String>]
56
+ def self.root_class_names_from(node)
57
+ node.classes.grep(/^h(?:-[0-9a-z]+)?(?:-[a-z]+)+$/).uniq.sort
58
+ end
59
+
60
+ # @see https://microformats.org/wiki/microformats2-parsing#parse_an_element_for_properties
61
+ # microformats.org: microformats2 parsing specification § Parse an element for properties
62
+ # @see https://microformats.org/wiki/microformats2-parsing#parsing_for_implied_properties
63
+ # microformats.org: microformats2 parsing specification § Parsing for implied properties
64
+ #
65
+ # @param context [Nokogiri::HTML::Document, Nokogiri::XML::NodeSet, Nokogiri::XML::Element]
66
+ # @yield [context]
67
+ # @return [String]
68
+ def self.text_content_from(context)
69
+ context.css(*IGNORED_NODE_NAMES).unlink
70
+
71
+ yield(context) if block_given?
72
+
73
+ context.text.strip
74
+ end
75
+
76
+ # @see https://microformats.org/wiki/value-class-pattern#Basic_Parsing
77
+ # microformats.org: Value Class Pattern § Basic Parsing
78
+ #
79
+ # @param node [Nokogiri::XML::Element]
80
+ # @return [Boolean]
81
+ def self.value_class_node?(node)
82
+ node.classes.include?('value')
83
+ end
84
+
85
+ # @see https://microformats.org/wiki/value-class-pattern#Parsing_value_from_a_title_attribute
86
+ # microformats.org: Value Class Pattern § Parsing value from a title attribute
87
+ #
88
+ # @param node [Nokogiri::XML::Element]
89
+ # @return [Boolean]
90
+ def self.value_title_node?(node)
91
+ node.classes.include?('value-title')
92
+ end
93
+ end
94
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  class ImpliedProperty < Property
3
5
  IMPLIED_PROPERTY_PARSERS_MAP = {
@@ -6,11 +8,24 @@ module MicroMicro
6
8
  'url' => Parsers::ImpliedUrlPropertyParser
7
9
  }.freeze
8
10
 
11
+ private_constant :IMPLIED_PROPERTY_PARSERS_MAP
12
+
13
+ # Always return +true+ when asked if this {MicroMicro::ImpliedProperty} is
14
+ # an implied property.
15
+ #
16
+ # @see https://microformats.org/wiki/microformats2-parsing#parsing_for_implied_properties
17
+ # microformats.org: microformats2 parsing specification § Parsing for implied properties
18
+ #
19
+ # @see MicroMicro::Property#implied?
20
+ #
9
21
  # @return [Boolean]
10
22
  def implied?
11
23
  true
12
24
  end
13
25
 
26
+ # Always return +false+ when asked if this {MicroMicro::ImpliedProperty} is
27
+ # a {MicroMicro::Item} node.
28
+ #
14
29
  # @return [Boolean]
15
30
  def item_node?
16
31
  false
@@ -1,10 +1,50 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  class Item
3
5
  include Collectible
4
6
 
7
+ class ItemNodeSearch
8
+ attr_reader :node_set
9
+
10
+ def initialize(document)
11
+ @node_set = Nokogiri::XML::NodeSet.new(document, [])
12
+ end
13
+
14
+ # rubocop:disable Metrics
15
+ def search(context)
16
+ context.each { |node| search(node) } if context.is_a?(Nokogiri::XML::NodeSet)
17
+
18
+ if context.is_a?(Nokogiri::XML::Element) && !Helpers.ignore_node?(context)
19
+ if Helpers.item_node?(context)
20
+ node_set << context unless Helpers.item_nodes?(context.ancestors) && Helpers.property_node?(context)
21
+ else
22
+ search(context.element_children)
23
+ end
24
+ end
25
+
26
+ node_set
27
+ end
28
+ # rubocop:enable Metrics
29
+ end
30
+
31
+ private_constant :ItemNodeSearch
32
+
33
+ # Extract {MicroMicro::Item}s from a context.
34
+ #
35
+ # @param context [Nokogiri::HTML::Document, Nokogiri::XML::NodeSet, Nokogiri::XML::Element]
36
+ # @return [Array<MicroMicro::Item>]
37
+ def self.from_context(context)
38
+ ItemNodeSearch
39
+ .new(context.document)
40
+ .search(context)
41
+ .map { |node| new(node) }
42
+ end
43
+
5
44
  # Parse a node for microformats2-encoded data.
6
45
  #
7
46
  # @param node [Nokogiri::XML::Element]
47
+ # @return [MicroMicro::Item]
8
48
  def initialize(node)
9
49
  @node = node
10
50
 
@@ -13,44 +53,65 @@ module MicroMicro
13
53
  properties << implied_url if implied_url?
14
54
  end
15
55
 
16
- # A collection of child items parsed from the node.
56
+ # A collection of child {MicroMicro::Item}s parsed from the node.
17
57
  #
18
58
  # @see https://microformats.org/wiki/microformats2-parsing#parse_an_element_for_class_microformats
59
+ # microformats.org: microformats2 parsing specification § Parse an element for class microformats
19
60
  #
20
61
  # @return [MicroMicro::Collections::ItemsCollection]
21
62
  def children
22
- @children ||= Collections::ItemsCollection.new(Item.items_from(node.element_children))
63
+ @children ||= Collections::ItemsCollection.new(self.class.from_context(node.element_children))
23
64
  end
24
65
 
25
- # The value of the node's `id` attribute, if present.
66
+ # Does this {MicroMicro::Item} contain any child {MicroMicro::Item}s?
67
+ #
68
+ # @return [Boolean]
69
+ def children?
70
+ children.any?
71
+ end
72
+
73
+ # The value of the node's +id+ attribute, if present.
26
74
  #
27
75
  # @return [String, nil]
28
76
  def id
29
77
  @id ||= node['id']&.strip
30
78
  end
31
79
 
32
- # @return [String]
33
- def inspect
34
- format(%(#<#{self.class.name}:%#0x types: #{types.inspect}, properties: #{properties.count}, children: #{children.count}>), object_id)
80
+ # Does this {MicroMicro::Item} have an +id+ attribute value?
81
+ #
82
+ # @return [Boolean]
83
+ def id?
84
+ id.present?
35
85
  end
36
86
 
37
- # A collection of plain text properties parsed from the node.
87
+ # @return [String]
38
88
  #
39
- # @return [MicroMicro::Collections::PropertiesCollection]
40
- def plain_text_properties
41
- @plain_text_properties ||= Collections::PropertiesCollection.new(properties.select { |property| property.prefix == 'p' })
89
+ # :nocov:
90
+ def inspect
91
+ "#<#{self.class}:#{format('%#0x', object_id)} " \
92
+ "types: #{types.inspect}, " \
93
+ "properties: #{properties.count}, " \
94
+ "children: #{children.count}>"
42
95
  end
96
+ # :nocov:
43
97
 
44
- # A collection of properties parsed from the node.
98
+ # A collection of {MicroMicro::Property}s parsed from the node.
45
99
  #
46
100
  # @return [MicroMicro::Collections::PropertiesCollection]
47
101
  def properties
48
- @properties ||= Collections::PropertiesCollection.new(Property.properties_from(node.element_children))
102
+ @properties ||= Collections::PropertiesCollection.new(Property.from_context(node.element_children))
49
103
  end
50
104
 
51
- # Return the parsed item as a Hash.
105
+ # Return the parsed {MicroMicro::Item} as a Hash.
52
106
  #
53
107
  # @see https://microformats.org/wiki/microformats2-parsing#parse_an_element_for_class_microformats
108
+ # microformats.org: microformats2 parsing specification § Parse an element for class microformats
109
+ #
110
+ # @see MicroMicro::Item#children
111
+ # @see MicroMicro::Item#id
112
+ # @see MicroMicro::Item#properties
113
+ # @see MicroMicro::Item#types
114
+ # @see MicroMicro::Collections::PropertiesCollection#to_h
54
115
  #
55
116
  # @return [Hash]
56
117
  def to_h
@@ -59,81 +120,27 @@ module MicroMicro
59
120
  properties: properties.to_h
60
121
  }
61
122
 
62
- hash[:id] = id if id.present?
63
- hash[:children] = children.to_a if children.any?
123
+ hash[:id] = id if id?
124
+ hash[:children] = children.to_a if children?
64
125
 
65
126
  hash
66
127
  end
67
128
 
68
- # An array of root class names parsed from the node's `class` attribute.
129
+ # An Array of root class names parsed from the node's +class+ attribute.
69
130
  #
70
131
  # @return [Array<String>]
71
132
  def types
72
- @types ||= self.class.types_from(node)
73
- end
74
-
75
- # A collection of url properties parsed from the node.
76
- #
77
- # @return [MicroMicro::Collections::PropertiesCollection]
78
- def url_properties
79
- @url_properties ||= Collections::PropertiesCollection.new(properties.select { |property| property.prefix == 'u' })
80
- end
81
-
82
- # Does this node's `class` attribute contain root class names?
83
- #
84
- # @param node [Nokogiri::XML::Element]
85
- # @return [Boolean]
86
- def self.item_node?(node)
87
- types_from(node).any?
88
- end
89
-
90
- # Extract items from a context.
91
- #
92
- # @param context [Nokogiri::HTML::Document, Nokogiri::XML::NodeSet, Nokogiri::XML::Element]
93
- # @return [Array<MicroMicro::Item>]
94
- def self.items_from(context)
95
- nodes_from(context).map { |node| new(node) }
96
- end
97
-
98
- # Extract item nodes from a context.
99
- #
100
- # @param context [Nokogiri::HTML::Document, Nokogiri::XML::NodeSet, Nokogiri::XML::Element]
101
- # @param node_set [Nokogiri::XML::NodeSet]
102
- # @return [Nokogiri::XML::NodeSet]
103
- def self.nodes_from(context, node_set = Nokogiri::XML::NodeSet.new(context.document, []))
104
- return nodes_from(context.element_children, node_set) if context.is_a?(Nokogiri::HTML::Document)
105
-
106
- context.each { |node| nodes_from(node, node_set) } if context.is_a?(Nokogiri::XML::NodeSet)
107
-
108
- if context.is_a?(Nokogiri::XML::Element) && !Document.ignore_node?(context)
109
- if item_node?(context)
110
- node_set << context unless Property.property_node?(context)
111
- else
112
- nodes_from(context.element_children, node_set)
113
- end
114
- end
115
-
116
- node_set
117
- end
118
-
119
- # Extract root class names from a node.
120
- #
121
- # node = Nokogiri::HTML('<div class="h-card">Jason Garber</div>').at_css('div')
122
- # MicroMicro::Item.types_from(node) #=> ['h-card']
123
- #
124
- # @param node [Nokogiri::XML::Element]
125
- # @return [Array<String>]
126
- def self.types_from(node)
127
- node.classes.select { |token| token.match?(/^h(?:-[0-9a-z]+)?(?:-[a-z]+)+$/) }.uniq.sort
133
+ @types ||= Helpers.root_class_names_from(node)
128
134
  end
129
135
 
130
136
  private
131
137
 
138
+ # @return [Nokogiri::XML::Element]
132
139
  attr_reader :node
133
140
 
134
141
  # @return [MicroMicro::ImpliedProperty]
135
142
  def implied_name
136
- @implied_name ||= ImpliedProperty.new(node, name: 'name', prefix: 'p')
143
+ @implied_name ||= ImpliedProperty.new(node, 'p-name')
137
144
  end
138
145
 
139
146
  # @return [Boolean]
@@ -143,7 +150,7 @@ module MicroMicro
143
150
 
144
151
  # @return [MicroMicro::ImpliedProperty]
145
152
  def implied_photo
146
- @implied_photo ||= ImpliedProperty.new(node, name: 'photo', prefix: 'u')
153
+ @implied_photo ||= ImpliedProperty.new(node, 'u-photo')
147
154
  end
148
155
 
149
156
  # @return [Boolean]
@@ -153,7 +160,7 @@ module MicroMicro
153
160
 
154
161
  # @return [MicroMicro::ImpliedProperty]
155
162
  def implied_url
156
- @implied_url ||= ImpliedProperty.new(node, name: 'url', prefix: 'u')
163
+ @implied_url ||= ImpliedProperty.new(node, 'u-url')
157
164
  end
158
165
 
159
166
  # @return [Boolean]
@@ -163,22 +170,29 @@ module MicroMicro
163
170
 
164
171
  # @return [Boolean]
165
172
  def imply_name?
166
- properties.none? { |prop| prop.name == 'name' } && properties.none? { |prop| %w[e p].include?(prop.prefix) } && !nested_items?
173
+ properties.names.none?('name') &&
174
+ properties.none?(&:embedded_markup_property?) &&
175
+ properties.none?(&:plain_text_property?) &&
176
+ !nested_items?
167
177
  end
168
178
 
169
179
  # @return [Boolean]
170
180
  def imply_photo?
171
- properties.none? { |prop| prop.name == 'photo' } && properties.reject(&:implied?).none? { |prop| prop.prefix == 'u' } && !nested_items?
181
+ properties.names.none?('photo') &&
182
+ properties.reject(&:implied?).none?(&:url_property?) &&
183
+ !nested_items?
172
184
  end
173
185
 
174
186
  # @return [Boolean]
175
187
  def imply_url?
176
- properties.none? { |prop| prop.name == 'url' } && properties.reject(&:implied?).none? { |prop| prop.prefix == 'u' } && !nested_items?
188
+ properties.names.none?('url') &&
189
+ properties.reject(&:implied?).none?(&:url_property?) &&
190
+ !nested_items?
177
191
  end
178
192
 
179
193
  # @return [Boolean]
180
194
  def nested_items?
181
- @nested_items ||= properties.find(&:item_node?) || children.any?
195
+ @nested_items ||= properties.find(&:item_node?) || children?
182
196
  end
183
197
  end
184
198
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MicroMicro
4
+ module Parsers
5
+ class BaseImpliedPropertyParser < BasePropertyParser
6
+ private
7
+
8
+ # @return [String, nil]
9
+ def attribute_value
10
+ candidate_node[self.class::HTML_ELEMENTS_MAP[candidate_node.name]] if candidate_node
11
+ end
12
+
13
+ # @return [Nokogiri::XML::Element, nil]
14
+ def candidate_node
15
+ @candidate_node ||=
16
+ candidate_nodes.find do |node|
17
+ self.class::HTML_ELEMENTS_MAP.filter_map do |name, attribute|
18
+ node if name == node.name && node[attribute]
19
+ end.any?
20
+ end
21
+ end
22
+
23
+ # @return [Nokogiri::XML::NodeSet]
24
+ def candidate_nodes
25
+ Nokogiri::XML::NodeSet.new(node.document, child_nodes.unshift(node))
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  module Parsers
3
5
  class BasePropertyParser
@@ -8,24 +10,16 @@ module MicroMicro
8
10
  end
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
  # @see https://microformats.org/wiki/microformats2-parsing#parsing_an_e-_property
15
+ # microformats.org: microformats2 parsing specification § Parsing an +e-+ property
12
16
  #
13
17
  # @return [String]
14
18
  def value
15
- @value ||= begin
16
- Document.text_content_from(node) do |context|
19
+ @value ||=
20
+ Helpers.text_content_from(node) do |context|
17
21
  context.css('img').each { |img| img.content = " #{img['alt'] || img['src']} " }
18
22
  end
19
- end
20
- end
21
-
22
- # @param node [Nokogiri::XML::Element]
23
- # @param attributes_map [Hash{String => Array}]
24
- # @return [Array]
25
- def self.attribute_value_from(node, attributes_map)
26
- attributes_map.map do |attribute, names|
27
- node[attribute] if names.include?(node.name) && node[attribute]
28
- end.compact.first
29
23
  end
30
24
 
31
25
  private