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

markdown リストの入れ子が反映されないことがある #1362

Open
k-satoda opened this issue Nov 17, 2024 · 38 comments
Open

markdown リストの入れ子が反映されないことがある #1362

k-satoda opened this issue Nov 17, 2024 · 38 comments
Assignees
Labels

Comments

@k-satoda
Copy link
Contributor

erroneous_behavior_for_uninitialized_reads.md の「仕様」でリストの入れ子による
記述があり GitHub 上の表示でも構造が反映されますが、 cpprefjp.github.io 側では
入れ子にならず同じレベルに並ぶ形になっています。

ソース:

- 自動記憶域期間をもつオブジェクトの記憶域は確保時点で「エラー性の値 (erroneous value)」をもつとされ、処理系がプログラムの状態に依存せず決定する何らかの値で埋められる
  - 動的記憶域期間であれば不定値、静的・スレッド記憶域期間であればゼロで埋められる。C++23までは自動記憶域期間も不定値で埋められていた

GitHub 表示

<li>自動記憶域期間をもつオブジェクトの記憶域は確保時点で「エラー性の値 (erroneous value)」をもつとされ、処理系がプログラムの状態に依存せず決定する何らかの値で埋められる
<ul dir="auto">
<li>動的記憶域期間であれば不定値、静的・スレッド記憶域期間であればゼロで埋められる。C++23までは自動記憶域期間も不定値で埋められていた</li>
</ul>
</li>

cpprefjp.github.io:

<li>自動記憶域期間をもつオブジェクトの記憶域は確保時点で「エラー性の値 (erroneous value)」をもつとされ、処理系がプログラムの状態に依存せず決定する何らかの値で埋められる</li>
<li>動的記憶域期間であれば不定値、静的・スレッド記憶域期間であればゼロで埋められる。C++23までは自動記憶域期間も不定値で埋められていた</li>

他の箇所を見て回るとどうもインデントが半角スペース2個だと入れ子にならず
4個だと入れ子になるようです。 site_generator の仕様として変える余地がないところなら
個別修正として該当箇所を半角スペース4個に直しておけばよさそうですが、
変更の余地があるところなら GitHub 上と同じく半角スペース2個で入れ子になるように
しておいたほうがよさそうな気もします。
ここらへん事情など知ってる人がいるかも?ということで一旦 issue にしてみます。

参照:

@faithandbrave
Copy link
Member

4スペースで直しました

@k-satoda
Copy link
Contributor Author

@faithandbrave 修正ありがとうございます。修正をもって close とされたようですが、
表題の「markdown リストの入れ子が反映されないことがある」という件については
特に対応が見られず変わりないようだったので reopen させてもらいます。

今後も発生しそうな話なので、修正あるいは事情を文書化できたらいいなと考えています。

@k-satoda k-satoda reopened this Nov 17, 2024
@faithandbrave
Copy link
Member

Python標準のmarkdownライブラリでHTMLに変換しているので、そのライブラリの仕様でそうなってますね。

https://python-markdown.github.io/

Indentation/Tab Length

The syntax rules clearly state that when a list item consists of multiple paragraphs, “each subsequent paragraph in a list item must be indented by either 4 spaces or one tab” (emphasis added). However, many implementations do not enforce this rule and allow less than 4 spaces of indentation. The implementers of Python-Markdown consider it a bug to not enforce this rule.
構文規則では、リスト項目が複数の段落で構成されている場合、「リスト項目内の後続の各段落は、スペース4つまたはタブ1つ分インデントされなければならない」と明記しています(強調)。しかし、多くの実装ではこのルールが適用されず、4スペース未満のインデントが許されています。Python-Markdownの実装者は、このルールを強制しないことはバグであると考えています。

どこかに書いておきます

@faithandbrave
Copy link
Member

1714d22

  • 箇条書きのインデントとして4スペースのみを許可している (2スペースでは正しくインデントされない)

start_editing.mdにこのように記載しました。

@k-satoda
Copy link
Contributor Author

ありがとうございます。
使用ライブラリの意図的な動作となると簡単に変えられなさそうですね。
事情の文書化、いくらかの再発予防もできたと思うので close とします。

@yohhoy
Copy link
Member

yohhoy commented Nov 17, 2024

FYI: https://github.com/cpprefjp/site/blob/master/NOTICE.md Markdownレンダリングエンジンの仕様(特性)なんですかねぇ(今でもコレを使っている?)

インデントが 4 スペースである

@faithandbrave
Copy link
Member

あ、どこかに書いてあった気はしましたが、そこにありましたか…。
そのドキュメントも一本化したいところです。

specialized.mdの名前を変えて、「cpprefjpのMarkdown記法」みたいにして、制限と拡張をまとめたいですね。やっておきます。

たしかsite_generatorのどこかで、GitHub Flavored Markdownと描画を合わせるために、.mdのインデントを一部変換してたりはしますね。箇条書きのなかでのコードブロックだったと思います。
ただこの件については、多様な書き方を許可すると、それはそれで管理がたいへんなので (一括変換とか検索とか)、制限がついてるのは悪いことではない気がしています。

@faithandbrave
Copy link
Member

specialized.mdからリネームした以下のページに、拡張と制限をまとめました。

https://cpprefjp.github.io/start_editing/markdown_cpprefjp.html

@akinomyoga
Copy link
Member

https://cpprefjp.github.io/start_editing/markdown_cpprefjp.html

元の議論内容と関係ないですが…上のページのセクション "HTMLエンティティを使用できない制限" の中の変換が壊れている気がします。コードブロックの中の実体参照が変換されて、更に地の文が消えている? ように見えます。

ソース

### HTMLエンティティを使用できない制限
```
&reg;
```
のような書き方をすると、cpprefjpではエラーになります。HTML エンティティを使わず、以下のように書いてください。

```
®
```

HTML表示

HTMLエンティティを使用できない制限

®
®

@faithandbrave
Copy link
Member

のような書き方をすると、cpprefjpではエラーになります。HTML エンティティを使わず、以下のように書いてください。

これが表示されない問題は直しました。コードブロック内のHTMLエンティティはなぜこうなるのだろう…。

close状態でコメントし合うのもよくないと思うので、一旦reopenします。

@faithandbrave faithandbrave reopened this Nov 18, 2024
@akinomyoga
Copy link
Member

これが表示されない問題は直しました。

ありがとうございます! これも現 Markdown の制限でしょうか。見落としでなければ、この制限も上記ページに記されていない制限ということになるでしょうか。### コードブロックの後に空行を入れなければならない制限 みたいなセクションもあって良いのでは。

@faithandbrave
Copy link
Member

「コードブロックのあとに空行が必要な制限」を追加しました

@akinomyoga
Copy link
Member

akinomyoga commented Nov 18, 2024

unordered_*/*/insert_range に同様の問題があったので修正しました。他にもあるかもしれません。

edit: プログラムの修飾に関係しているのだとすれば、その時点で認識されない文字列が含まれていた場合に検出してエラーメッセージを出力できないでしょうか (或いは、既にエラーメッセージは出力しているが誰もエラーログをチェックしていないだけ?)。或いは、もし検出できるのだとすればエラーメッセージを出力する代わりに、通常の処理に切り替えれば良いのかもしれませんが。

@faithandbrave
Copy link
Member

エラーにできそうですね。
閉じコードブロック (偶数回目の``` ) のあとの 行頭が* でも- でもなければエラーですね。

site_generatorに手を入れてそのようなMarkdownを修正して実行するのもできそうではあります。

@faithandbrave
Copy link
Member

HTMLエンティティのところ、

Gistとかで以下のように書くと問題なく &reg; と表示されますが、

&reg;

site_generatorでそこから変換された以下をGistに貼ると ® になりますね。

<p><pre><code>&reg;</code></pre></p>

どうしたものか…

@faithandbrave
Copy link
Member

pandocでMarkdownをHTMLに変換したら、以下のようになりました。

変換前:

&reg;

変換後:

<pre><code>&amp;reg;</code></pre>
<p>

site_generatorで&amp;がつかないのはMarkdownライブラリのオプションかなにかかな…

@faithandbrave
Copy link
Member

コードブロックに言語指定としてbashを指定するとエスケープされました。textだとエスケープされませんでした。

なにも言語指定しない場合にbashを指定するよう、このへんを直そうかと思います。そうした場合に、コードブロックに$ ではじまる行があってもシンタックスハイライトされないことは確認してあります。

影響が大きいので懸念がある方がいたら教えてください。

https://github.com/cpprefjp/markdown_to_html/blob/f528f80c1d43ba261dcb09bb4946b96b757eb4e7/qualified_fenced_code.py#L289-L308

            if self.codehilite_conf and m.group('lang'):
                    highliter = CodeHilite(
                        code,
                        linenums=self.codehilite_conf['linenums'][0],
                        guess_lang=self.codehilite_conf['guess_lang'][0],
                        css_class=self.codehilite_conf['css_class'][0],
                        style=self.codehilite_conf['pygments_style'][0],
                        lang=(m.group('lang') or None),
                        noclasses=self.codehilite_conf['noclasses'][0])

                    code = highliter.hilite()
                    # サンプルコードだったら <div id="..." class="yata"> で囲む
                    if is_example:
                        code = '<div id="%s" class="yata">%s</div>' % (example_id, code)
                else:
                    lang = ''
                    if m.group('lang'):
                        lang = LANG_TAG % m.group('lang')

                    code = CODE_WRAP % (lang, _escape(code)) # このへんをm.group('lang') or bashのシンタックスハイライトに直す

@yumetodo
Copy link
Member

ちょっと影響範囲が大きすぎるので、langが指定されていないコードブロックで&がいたら変換がエラーになるか、そういうのを落とすCIを作るほうがマイルドなのかなと思えます。

@akinomyoga
Copy link
Member

そもそも _escape(code) が動かない根本原因は何でしょうか。

@faithandbrave
Copy link
Member

bashでコードハイライトした場合の出力:

<div class="codehilite"><pre><span></span><code><span class="p">&amp;</span>reg<span class="p">;</span>
</code></pre></div>

_escape(code)した場合の出力 (現状):

<p><pre><code>&reg;
</code></pre></p>

_escape(code)の場合、その段階では&&amp;に変換されますが、最終的なHTMLの段階で&に再変換されてしまってますね。

@faithandbrave
Copy link
Member

memo

markdownライブラリのコードハイライトのコード:

https://github.com/Python-Markdown/markdown/blob/master/markdown/extensions/codehilite.py

@faithandbrave
Copy link
Member

@yumetodo

本文中ではHTMLエンティティがでてきたら現状エラーになりますが、コードブロックでは書いたものがそのままでてきてほしい、というのが課題ですね

@yumetodo
Copy link
Member

エラーかCIで検知にすることで、問題があるコードブロックに対して手動でlangをいじるみたいなワークアラウンドのほうがマイルドなのかなと思っていました。

雑にgrepしてもそんなに多くないので

image

@akinomyoga
Copy link
Member

akinomyoga commented Nov 19, 2024

その段階では&&amp;に変換されますが、最終的なHTMLの段階で&に再変換されてしまってますね。

うーん。何故そのような処理が入っているのか分かりますか。

bashでコードハイライトした場合の出力:

<div class="codehilite"><pre><span></span><code><span class="p">&amp;</span>reg<span class="p">;</span>
</code></pre></div>

_escape(code)した場合の出力 (現状):

<p><pre><code>&reg;
</code></pre></p>

つまり、bash の時にはたまたま &; が個別にハイライトされるから、後の & への再変換があっても &reg; のように結合しないということでしょうか。という事は、「lang を指定しないコードブロック」だけでなく「&; を特別扱いしない lang のコードブロック」も全て同様の問題があるということになりますか。

  • edit: →確認しました。lang を指定していても lang によっては同様の問題が発生します。
  • edit2: 更に、bash にしたとしても # &reg; などの形の行だとやはり &reg; が生で出力されますね。

更に、bash であっても & が単体で現れる壊れた HTML が生成されるということでしょうか。 と思って手元で生成してみたら bash をつけたら & への再変換は発生していませんね (& への再変換は何のためにどういう条件で発生するのだろう…)。

「lang を指定しないコードブロック」だけだったら lang='bash' でハイライトしなくても _escape() (or その変種を新しく作る) で <span>&amp;</span> みたいに変換したらどうにかなりませんか。

@faithandbrave
Copy link
Member

faithandbrave commented Nov 19, 2024

あ、これなら直りましたね。

def _escape(txt):
    """basic html escaping"""
    before = txt
    txt = txt.replace('&', '&amp;')
    txt = txt.replace('<', '&lt;')
    txt = txt.replace('>', '&gt;')
    txt = txt.replace('"', '&quot;')
    txt = txt.replace("&amp;", "<span>&amp;</span>")
    return txt

txt = txt.replace('&', '&amp;')txt = txt.replace('&', '<span>&amp;</span>')に変更するのだと以下のようになってうまくいきませんでした。

出力HTML:

<p><pre><code>&lt;span&gt;&amp;&lt;/span&gt;reg;
</code></pre></p>

表示:

<span>&</span>reg;

@faithandbrave
Copy link
Member

とりあえず明日、影響しそうなところを一通り確認します

@faithandbrave
Copy link
Member

確認してOKだったページ:

  • /lang/cpp11/auto.html (&i;みたいなアドレス取得)
  • /reference/cfloat.html (数式ベースのパラメータ表)
  • /reference/atomic.html (HTMLエンティティとして<>|などを入力している)

NGだったページ:

  • /reference/atomic/op_and_assign.html
    • 表示としてx <span>&</span>= b;になってる
    • HTMLは<span class="n"><span style="color:#ff0000">x &lt;span&gt;&amp;&lt;/span&gt;= b;</span></span>
    • なんでだろ

@faithandbrave
Copy link
Member

あ、まちがってる変換コードのままやってました。。。失礼しました。
確認し直します。

@faithandbrave
Copy link
Member

上記ページに加えて確認したページ:

  • /lang/cpp11/char16_32.html (Unicode関係)
  • /reference/cmath/acos.html (コードブロックではない数式)
  • /reference/ios/fpos.html (&lt;とか直接書いてた)
  • /reference/memory/unique_ptr/op_at.html (<[]>がでてくる)
  • /start_editing/markdown_cpprefjp.html (数式の記述例をコードブロックに書いてる)
  • /lang/cpp23/delimited_escape_sequences.html (エスケープシーケンス)

その他、関係なさそうな普通のページを数十ページ確認しました。

これでいけそうな気がしますが、懸念があれば数日中に教えてください。

@faithandbrave
Copy link
Member

差分としては、この1行だけです。

diff --git forkSrcPrefix/qualified_fenced_code.py forkDstPrefix/qualified_fenced_code.py
index 650929971a8341f7dc05ab63749f22354d35aed8..52039dd155cb967d41e4cf2f382e102b3a100579 100644
--- forkSrcPrefix/qualified_fenced_code.py
+++ forkDstPrefix/qualified_fenced_code.py
@@ -72,6 +72,7 @@ def _escape(txt):
     txt = txt.replace('<', '&lt;')
     txt = txt.replace('>', '&gt;')
     txt = txt.replace('"', '&quot;')
+    txt = txt.replace("&amp;", "<span>&amp;</span>")
     return txt

@akinomyoga
Copy link
Member

言語指定がない場合はそれで良さそうですが、言語指定がある場合でも & 単体でマークアップされない文脈では同じ問題が発生します。作為的な例ですが

```cpp
int reg = 0;
int& ref = reg; // int* ptr = &reg; とやっていることは同じ
```

を変換すると

<span class="kt">int</span><span class="o">&amp;</span> <span class="n">ref</span> <span class="o">=</span> <span class="n">reg</span><span class="p">;</span> <span class="c1">// int* ptr = &reg; とやっていることは同じ</span>

という HTML になって、コメント内の &reg; がそのまま出力されています。どうにか根本原因 (一旦 &amp; にしたものがまた & に戻ってしまう) の方で修正はできないものでしょうか。。

@akinomyoga
Copy link
Member

これ markdown_to_html.html_attribute が犯人みたいです…。

というか自分の手元に昔作った html_attribute の修正ブランチがあるのですが、これで治るかも…。

@akinomyoga
Copy link
Member

akinomyoga commented Nov 20, 2024

akinomyoga:html_attribute-fix-serialize このブランチ (two commits f4d5657 and dd9ab54) なんですが、よく見たら1つ目の commit メッセージに何か解説が書いてあります。更に、2番目の commit では明示的に & の処理を差し替えたりしていて、正に今回の問題に対する対策になっています。実際、上で出した動かないパターンを、このブランチのもとで色々見てみましたが正しく変換されるようです。

(なんかこの問題デジャヴな感じがするなあと思っていたら、実は2年半前に手元で修正仕掛けていたということでした…)

@faithandbrave
Copy link
Member

それでいきましょう!

@akinomyoga
Copy link
Member

ありがとうございます。取り敢えずこの方針で考えてみることにします。ただ、当該ブランチが放置されていたのは何か微妙な点があったからだったはずで、なので改めて注意深く実装・動作の確認をする必要がある気がします。

@akinomyoga
Copy link
Member

cpprefjp/markdown_to_html#7 に移動します。

@faithandbrave
Copy link
Member

faithandbrave commented Nov 20, 2024

HTMLエンティティの問題は別PRで直すとして、あとは

  • 箇条書きの前に空行が必要な制限
  • コードブロックの次の行にコード修飾以外を書く際に空行が必要な制限

これらについて、「書いた文章が表示されない」という気づきにくい問題が発生するので、site_generatorでHTMLへの変換前にMarkdownの変換をして解決しようかと思います。

@faithandbrave
Copy link
Member

箇条書きのインデントが2の倍数ならエラーにするCIはあっていい気がします

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

No branches or pull requests

5 participants