lib/kramdown/parser/kramdown/link.rb



# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2012 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#++
#

module Kramdown
  module Parser
    class Kramdown

      # Normalize the link identifier.
      def normalize_link_id(id)
        id.gsub(/(\s|\n)+/, ' ').downcase
      end

      LINK_DEFINITION_START = /^#{OPT_SPACE}\[([^\n\]]+)\]:[ \t]*(?:<(.*?)>|([^'"\n]*?\S[^'"\n]*?))[ \t]*?(?:\n?[ \t]*?(["'])(.+?)\4[ \t]*?)?\n/

      # Parse the link definition at the current location.
      def parse_link_definition
        @src.pos += @src.matched_size
        link_id, link_url, link_title = normalize_link_id(@src[1]), @src[2] || @src[3], @src[5]
        warning("Duplicate link ID '#{link_id}' - overwriting") if @link_defs[link_id]
        @link_defs[link_id] = [link_url, link_title]
        @tree.children << Element.new(:eob, :link_def)
        true
      end
      define_parser(:link_definition, LINK_DEFINITION_START)


      # This helper methods adds the approriate attributes to the element +el+ of type +a+ or +img+
      # and the element itself to the @tree.
      def add_link(el, href, title, alt_text = nil)
        if el.type == :a
          el.attr['href'] = href
        else
          el.attr['src'] = href
          el.attr['alt'] = alt_text
          el.children.clear
        end
        el.attr['title'] = title if title
        @tree.children << el
      end

      LINK_BRACKET_STOP_RE = /(\])|!?\[/
      LINK_PAREN_STOP_RE = /(\()|(\))|\s(?=['"])/
      LINK_INLINE_ID_RE = /\s*?\[([^\]]+)?\]/
      LINK_INLINE_TITLE_RE = /\s*?(["'])(.+?)\1\s*?\)/m
      LINK_START = /!?\[(?=[^^])/

      # Parse the link at the current scanner position. This method is used to parse normal links as
      # well as image links.
      def parse_link
        result = @src.scan(LINK_START)
        reset_pos = @src.pos

        link_type = (result =~ /^!/ ? :img : :a)

        # no nested links allowed
        if link_type == :a && (@tree.type == :img || @tree.type == :a || @stack.any? {|t,s| t && (t.type == :img || t.type == :a)})
          add_text(result)
          return
        end
        el = Element.new(link_type)

        count = 1
        found = parse_spans(el, LINK_BRACKET_STOP_RE) do
          count = count + (@src[1] ? -1 : 1)
          count - el.children.select {|c| c.type == :img}.size == 0
        end
        if !found || (link_type == :a && el.children.empty?)
          @src.pos = reset_pos
          add_text(result)
          return
        end
        alt_text = extract_string(reset_pos...@src.pos, @src)
        @src.scan(LINK_BRACKET_STOP_RE)

        # reference style link or no link url
        if @src.scan(LINK_INLINE_ID_RE) || !@src.check(/\(/)
          link_id = normalize_link_id(@src[1] || alt_text)
          if @link_defs.has_key?(link_id)
            add_link(el, @link_defs[link_id].first, @link_defs[link_id].last, alt_text)
          else
            warning("No link definition for link ID '#{link_id}' found")
            @src.pos = reset_pos
            add_text(result)
          end
          return
        end

        # link url in parentheses
        if @src.scan(/\(<(.*?)>/)
          link_url = @src[1]
          if @src.scan(/\)/)
            add_link(el, link_url, nil, alt_text)
            return
          end
        else
          link_url = ''
          nr_of_brackets = 0
          while temp = @src.scan_until(LINK_PAREN_STOP_RE)
            link_url << temp
            if @src[2]
              nr_of_brackets -= 1
              break if nr_of_brackets == 0
            elsif @src[1]
              nr_of_brackets += 1
            else
              break
            end
          end
          link_url = link_url[1..-2]
          link_url.strip!

          if nr_of_brackets == 0
            add_link(el, link_url, nil, alt_text)
            return
          end
        end

        if @src.scan(LINK_INLINE_TITLE_RE)
          add_link(el, link_url, @src[2], alt_text)
        else
          @src.pos = reset_pos
          add_text(result)
        end
      end
      define_parser(:link, LINK_START, '!?\[')

    end
  end
end