This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
# -*- coding: utf-8; frozen_string_literal: true -*-
|
||||
#
|
||||
#--
|
||||
# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
|
||||
#
|
||||
# This file is part of kramdown which is licensed under the MIT.
|
||||
#++
|
||||
#
|
||||
|
||||
module Kramdown
|
||||
module Utils
|
||||
|
||||
# Methods for registering configurable extensions.
|
||||
module Configurable
|
||||
|
||||
# Create a new configurable extension called +name+.
|
||||
#
|
||||
# Three methods will be defined on the calling object which allow to use this configurable
|
||||
# extension:
|
||||
#
|
||||
# configurables:: Returns a hash of hashes that is used to store all configurables of the
|
||||
# object.
|
||||
#
|
||||
# <name>(ext_name):: Return the configured extension +ext_name+.
|
||||
#
|
||||
# add_<name>(ext_name, data=nil, &block):: Define an extension +ext_name+ by specifying either
|
||||
# the data as argument or by using a block.
|
||||
def configurable(name)
|
||||
unless respond_to?(:configurables)
|
||||
singleton_class.send(:define_method, :configurables) do
|
||||
@_configurables ||= Hash.new {|h, k| h[k] = {} }
|
||||
end
|
||||
end
|
||||
singleton_class.send(:define_method, name) do |data|
|
||||
configurables[name][data]
|
||||
end
|
||||
singleton_class.send(:define_method, "add_#{name}".intern) do |data, *args, &block|
|
||||
configurables[name][data] = args.first || block
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8; frozen_string_literal: true -*-
|
||||
#
|
||||
#--
|
||||
# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
|
||||
#
|
||||
# This file is part of kramdown which is licensed under the MIT.
|
||||
#++
|
||||
#
|
||||
|
||||
require 'rexml/parsers/baseparser'
|
||||
|
||||
module Kramdown
|
||||
|
||||
module Utils
|
||||
|
||||
# Provides convenience methods for HTML related tasks.
|
||||
#
|
||||
# *Note* that this module has to be mixed into a class that has a @root (containing an element
|
||||
# of type :root) and an @options (containing an options hash) instance variable so that some of
|
||||
# the methods can work correctly.
|
||||
module Html
|
||||
|
||||
# Convert the entity +e+ to a string. The optional parameter +original+ may contain the
|
||||
# original representation of the entity.
|
||||
#
|
||||
# This method uses the option +entity_output+ to determine the output form for the entity.
|
||||
def entity_to_str(e, original = nil)
|
||||
entity_output = @options[:entity_output]
|
||||
|
||||
if entity_output == :as_char &&
|
||||
(c = e.char.encode(@root.options[:encoding]) rescue nil) &&
|
||||
((c = e.char) == '"' || !ESCAPE_MAP.key?(c))
|
||||
c
|
||||
elsif (entity_output == :as_input || entity_output == :as_char) && original
|
||||
original
|
||||
elsif (entity_output == :symbolic || ESCAPE_MAP.key?(e.char)) && !e.name.nil?
|
||||
"&#{e.name};"
|
||||
else # default to :numeric
|
||||
"&##{e.code_point};"
|
||||
end
|
||||
end
|
||||
|
||||
# Return the HTML representation of the attributes +attr+.
|
||||
def html_attributes(attr)
|
||||
return '' if attr.empty?
|
||||
|
||||
attr.map do |k, v|
|
||||
v.nil? || (k == 'id' && v.strip.empty?) ? '' : " #{k}=\"#{escape_html(v.to_s, :attribute)}\""
|
||||
end.join
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
ESCAPE_MAP = {
|
||||
'<' => '<',
|
||||
'>' => '>',
|
||||
'&' => '&',
|
||||
'"' => '"',
|
||||
}
|
||||
ESCAPE_ALL_RE = /<|>|&/
|
||||
ESCAPE_TEXT_RE = Regexp.union(REXML::Parsers::BaseParser::REFERENCE_RE, /<|>|&/)
|
||||
ESCAPE_ATTRIBUTE_RE = Regexp.union(REXML::Parsers::BaseParser::REFERENCE_RE, /<|>|&|"/)
|
||||
ESCAPE_RE_FROM_TYPE = {all: ESCAPE_ALL_RE, text: ESCAPE_TEXT_RE, attribute: ESCAPE_ATTRIBUTE_RE}
|
||||
# :startdoc:
|
||||
|
||||
# Escape the special HTML characters in the string +str+. The parameter +type+ specifies what
|
||||
# is escaped: :all - all special HTML characters except the quotation mark as well as
|
||||
# entities, :text - all special HTML characters except the quotation mark but no entities and
|
||||
# :attribute - all special HTML characters including the quotation mark but no entities.
|
||||
def escape_html(str, type = :all)
|
||||
str.gsub(ESCAPE_RE_FROM_TYPE[type]) {|m| ESCAPE_MAP[m] || m }
|
||||
end
|
||||
|
||||
REDUNDANT_LINE_BREAK_REGEX = /([\p{Han}\p{Hiragana}\p{Katakana}]+)\n([\p{Han}\p{Hiragana}\p{Katakana}]+)/u
|
||||
def fix_cjk_line_break(str)
|
||||
while str.gsub!(REDUNDANT_LINE_BREAK_REGEX, '\1\2')
|
||||
end
|
||||
str
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8; frozen_string_literal: true -*-
|
||||
#
|
||||
#--
|
||||
# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
|
||||
#
|
||||
# This file is part of kramdown which is licensed under the MIT.
|
||||
#++
|
||||
#
|
||||
|
||||
module Kramdown
|
||||
module Utils
|
||||
|
||||
# A simple least recently used (LRU) cache.
|
||||
#
|
||||
# The cache relies on the fact that Ruby's Hash class maintains insertion order. So deleting
|
||||
# and re-inserting a key-value pair on access moves the key to the last position. When an
|
||||
# entry is added and the cache is full, the first entry is removed.
|
||||
class LRUCache
|
||||
|
||||
# Creates a new LRUCache that can hold +size+ entries.
|
||||
def initialize(size)
|
||||
@size = size
|
||||
@cache = {}
|
||||
end
|
||||
|
||||
# Returns the stored value for +key+ or +nil+ if no value was stored under the key.
|
||||
def [](key)
|
||||
(val = @cache.delete(key)).nil? ? nil : @cache[key] = val
|
||||
end
|
||||
|
||||
# Stores the +value+ under the +key+.
|
||||
def []=(key, value)
|
||||
@cache.delete(key)
|
||||
@cache[key] = value
|
||||
@cache.shift if @cache.length > @size
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8; frozen_string_literal: true -*-
|
||||
#
|
||||
#--
|
||||
# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
|
||||
#
|
||||
# This file is part of kramdown which is licensed under the MIT.
|
||||
#++
|
||||
#
|
||||
|
||||
require 'strscan'
|
||||
|
||||
module Kramdown
|
||||
module Utils
|
||||
|
||||
# This patched StringScanner adds line number information for current scan position and a
|
||||
# start_line_number override for nested StringScanners.
|
||||
class StringScanner < ::StringScanner
|
||||
|
||||
# The start line number. Used for nested StringScanners that scan a sub-string of the source
|
||||
# document. The kramdown parser uses this, e.g., for span level parsers.
|
||||
attr_reader :start_line_number
|
||||
|
||||
# Takes the start line number as optional second argument.
|
||||
#
|
||||
# Note: The original second argument is no longer used so this should be safe.
|
||||
def initialize(string, start_line_number = 1)
|
||||
super(string)
|
||||
@start_line_number = start_line_number || 1
|
||||
@previous_pos = 0
|
||||
@previous_line_number = @start_line_number
|
||||
end
|
||||
|
||||
# Sets the byte position of the scan pointer.
|
||||
#
|
||||
# Note: This also resets some internal variables, so always use pos= when setting the position
|
||||
# and don't use any other method for that!
|
||||
def pos=(pos)
|
||||
if self.pos > pos
|
||||
@previous_line_number = @start_line_number
|
||||
@previous_pos = 0
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
# Return information needed to revert the byte position of the string scanner in a performant
|
||||
# way.
|
||||
#
|
||||
# The returned data can be fed to #revert_pos to revert the position to the saved one.
|
||||
#
|
||||
# Note: Just saving #pos won't be enough.
|
||||
def save_pos
|
||||
[pos, @previous_pos, @previous_line_number]
|
||||
end
|
||||
|
||||
# Revert the position to one saved by #save_pos.
|
||||
def revert_pos(data)
|
||||
self.pos = data[0]
|
||||
@previous_pos, @previous_line_number = data[1], data[2]
|
||||
end
|
||||
|
||||
# Returns the line number for current charpos.
|
||||
#
|
||||
# NOTE: Requires that all line endings are normalized to '\n'
|
||||
#
|
||||
# NOTE: Normally we'd have to add one to the count of newlines to get the correct line number.
|
||||
# However we add the one indirectly by using a one-based start_line_number.
|
||||
def current_line_number
|
||||
# Not using string[@previous_pos..best_pos].count('\n') because it is slower
|
||||
strscan = ::StringScanner.new(string)
|
||||
strscan.pos = @previous_pos
|
||||
old_pos = pos + 1
|
||||
@previous_line_number += 1 while strscan.skip_until(/\n/) && strscan.pos <= old_pos
|
||||
|
||||
@previous_pos = (eos? ? pos : pos + 1)
|
||||
@previous_line_number
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
# -*- coding: utf-8; frozen_string_literal: true -*-
|
||||
#
|
||||
#--
|
||||
# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
|
||||
#
|
||||
# This file is part of kramdown which is licensed under the MIT.
|
||||
#++
|
||||
#
|
||||
# This file is based on code originally from the Stringex library and needs the data files from
|
||||
# Stringex to work correctly.
|
||||
|
||||
module Kramdown
|
||||
module Utils
|
||||
|
||||
# Provides the ability to tranliterate Unicode strings into plain ASCII ones.
|
||||
module Unidecoder
|
||||
|
||||
gem 'stringex'
|
||||
path = $:.find do |dir|
|
||||
File.directory?(File.join(File.expand_path(dir), "stringex", "unidecoder_data"))
|
||||
end
|
||||
|
||||
if path
|
||||
CODEPOINTS = Hash.new do |h, k|
|
||||
h[k] = YAML.load_file(File.join(path, "stringex", "unidecoder_data", "#{k}.yml"))
|
||||
end
|
||||
|
||||
# Transliterate string from Unicode into ASCII.
|
||||
def self.decode(string)
|
||||
string.gsub(/[^\x00-\x7f]/u) do |codepoint|
|
||||
unpacked = codepoint.unpack1("U")
|
||||
CODEPOINTS[sprintf("x%02x", unpacked >> 8)][unpacked & 255]
|
||||
rescue StandardError
|
||||
"?"
|
||||
end
|
||||
end
|
||||
else
|
||||
def self.decode(string)
|
||||
string
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user