Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue when $ appears in code block #11

Open
MateuszKubuszok opened this issue Dec 25, 2018 · 3 comments
Open

Issue when $ appears in code block #11

MateuszKubuszok opened this issue Dec 25, 2018 · 3 comments

Comments

@MateuszKubuszok
Copy link

When I have code block (rendered by rouge) in my post.md e.g.

```bash
echo $ENV_VAR
````

and I want to render LaTeX as well using $$ tags in markdown and katexmm in layout:

{% katexmm %}
{{ content }}
{% endkatexmm %}

I end up with KaTeX exception Liquid Exception: ParseError: KaTeX parse error: Expected 'EOF', got … in /_layouts/post.html. MathJax simply ignores unmatched $ so it wasn't a problem before migration. Additionally, since rouge runs somewhere between putting templates together and running KaTeX, so neither escaping $ manually in all .md files, nor writing a custom filter that prepends all $ with \ help. This error happens on $ pairing, so throw_error: false cannot fix it.

It would be helpful to be able to ignore $ at least in certain contexts, like e.g. code tag.

@linjer
Copy link
Owner

linjer commented Dec 26, 2018

Thanks for reporting this issue.

As a temporary workaround, I think you might have to close the katexmm environment when you have code blocks with $.

I'd have to think about this for a bit. I'm not sure how easy it would be to detect all the different situations where this might be impacted (e.g., fenced code blocks) and it's not obvious to me at this time what the right strategy would be for ignoring unmatched $ since there can be many possible endings/openings.

Currently, the matching strategy is to select first matching delimiter ($/$$). If there are many math blocks, it's unclear how to reliably determine which $/$$ is unclosed since it will likely detect any following $ as the close. Doing a first pass to match everything up can be dicey since it's not obvious how I can tell if contents within a block was intended or not.

Suggestions for how to gracefully handle this situation would be appreciated. I am currently considering the following:

  1. Look at easy ways to skip over code blocks
  2. Fix inability to escape $ in blocks

@MateuszKubuszok
Copy link
Author

I had a workaround I tried (before switching to katex math engine in kramdown) was declaring a fixed tag as a Jekyll plugin:

# frozen_string_literal: true

require 'jekyll'
require 'jekyll-katex/configuration'
require 'jekyll-katex/katex_js'
require 'nokogiri'

module Jekyll
  module Tags
    # Defines the custom Liquid tag for compile-time rendering of KaTeX math.
    # This differs from the katex tag in that it allows use of `$` and `$$` fencing to mark math mode blocks similar to
    # standard latex.
    #   {% katexmm %}
    #   This is a mixed environment where you can write text as normal but fence off latex math using `$`. Escape
    #   using `\$`. For example.
    #   $latex math with \$$
    #   $$display mode latex$$
    #   {% endkatexmm %}
    class KatexMathModeFixed < Liquid::Block
      LOG_TOPIC = 'KatexMathModeFixed:'
      KATEX ||= Jekyll::Katex::KATEX_JS
      LATEX_TOKEN_PATTERN = /(?<!\\)([$]{2}|[$]{1})(.+?)(?<!\\)\1/m

      def initialize(tag_name, markup, tokens)
        super
        @markup = markup
        @tokens = tokens
        @display_mode_rendering = Jekyll::Katex::Configuration.global_rendering_options.merge(displayMode: true)
        @inline_mode_rendering = Jekyll::Katex::Configuration.global_rendering_options.merge(displayMode: false)
      end

      def render(context)
        enclosed_block = super
        fixed_block = fix_code(enclosed_block)
        rendered_str = fixed_block.to_s.gsub(LATEX_TOKEN_PATTERN) do |match|
          display_mode = match.to_s.start_with? '$$'
          rendering_options = display_mode ? @display_mode_rendering : @inline_mode_rendering
          Jekyll.logger.debug LOG_TOPIC, "Rendering matched block - #{match}"
          KATEX.call('katex.renderToString', Regexp.last_match(2), rendering_options)
        end
        # KaTeX should fix escaped `$` within fenced blocks, this addresses instances outside of math mode
        rendered_str.to_s.gsub(/\\[$]/, '$').to_s
      end

      def fix_code(input)
        updated = false
        html = Nokogiri::HTML.fragment(input)
        Jekyll.logger.debug LOG_TOPIC, "Fixing - #{input}"
        html.css("code, code span").each do |c|
          if c.css('*').empty? && c.content['$']
            updated = true
            Jekyll.logger.debug LOG_TOPIC, "current tag - #{c}"
            content = c.content
            content['$'] = '\$'
            c.content = content
            Jekyll.logger.debug LOG_TOPIC, "current tag now - #{c}/#{content}"
          end
        end
        output = html.to_s
        Jekyll.logger.debug LOG_TOPIC, "Fixed - #{output}"
        if updated then html.to_s else input end
      end
    end
  end
end

Liquid::Template.register_tag('katexmmx', Jekyll::Tags::KatexMathModeFixed)

What it basically does, is searching for <pre><code><span>AST element</span></code></pre> (block case, code span css selector) or <code>inline<code> (inline code, code selector) to using XPath and then replace each $ with \$. From what I saw it worked, though it was a bit slower than normal katexmm and I think a lot slower than kramdown build-in support for katex. But it might be some starting point.

@linjer
Copy link
Owner

linjer commented Jan 2, 2019

@MateuszKubuszok Thank you for spending so much time to help document and understand the issue. Unfortunately I'm in the middle of a lot of life events and probably won't have time to address this issue until after April.

If you want to open a PR I can dedicate time to helping you get any fixes in. If you don't mind waiting, I can return to this in a few months.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants