lib/govspeak.rb in govspeak-5.6.0 vs lib/govspeak.rb in govspeak-5.7.0
- old
+ new
@@ -2,10 +2,11 @@
require 'active_support/core_ext/array'
require 'erb'
require 'htmlentities'
require 'kramdown'
require 'kramdown/parser/kramdown_with_automatic_external_links'
+require 'rinku'
require 'govspeak/header_extractor'
require 'govspeak/structured_header_extractor'
require 'govspeak/html_validator'
require 'govspeak/html_sanitizer'
require 'govspeak/kramdown_overrides'
@@ -14,65 +15,64 @@
require 'govspeak/link_extractor'
require 'govspeak/presenters/attachment_presenter'
require 'govspeak/presenters/contact_presenter'
require 'govspeak/presenters/h_card_presenter'
+
module Govspeak
+ def self.root
+ File.expand_path('..', File.dirname(__FILE__))
+ end
class Document
-
Parser = Kramdown::Parser::KramdownWithAutomaticExternalLinks
PARSER_CLASS_NAME = Parser.name.split("::").last
+ UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.freeze
- @@extensions = []
+ @extensions = []
attr_accessor :images
attr_reader :attachments, :contacts, :links, :locale
def self.to_html(source, options = {})
new(source, options).to_html
end
+ def self.extensions
+ @extensions
+ end
+
def initialize(source, options = {})
options = options.dup.deep_symbolize_keys
@source = source ? source.dup : ""
@images = options.delete(:images) || []
@attachments = Array.wrap(options.delete(:attachments))
@links = Array.wrap(options.delete(:links))
@contacts = Array.wrap(options.delete(:contacts))
@locale = options.fetch(:locale, "en")
- @options = {input: PARSER_CLASS_NAME}.merge(options)
+ @options = { input: PARSER_CLASS_NAME }.merge(options)
@options[:entity_output] = :symbolic
- i18n_load_paths
end
- def i18n_load_paths
- Dir.glob('locales/*.yml') do |f|
- I18n.load_path << f
- end
- end
- private :i18n_load_paths
-
- def kramdown_doc
- @kramdown_doc ||= Kramdown::Document.new(preprocess(@source), @options)
- end
- private :kramdown_doc
-
def to_html
- @html ||= Govspeak::PostProcessor.process(kramdown_doc.to_html)
+ @to_html ||= Govspeak::PostProcessor.process(kramdown_doc.to_html)
end
def to_liquid
to_html
end
def t(*args)
options = args.last.is_a?(Hash) ? args.last.dup : {}
key = args.shift
- I18n.t(key, options.merge(locale: locale))
+ I18n.t!(key, options.merge(locale: locale))
end
+ def format_with_html_line_breaks(string)
+ ERB::Util.html_escape(string || "").strip.gsub(/(?:\r?\n)/, "<br/>").html_safe
+ end
+
def to_sanitized_html
HtmlSanitizer.new(to_html).sanitize
end
def to_sanitized_html_without_images
@@ -97,49 +97,51 @@
def extracted_links(website_root: nil)
Govspeak::LinkExtractor.new(self, website_root: website_root).call
end
+ def extract_contact_content_ids
+ _, regex = self.class.extensions.find { |(title)| title == "Contact" }
+ return [] unless regex
+
+ @source.scan(regex).map(&:first).uniq.select { |id| id.match(UUID_REGEX) }
+ end
+
def preprocess(source)
source = Govspeak::BlockquoteExtraQuoteRemover.remove(source)
- @@extensions.each do |title,regexp,block|
+ self.class.extensions.each do |_, regexp, block|
source.gsub!(regexp) {
instance_exec(*Regexp.last_match.captures, &block)
}
end
source
end
- def encode(text)
- HTMLEntities.new.encode(text)
- end
- private :encode
-
def self.extension(title, regexp = nil, &block)
regexp ||= %r${::#{title}}(.*?){:/#{title}}$m
- @@extensions << [title, regexp, block]
+ @extensions << [title, regexp, block]
end
- def self.surrounded_by(open, close=nil)
+ def self.surrounded_by(open, close = nil)
open = Regexp::escape(open)
if close
close = Regexp::escape(close)
%r+(?:\r|\n|^)#{open}(.*?)#{close} *(\r|\n|$)?+m
else
%r+(?:\r|\n|^)#{open}(.*?)#{open}? *(\r|\n|$)+m
end
end
- def self.wrap_with_div(class_name, character, parser=Kramdown::Document)
+ def self.wrap_with_div(class_name, character, parser = Kramdown::Document)
extension(class_name, surrounded_by(character)) { |body|
content = parser ? parser.new("#{body.strip}\n").to_html : body.strip
%{\n<div class="#{class_name}">\n#{content}</div>\n}
}
end
- def insert_strong_inside_p(body, parser=Govspeak::Document)
- parser.new(body.strip).to_html.sub(/^<p>(.*)<\/p>$/,"<p><strong>\\1</strong></p>")
+ def insert_strong_inside_p(body, parser = Govspeak::Document)
+ parser.new(body.strip).to_html.sub(/^<p>(.*)<\/p>$/, "<p><strong>\\1</strong></p>")
end
extension('button', %r{
(?:\r|\n|^) # non-capturing match to make sure start of line and linebreak
{button(.*?)} # match opening bracket and capture attributes
@@ -195,11 +197,11 @@
extension('helpful', surrounded_by("%")) { |body|
%{\n\n<div role="note" aria-label="Help" class="application-notice help-notice">\n#{Govspeak::Document.new(body.strip).to_html}</div>\n}
}
- extension('barchart', /{barchart(.*?)}/) do |captures, body|
+ extension('barchart', /{barchart(.*?)}/) do |captures|
stacked = '.mc-stacked' if captures.include? 'stacked'
compact = '.compact' if captures.include? 'compact'
negative = '.mc-negative' if captures.include? 'negative'
[
@@ -224,18 +226,20 @@
end
extension('attachment', /\[embed:attachments:(?!inline:|image:)\s*(.*?)\s*\]/) do |content_id, body|
attachment = attachments.detect { |a| a[:content_id] == content_id }
next "" unless attachment
+
attachment = AttachmentPresenter.new(attachment)
content = File.read(__dir__ + '/templates/attachment.html.erb')
ERB.new(content).result(binding)
end
extension('attachment inline', /\[embed:attachments:inline:\s*(.*?)\s*\]/) do |content_id|
attachment = attachments.detect { |a| a[:content_id] == content_id }
next "" unless attachment
+
attachment = AttachmentPresenter.new(attachment)
span_id = attachment.id ? %{ id="attachment_#{attachment.id}"} : ""
# new lines inside our title cause problems with govspeak rendering as this is expected to be on one line.
title = (attachment.title || "").tr("\n", " ")
link = attachment.link(title, attachment.url)
@@ -244,10 +248,11 @@
end
extension('attachment image', /\[embed:attachments:image:\s*(.*?)\s*\]/) do |content_id|
attachment = attachments.detect { |a| a[:content_id] == content_id }
next "" unless attachment
+
attachment = AttachmentPresenter.new(attachment)
title = (attachment.title || "").tr("\n", " ")
render_image(attachment.url, title, nil, attachment.id)
end
@@ -263,12 +268,12 @@
# This issue is not considered a bug by kramdown: https://github.com/gettalong/kramdown/issues/191
def render_image(url, alt_text, caption = nil, id = nil)
id_attr = id ? %{ id="attachment_#{id}"} : ""
lines = []
lines << %{<figure#{id_attr} class="image embedded">}
- lines << %Q{<div class="img"><img src="#{encode(url)}" alt="#{encode(alt_text)}"></div>}
- lines << %Q{<figcaption>#{caption.strip}</figcaption>} if caption && !caption.strip.empty?
+ lines << %{<div class="img"><img src="#{encode(url)}" alt="#{encode(alt_text)}"></div>}
+ lines << %{<figcaption>#{caption.strip}</figcaption>} if caption && !caption.strip.empty?
lines << '</figure>'
lines.join
end
wrap_with_div('summary', '$!')
@@ -279,11 +284,11 @@
wrap_with_div('additional-information', '$AI')
wrap_with_div('example', '$E', Govspeak::Document)
wrap_with_div('call-to-action', '$CTA', Govspeak::Document)
extension('address', surrounded_by("$A")) { |body|
- %{\n<div class="address"><div class="adr org fn"><p>\n#{body.sub("\n", "").gsub("\n", "<br />")}\n</p></div></div>\n}
+ %{\n<div class="address"><div class="adr org fn"><p>\n#{body.sub("\n", '').gsub("\n", '<br />')}\n</p></div></div>\n}
}
extension("legislative list", /(?<=\A|\n\n|\r\n\r\n)^\$LegislativeList\s*$(.*?)\$EndLegislativeList/m) do |body|
Govspeak::KramdownOverrides.with_kramdown_ordered_lists_disabled do
Kramdown::Document.new(body.strip).to_html.tap do |doc|
@@ -293,32 +298,33 @@
end
end
end
extension("numbered list", /^[ \t]*((s\d+\.\s.*(?:\n|$))+)/) do |body|
- steps ||= 0
- body.gsub!(/s(\d+)\.\s(.*)(?:\n|$)/) do |b|
- "<li>#{Govspeak::Document.new($2.strip).to_html}</li>\n"
+ body.gsub!(/s(\d+)\.\s(.*)(?:\n|$)/) do
+ "<li>#{Govspeak::Document.new($2.strip).to_html}</li>\n"
end
%{<ol class="steps">\n#{body}</ol>}
end
def self.devolved_options
- { 'scotland' => 'Scotland',
- 'england' => 'England',
- 'england-wales' => 'England and Wales',
- 'northern-ireland' => 'Northern Ireland',
- 'wales' => 'Wales',
- 'london' => 'London' }
+ { 'scotland' => 'Scotland',
+ 'england' => 'England',
+ 'england-wales' => 'England and Wales',
+ 'northern-ireland' => 'Northern Ireland',
+ 'wales' => 'Wales',
+ 'london' => 'London' }
end
- devolved_options.each do |k,v|
- extension("devolved-#{k}",/:#{k}:(.*?):#{k}:/m) do |body|
-%{<div class="devolved-content #{k}">
-<p class="devolved-header">This section applies to #{v}</p>
-<div class="devolved-body">#{Govspeak::Document.new(body.strip).to_html}</div>
-</div>\n}
+ devolved_options.each do |k, v|
+ extension("devolved-#{k}", /:#{k}:(.*?):#{k}:/m) do |body|
+ <<~HTML
+ <div class="devolved-content #{k}">
+ <p class="devolved-header">This section applies to #{v}</p>
+ <div class="devolved-body">#{Govspeak::Document.new(body.strip).to_html}</div>
+ </div>
+ HTML
end
end
extension("Priority list", /(?<=\A|\n\n|\r\n\r\n)^\$PriorityList:(\d+)\s*$(.*?)(?:^\s*$|\Z)/m) do |number_to_show, body|
number_to_show = number_to_show.to_i
@@ -334,26 +340,41 @@
end
extension('embed link', /\[embed:link:\s*(.*?)\s*\]/) do |content_id|
link = links.detect { |l| l[:content_id] == content_id }
next "" unless link
+
if link[:url]
"[#{link[:title]}](#{link[:url]})"
else
link[:title]
end
end
- def render_hcard_address(contact)
- HCardPresenter.from_contact(contact).render
- end
- private :render_hcard_address
-
extension('Contact', /\[Contact:\s*(.*?)\s*\]/) do |content_id|
contact = contacts.detect { |c| c[:content_id] == content_id }
next "" unless contact
+
contact = ContactPresenter.new(contact)
@renderer ||= ERB.new(File.read(__dir__ + '/templates/contact.html.erb'))
@renderer.result(binding)
end
+
+ private
+
+ def kramdown_doc
+ @kramdown_doc ||= Kramdown::Document.new(preprocess(@source), @options)
+ end
+
+ def encode(text)
+ HTMLEntities.new.encode(text)
+ end
+
+ def render_hcard_address(contact_address)
+ HCardPresenter.new(contact_address).render
+ end
end
end
+
+I18n.load_path.unshift(
+ *Dir.glob(File.expand_path('locales/*.yml', Govspeak.root))
+)