# ~*~ encoding: utf-8 ~*~ # Render all tags (things in double-square-brackets). This one's a biggie. class Gollum::Filter::Tags < Gollum::Filter # Extract all tags into the tagmap and replace with placeholders. def extract(data) data.gsub!(/(.?)\[\[(.+?)\]\]([^\[]?)/) do if Regexp.last_match[1] == "'" && Regexp.last_match[3] != "'" "[[#{Regexp.last_match[2]}]]#{Regexp.last_match[3]}" elsif Regexp.last_match[2].include?('][') if Regexp.last_match[2][0..4] == 'file:' pre = Regexp.last_match[1] post = Regexp.last_match[3] parts = Regexp.last_match[2].split('][') parts[0][0..4] = "" link = "#{parts[1]}|#{parts[0].sub(/\.org/, '')}" id = register_tag(link) "#{pre}#{id}#{post}" else Regexp.last_match[0] end else id = register_tag(Regexp.last_match[2]) "#{Regexp.last_match[1]}#{id}#{Regexp.last_match[3]}" end end data end # Process all text nodes from the doc and replace the placeholders with the # final markup. def process(rendered_data) doc = Nokogiri::HTML::DocumentFragment.parse(rendered_data) doc.traverse do |node| if node.text? then content = node.content content.gsub!(%r{#{open_pattern}[a-f0-9]+#{close_pattern}}) do |id| if (tag = @map[id]) then if is_preformatted?(node) then "[[#{tag}]]" else process_tag(tag).gsub('%2f', '/') end end end node.replace(content) if content != node.content end end doc.to_html end private PREFORMATTED_TAGS = %w(code tt) INCLUDE_TAG = 'include:' def register_tag(tag) id = "#{open_pattern}#{Digest::SHA1.hexdigest(tag)}#{close_pattern}" @map[id] = tag id end def is_preformatted?(node) node && (PREFORMATTED_TAGS.include?(node.name) || node.ancestors.any? { |a| PREFORMATTED_TAGS.include?(a.name) }) end # Process a single tag into its final HTML form. # # tag - The String tag contents (the stuff inside the double # brackets). # # Returns the String HTML version of the tag. def process_tag(tag) link_part, extra = parse_tag_parts(tag) return generate_link('', nil, nil, :page_absent) if link_part.nil? img_args = extra ? [extra, link_part] : [link_part] mime = MIME::Types.type_for(::File.extname(img_args.first.to_s)).first result = if tag =~ /^_TOC_/ %{[[#{tag}]]} elsif link_part =~ /^_$/ %{
} elsif link_part =~ /^#{INCLUDE_TAG}/ process_include_tag(link_part) elsif mime && mime.content_type =~ /^image/ process_image_tag(*img_args) elsif external = process_external_link_tag(link_part, extra) external end result ? result : process_link_tag(link_part, extra) end # Process the tag parts as an internal link to a File or Page. def process_link_tag(link_part, pretty_name) process_file_link_tag(link_part, pretty_name) || process_page_link_tag(link_part, pretty_name) end # Parse the tag (stuff between the double brackets) into a link part and additional information (a pretty name, description, or image options). # # tag - The String tag contents (the stuff inside the double # brackets). # # Returns an Array of the form [link_part, extra], where both elements are Strings and the second element may be nil. def parse_tag_parts(tag) parts = tag.split('|').map(&:strip)[0..1] parts.reverse! if @markup.reverse_links? if parts[1] return parts[1], parts[0] else return parts[0], nil end end # Attempt to process the tag as an include tag # # tag - The String tag contents (the stuff inside the double brackets). # # Returns the String HTML if the tag includes a valid page or an error message if the page could not be found. def process_include_tag(tag) len = INCLUDE_TAG.length return html_error('Cannot process include directive: no page name given') if tag.length <= len page_name = tag[len..-1] resolved_page_name = ::File.join(@markup.dir, page_name) if @markup.include_levels > 0 page = find_page_or_file_from_path(resolved_page_name) if page page.formatted_data(@markup.encoding, @markup.include_levels-1) else html_error("Cannot include #{process_page_link_tag(resolved_page_name)} - does not exist yet") end else html_error("Too many levels of included pages, will not include #{process_page_link_tag(resolved_page_name)}") end end # Attempt to process the tag as an image tag. # # path - The String path to the image. # options - The String of options for the image (the stuff after the '|'). Optional. # # Returns the String HTML if the tag is a valid image tag or nil # if it is not. def process_image_tag(path, options = nil) opts = parse_image_tag_options(options) if path =~ /^https?:\/\/.+$/i generate_image(path, opts) elsif file = find_page_or_file_from_path(path, :file) generate_image(generate_href_for_path(file.url_path), opts) else generate_image('', opts) end end # Parse any options present on the image tag (comma separated) and extract them into a # Hash of option names and values. # # options - The String image options (the stuff in the after '|'). # # Returns the options Hash: # key - The String option name. # val - The String option value or true if it is a binary option. def parse_image_tag_options(options) return {} if options.nil? options.split(',').inject({}) do |memo, attr| parts = attr.split('=').map { |x| x.strip } memo[parts[0].to_sym] = (parts.size == 1 ? true : parts[1]) memo end end # Return the String HTML if the tag is a valid external link tag or # nil if it is not. def process_external_link_tag(url, pretty_name = nil) @accepted_protocols_regex ||= %r{^((#{::Gollum::Sanitization.accepted_protocols.join('|')}):)?(//)} if url =~ @accepted_protocols_regex generate_link(url, pretty_name, nil, :external) else nil end end # Attempt to process the tag as a file link tag. # # link_part - The String part of the tag containing the link # pretty_name - The String name for the link (optional) # # Returns the String HTML if the tag is a valid file link tag or nil # if it is not. def process_file_link_tag(link_part, pretty_name) return nil if ::Gollum::Page.valid_extension?(link_part) if file = find_page_or_file_from_path(link_part, :file) generate_link(file.url_path, pretty_name, nil, :file) else nil end end # Attempt to process the tag as a page link tag. # # link_part - The String part of the tag containing the link # pretty_name - The String name for the link (optional) # # Returns the String HTML if the tag is a valid page link tag or nil # if it is not. def process_page_link_tag(link_part, pretty_name = nil) presence = :page_absent link = link_part page = find_page_or_file_from_path(link) # If no match yet, try finding page with anchor removed if page.nil? if pos = link.rindex('#') extra = link[pos..-1] link = link[0...pos] else extra = nil end if link.empty? && extra # Internal anchor link, don't search for the page but return immediately return generate_link(nil, pretty_name, extra, :internal_anchor) end page = find_page_or_file_from_path(link) end presence = :page_present if page if pretty_name name = pretty_name else name = page ? path_to_link_text(link) : link end link = page ? page.escaped_url_path : ERB::Util.url_encode(link).force_encoding('utf-8') generate_link(link, name, extra, presence) end # Find a page from a given path # # path - The String path to search for. # # Returns a Gollum::Page instance if a page is found, or nil otherwise def find_page_or_file_from_path(path, kind = :page) if Pathname.new(path).relative? result = @markup.wiki.send(kind, ::File.join(@markup.dir, path)) if result.nil? && @markup.wiki.global_tag_lookup # 4.x link compatibility option. Slow! result = @markup.wiki.send(kind, path, nil, true) end result else @markup.wiki.send(kind, path) end end # Generate an HTML link tag. # # path - The String path (href) to construct a link to. # name - The String name of the link (text inside the link tag). Optional. # extra - The String anchor to add the link. Optional. # kind - A Symbol indicating whether this is a Page, File, or External link. # # Returns a String HTML link tag. def generate_link(path, name = nil, extra = nil, kind = nil) url = kind == :external ? path : generate_href_for_path(path, extra) %{#{name || path}} end # Generate a normalized href for a path, taking into consideration the wiki's path settings. # # path - The String path to generate an href for. # extra - The String anchor to add to the href. Optional. # # Returns a String href. def generate_href_for_path(path, extra = nil) return extra if !path && extra # Internal anchor link "#{trim_leading_slashes(::File.join(@markup.wiki.base_path, path))}#{extra}" end # Construct a CSS class and attribute string for different kinds of links: internal Pages (absent or present) and Files, and External links. # # kind - The Symbol indicating the kind of link. Can be one of: :page_absent, :page_present, :file, :external. # # Returns the String CSS class and attributes. def css_options_for_link(kind) case kind when :page_absent 'class="internal absent"' when :page_present 'class="internal present"' when :internal_anchor 'class="internal anchorlink"' when :file nil when :external nil else nil end end # Generate an HTML image tag. # # path - The String path (href) of the image. # options - The Hash of parsed image options. # # Returns a String HTML img tag. def generate_image(path, options = nil) classes, attrs, containered = generate_image_attributes(options) attrs[:alt] = 'Image not found' if path.empty? attr_string = attrs.map {|key, value| "#{key}=\"#{value}\""}.join(' ') if containered %{} + %{} + %{