# -*- encoding : utf-8 -*-
require_dependency 'card/content/chunk'
require_dependency 'card/content/parser'
class Card
class Content < SimpleDelegator
attr_reader :revision, :format, :chunks, :opts
def initialize content, format_or_card, opts={}
@format =
if format_or_card.is_a?(Card)
Format.new format_or_card, format: nil
else
format_or_card
end
@opts = opts || {}
@chunks = Parser.new(chunk_list).parse(content, self)
super(@chunks.any? ? @chunks : content)
end
def card
format.card
end
def chunk_list
@opts[:chunk_list] || @format.chunk_list
end
def to_s
case __getobj__
when Array then map(&:to_s) * ''
when String then __getobj__
when NilClass then '' # raise "Nil Card::Content"
else __getobj__.to_s
end
end
def inspect
"<#{__getobj__.class}:#{card}:#{self}>"
end
def each_chunk
return enum_for(:each_chunk) unless block_given?
case __getobj__
when Hash then each_value { |v| yield v if v.is_a?(Chunk::Abstract) }
when Array then each { |e| yield e if e.is_a?(Chunk::Abstract) }
when String # noop. strings are parsed in self, so no chunks in a String
else
Rails.logger.warn 'error self is unrecognized type' \
" #{self.class} #{__getobj__.class}"
end
end
def find_chunks chunk_type
each_chunk.select { |chunk| chunk.is_a?(chunk_type) }
end
def process_each_chunk &block
each_chunk { |chunk| chunk.process_chunk(&block) }
self
end
ALLOWED_TAGS = {}
%w(
br i b pre cite caption strong em ins sup sub del ol hr ul li p
div h1 h2 h3 h4 h5 h6 span table tr td th tbody thead tfoot
).each { |tag| ALLOWED_TAGS[tag] = [] }
# allowed attributes
ALLOWED_TAGS.merge!(
'a' => %w(href title target),
'img' => %w(src alt title),
'code' => ['lang'],
'blockquote' => ['cite']
)
if Card.config.allow_inline_styles
ALLOWED_TAGS['table'] += %w( cellpadding align border cellspacing )
end
ALLOWED_TAGS.each_key do |k|
ALLOWED_TAGS[k] << 'class'
ALLOWED_TAGS[k] << 'style' if Card.config.allow_inline_styles
ALLOWED_TAGS[k]
end
ALLOWED_TAGS.freeze
ATTR_VALUE_RE = [/(?<=^')[^']+(?=')/, /(?<=^")[^"]+(?=")/, /\S+/].freeze
class << self
## Method that cleans the String of HTML tags
## and attributes outside of the allowed list.
# this has been hacked for card to allow classes if
# the class begins with "w-"
def clean! string, tags=ALLOWED_TAGS
string.gsub(%r{<(/*)(\w+)([^>]*)>}) do
raw = $LAST_MATCH_INFO
tag = raw[2].downcase
if (attrs = tags[tag])
html_attribs =
attrs.each_with_object([tag]) do |attr, pcs|
q, rest_value = process_attribute attr, raw[3]
pcs << "#{attr}=#{q}#{rest_value}#{q}" unless rest_value.blank?
end * ' '
"<#{raw[1]}#{html_attribs}>"
else
' '
end
end.gsub(/<\!--.*?-->/, '')
end
def process_attribute attr, all_attributes
return ['"', nil] unless all_attributes =~ /\b#{attr}\s*=\s*(?=(.))/i
q = '"'
rest_value = $'
(idx = %w(' ").index(Regexp.last_match(1))) &&
(q = Regexp.last_match(1))
re = ATTR_VALUE_RE[idx || 2]
if (match = rest_value.match(re))
rest_value = match[0]
if attr == 'class'
rest_value =
rest_value.split(/\s+/).select { |s| s =~ /^w-/i }.join(' ')
end
end
[q, rest_value]
end
if Card.config.space_last_in_multispace
def clean_with_space_last! string, tags=ALLOWED_TAGS
cwo = clean_without_space_last!(string, tags)
cwo.gsub(/(?:^|\b) ((?: )+)/, '\1 ')
end
alias_method_chain :clean!, :space_last
end
def truncatewords_with_closing_tags input, words=25,
_truncate_string='...'
return if input.nil?
wordlist = input.to_s.split
l = words.to_i - 1
l = 0 if l < 0
wordstring = wordlist.length > l ? wordlist[0..l].join(' ') : input.to_s
# nuke partial tags at end of snippet
wordstring.gsub!(/(<[^\>]+)$/, '')
tags = find_tags wordstring
tags.each { |t| wordstring += "#{t}>" }
if wordlist.length > l
wordstring += '...'
end
# wordstring += '...' if wordlist.length > l
wordstring.gsub! %r{<[/]?br[\s/]*>}, ' '
# Also a hack -- get rid of
's -- they make line view ugly.
wordstring.gsub! %r{<[/]?p[^>]*>}, ' '
## Also a hack -- get rid of
's -- they make line view ugly.
wordstring
end
def find_tags wordstring
tags = []
# match tags with or without self closing (ie. )
wordstring.scan(%r{\<([^\>\s/]+)[^\>]*?\>}).each do |t|
tags.unshift(t[0])
end
# match tags with self closing and mark them as closed
wordstring.scan(%r{\<([^\>\s/]+)[^\>]*?/\>}).each do |t|
next unless (x = tags.index(t[0]))
tags.slice!(x)
end
# match close tags
wordstring.scan(%r{\([^\>\s/]+)[^\>]*?\>}).each do |t|
next unless (x = tags.rindex(t[0]))
tags.slice!(x)
end
tags
end
end
end
end