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)) +)