|
|
|
@ -4,37 +4,33 @@ class HtmlTree
|
|
|
|
|
include Enumerable
|
|
|
|
|
|
|
|
|
|
def initialize(html)
|
|
|
|
|
puts "Input HTML:\n#{html.inspect}"
|
|
|
|
|
@root = parse_html(html)
|
|
|
|
|
raise "Parsed HTML tree is empty" if @root.nil?
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def each(order = :breadth_first, &block)
|
|
|
|
|
return enum_for(:each, order) unless block_given?
|
|
|
|
|
|
|
|
|
|
case order
|
|
|
|
|
when :breadth_first
|
|
|
|
|
breadth_first_traversal(&block)
|
|
|
|
|
queue = [@root].compact
|
|
|
|
|
until queue.empty?
|
|
|
|
|
current = queue.shift
|
|
|
|
|
yield current
|
|
|
|
|
queue.concat(current.children) if current.has_children?
|
|
|
|
|
end
|
|
|
|
|
when :depth_first
|
|
|
|
|
depth_first_traversal(&block)
|
|
|
|
|
stack = [@root].compact
|
|
|
|
|
until stack.empty?
|
|
|
|
|
current = stack.pop
|
|
|
|
|
yield current
|
|
|
|
|
stack.concat(current.children.reverse) if current.has_children?
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
raise ArgumentError, "Unknown order: #{order}"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def select(order = :breadth_first, &block)
|
|
|
|
|
results = []
|
|
|
|
|
each(order) do |node|
|
|
|
|
|
results << node if block.call(node)
|
|
|
|
|
end
|
|
|
|
|
results
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def reduce(accumulator = nil, order = :breadth_first, &block)
|
|
|
|
|
each(order) do |node|
|
|
|
|
|
accumulator = block.call(accumulator, node)
|
|
|
|
|
end
|
|
|
|
|
accumulator
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
|
|
def parse_html(html)
|
|
|
|
@ -53,24 +49,6 @@ class HtmlTree
|
|
|
|
|
HtmlTag.new("text", { "content" => node.content.strip }, [])
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def breadth_first_traversal
|
|
|
|
|
queue = [@root].compact
|
|
|
|
|
until queue.empty?
|
|
|
|
|
current = queue.shift
|
|
|
|
|
next if current.nil?
|
|
|
|
|
|
|
|
|
|
yield current
|
|
|
|
|
queue.concat(current.children.compact) if current.has_children?
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def depth_first_traversal(node = @root, &block)
|
|
|
|
|
return if node.nil?
|
|
|
|
|
|
|
|
|
|
yield node
|
|
|
|
|
node.children.compact.each { |child| depth_first_traversal(child, &block) } if node.has_children?
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
class HtmlTag
|
|
|
|
@ -87,7 +65,8 @@ class HtmlTag
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def to_s
|
|
|
|
|
"#{@name} #{@attributes}"
|
|
|
|
|
attr_str = attributes.map { |k, v| "#{k}=#{v.inspect}" }.join(", ")
|
|
|
|
|
"#{@name}(#{attr_str})"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
@ -103,16 +82,16 @@ HTML
|
|
|
|
|
|
|
|
|
|
tree = HtmlTree.new(html)
|
|
|
|
|
|
|
|
|
|
puts "Traversal in breadth-first order:"
|
|
|
|
|
puts "=== BFS Traversal ==="
|
|
|
|
|
tree.each(:breadth_first) { |tag| puts tag }
|
|
|
|
|
|
|
|
|
|
puts "\nTraversal in depth-first order:"
|
|
|
|
|
puts "\n=== DFS Traversal ==="
|
|
|
|
|
tree.each(:depth_first) { |tag| puts tag }
|
|
|
|
|
|
|
|
|
|
puts "Select tags with class 'text':"
|
|
|
|
|
selected_tags = tree.select { |tag| tag.attributes["class"] == "text" }
|
|
|
|
|
puts selected_tags
|
|
|
|
|
puts "\n=== Select Example (BFS) ==="
|
|
|
|
|
selected = tree.each(:breadth_first).select { |tag| tag.attributes["class"] == "text" }
|
|
|
|
|
puts "Selected tags: #{selected}"
|
|
|
|
|
|
|
|
|
|
puts "\nReduce example (concatenate all tag names):"
|
|
|
|
|
reduced_value = tree.reduce("") { |acc, tag| acc + " " + tag.name }
|
|
|
|
|
puts reduced_value.strip
|
|
|
|
|
puts "\n=== Reduce Example (DFS) ==="
|
|
|
|
|
reduced_value = tree.each(:depth_first).reduce("") { |acc, tag| acc + " " + tag.name }
|
|
|
|
|
puts "Reduced result: #{reduced_value.strip}"
|