123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 |
- # frozen_string_literal: true
- class TOCGenerator
- TARGET_ELEMENTS = %w(h1 h2 h3 h4 h5 h6).freeze
- LISTED_ELEMENTS = %w(h2 h3).freeze
- class Section
- attr_accessor :depth, :title, :children, :anchor
- def initialize(depth, title, anchor)
- @depth = depth
- @title = title
- @children = []
- @anchor = anchor
- end
- delegate :<<, to: :children
- end
- def initialize(source_html)
- @source_html = source_html
- @processed = false
- @target_html = ''
- @headers = []
- @slugs = Hash.new { |h, k| h[k] = 0 }
- end
- def html
- parse_and_transform unless @processed
- @target_html
- end
- def toc
- parse_and_transform unless @processed
- @headers
- end
- private
- def parse_and_transform
- return if @source_html.blank?
- parsed_html = Nokogiri::HTML.fragment(@source_html)
- parsed_html.traverse do |node|
- next unless TARGET_ELEMENTS.include?(node.name)
- anchor = node['id'] || node.text.parameterize.presence || 'sec'
- @slugs[anchor] += 1
- anchor = "#{anchor}-#{@slugs[anchor]}" if @slugs[anchor] > 1
- node['id'] = anchor
- next unless LISTED_ELEMENTS.include?(node.name)
- depth = node.name[1..-1]
- latest_section = @headers.last
- if latest_section.nil? || latest_section.depth >= depth
- @headers << Section.new(depth, node.text, anchor)
- else
- latest_section << Section.new(depth, node.text, anchor)
- end
- end
- @target_html = parsed_html.to_s
- @processed = true
- end
- end
|