Class: Metanorma::Standoc::MonospaceProtectPreprocessor

Inherits:
Asciidoctor::Extensions::Preprocessor
  • Object
show all
Defined in:
lib/metanorma/converter/macros_nosub.rb

Overview

protect monospace text from character substitutions

Constant Summary collapse

PASS_INLINE_MACROS =
%w(pass pass-format identifier std-link stem)
.join("|").freeze
PASS_INLINE_MACRO_STR =
<<~REGEX.freeze
  (
    \\b(?<![-\\\\])                        # word-separator, no hyphen or backslash
    (?:                                    # don't capture these!
      (?:#{PASS_INLINE_MACROS}):[^\\s\\[]* | # macro name, :, second key. OR:
      span:uri \\b [^\\s\\[]*              # span:uri, third key
    )
    \\[.*?(?<!\\\\)\\]                     # [ ... ] not preceded by \\
  )
REGEX
PASS_INLINE_MACRO_RX =
/#{PASS_INLINE_MACRO_STR}/xo
MonospaceRx =

Regex to match single or double backticks with content Matches: ‘text` or “text“ Avoids: escaped backticks ` Avoids: the backticks of Asciidoc smart-quote markup “`…`”, where a

backtick abuts an enclosing " (the leading (?<![\\"]) and the (?!")
guard). The (?!") guard is suppressed when the backtick run sits at a
genuine monospace boundary (start, whitespace, or opening bracket), so
monospace whose content itself begins with " — e.g. `"quote" A's` — is
still protected from character replacements.
/
  (?<![\\"]) (`{1,2})
  (?: (?!") | (?<=[\s(\[{<]`) | (?<=[\s(\[{<]``) | (?<=\A`) | (?<=\A``) )
  (.+?) (?<!\\) \1
/x

Instance Method Summary collapse

Instance Method Details

#inlinemonospace(text) ⇒ Object



160
161
162
163
164
165
166
# File 'lib/metanorma/converter/macros_nosub.rb', line 160

def inlinemonospace(text)
  /(?<!")`(?!")/.match?(text) or return text
  /^\[.*\]\s*$/.match?(text) and return text
  pass_inline_split(text) do |x|
    monospace_escape(x)
  end.join
end

#monospace_escape(text) ⇒ Object



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/metanorma/converter/macros_nosub.rb', line 168

def monospace_escape(text)
  text.gsub(MonospaceRx) do
    backticks = $1
    content = $2
    # Skip if content already starts with ++ (already protected)
    if content.start_with?("++") && content.end_with?("++")
      "#{backticks}#{content}#{backticks}"
    else
      # Protect content from character replacements but allow quotes substitution
      # Escape unescaped ] to prevent premature closing of pass:[...]
      protected_content = content.gsub(/(?<!\\)\]/, "\\]")
      "pass:c,q,a,m,p[#{backticks}#{protected_content}#{backticks}]"
    end
  end
end

#pass_inline_split(text) ⇒ Object



139
140
141
142
143
# File 'lib/metanorma/converter/macros_nosub.rb', line 139

def pass_inline_split(text)
  text.split(PASS_INLINE_MACRO_RX).each.map do |x|
    PASS_INLINE_MACRO_RX.match?(x) ? x : yield(x)
  end
end

#process(document, reader) ⇒ Object



115
116
117
118
119
120
121
122
# File 'lib/metanorma/converter/macros_nosub.rb', line 115

def process(document, reader)
  p = Metanorma::Utils::LineStatus.new
  lines = reader.lines.map do |t|
    p.process(t)
    !p.pass && /(?<!")`(?!")/.match?(t) ? inlinemonospace(t) : t
  end
  ::Asciidoctor::PreprocessorReader.new document, lines
end