Open ogwata opened 3 years ago
@ogwata
テーブル解析は VFM 独自ではなく remark という基礎部分のライブラリーになります。書式としては GFM (GitHub Flavored Markdown) 相当です。そして GFM ではテーブル内に |
(pipe) を含められません。本件と同様にセル区切りとして解釈されます。
|header|header|
|---|---|
|`|`|value|
は
<table>
<thead><tr><th>header</th><th>header</th></tr></thead>
<tbody><tr><td>`</td><td>`</td></tr></tbody>
</table>
になります。期待値は |
がセル内で <code>|</code>
となることですが pipe が優先されてしまいました。そのうえ、ここでカラム数が最大となるため後続の |value|
セルは無視されます。
単純に文字として pipe を描画したいなら HTML の数値文字参照を利用して |
または |
を指定します。手元の VFM v1.0.0-alpha.11 で小形さんの Markdown を {内容|ないよう}セル
に変えたものを渡したら pipe 処理されず {内容|ないよう}セル
となりました。MDAST では {内容
、|
、ないよう}セル
が分割された text
ですね。
ただし数値文字参照なので pipe ではなく <ruby>
としては処理されません。
本件を解決するとしたら
のいずれかになるでしょう。理想としては 1 ですが実装コストは高そうです。2 はエンドユーザーの手を煩わせることにはなりますが、本件のように VFM ではなくベースとなる GFM (remark) 部分へ手を入れるリスクに比べれば安全な方法です。
@ogwata 以上を踏まえてどのような方針でゆくのがよいか、意見をください。
{内容|ないよう}と{内容:ないよう}の両方をルビに使えるようにするとかどうでしょう。 これならruby.tsの正規表現を修正するだけのような気がします。
自分も以前、テーブル内に|を含めようとして結局実体参照に行きついたのですが、 "\|"などでエスケープしてくれるのが一番ユーザとしてはありがたいです。
@AyumuTakai ルビの区切り文字を追加する提案について #10 で意見を募ることにしました。
自分も以前、テーブル内に|を含めようとして結局実体参照に行きついたのですが、 "|"などでエスケープしてくれるのが一番ユーザとしてはありがたいです。
これはご指摘のとおりで私も GFM に期待していたことなのですが難しそうです。おそらく GFM で採用されていないのは <code>
などを考慮する必要があるからだと予想しています。
詳しい実装は見ていませんが、こちらの問題はrubyのパースルールの優先度を高めれば解決できるように見えます。
tableのパースより先にrubyのパースが実行されれば、誤って |
のパースがされることはないと思うのですが、どうでしょうか?
試しに rehive-parse.ts
の配列先頭に ruby
を移動してみたらテストがエラーになりますね。テーブルは remarkParse
の GFM か CommonMark だと思われますが、ここに割り込む方法はあるのでしたっけ?方針としてはよさそうなので、この方法を継続調査してみます。
もうひとつ懸念として他のブロック書式、例えば Code block はコードそのものの例示に利用されるため、これよりも後にする必要があります。remark 部分の記法へ割り込む場合、それら全体を踏まえて優先順位を決めることになります。
GitHub Flavored Markdown Spec のテーブルセル内に |
を入れる例 https://github.github.com/gfm/#example-200 を見ると
Include a pipe in a cell's content by escaping it, including inside other inline spans:
| f\|oo | | ------ | | b `\|` az | | b **\|** im |
結果: | f\ | oo |
---|---|---|
b \| az |
||
b | im |
で、テーブル内の `\|`
では <code>|</code>
が出力されます。現在のvfmではテーブルの外の `\|`
と同じく <code>\|</code>
が出力されるので、GFM仕様と違ってます。
テーブルの処理がされたあとほかの処理をする前に \|
の2文字を |
の1文字に置換する処理を入れると、GFMと同様になると思います。
そうすると、でんでんマークダウンのルビ記法でもテーブル内では |
の代わりに \|
と書けばよいということになります。
GFMの流儀に従い、「テーブル内では |
はセルの区切り文字なので代わりに \|
と書くこと」という決まりにするのでよい気がしてきました。
ルビの中に文字として "|" を入れることがテーブル内では不可能になるという問題がありますが、そのような場合はHTMLのrubyタグを書けばよいと言えます。
現在のvfmではテーブルの外の
\|
と同じく|
が出力されるので、GFM仕様と違ってます。
これは remark のバグで、そのissueが最近登録されていました:
https://github.com/remarkjs/remark/issues/583 (Backslashes appear in output when escaping inline code in a table cell)
とりあえずremark側でこれが解決されたら、「テーブル内では |
はセルの区切り文字なので代わりに \|
と書くこと」と言えるようになります。
↑そのremarkのissueにあるコメントを読むと、最新のremark-parseとremark-htmlで解決されているようです。
手元で試そうと npm を軒並み更新してテストを実行したら、最新の TypeScript だと判定が厳しくなるものがありエラーが結構でますね。いずれにせよ npm 更新はするべきなので対処してみます。現行のテストをすべて追加したら |
のエスケープをテストへ追加して試します。
調査メモ。手元で npm を一括更新したらテストがエラーになる。
jest 26.1.0 to 26.6.3、ts-jest 26.1.1 to 26.4.4
metadata.ts
の以下がエラーになる。
file.data = {
...file.data,
...yaml(node.value),
};
ymal
が object
以外も返すのに Spread を利用しているのが原因。VS Code 上でも警告される。
src/plugins/metadata.ts:24:7 - error TS2698: Spread types may only be created from object types.
24 ...yaml(node.value),
~~~~~~~~~~~~~~~~~~~
当初 typescript を 3.9.5 から 4.1.3 へ更新したことが原因だと予想していたが jest と ts-jest を更新することで発生。yaml
の戻り値を if
で明示的に object
と型判定することで回避可能。これは妥当なエラーである。
remark-parse 8.0.2 to 9.0.0
以下のような tokenizer の動的追加がすべてエラーになる。
const { blockTokenizers, blockMethods } = this.Parser.prototype;
blockTokenizers.fencedBlock = tokenizer;
エラーは以下。
TypeError: Cannot set property 'fencedBlock' of undefined
61 |
62 | const { blockTokenizers, blockMethods } = this.Parser.prototype;
> 63 | blockTokenizers.fencedBlock = tokenizer;
| ^
64 | blockMethods.splice(blockMethods.indexOf('text'), 0, 'fencedBlock');
65 | };
66 |
at Function.mdast (src/plugins/fenced-block.ts:63:30)
at Function.freeze (node_modules/unified/index.js:128:28)
at Object.<anonymous> (tests/utils.ts:9:38)
remark-parse に破壊的な変更があったのかもしれない。この npm さえ更新しなければテストは通るのだが本件対応に必須なのと npm 依存管理的になるべく最新としてゆきたいので継続調査する。
以下を読むと remark 周りはかなり変更されているようなので参考にしながら対応する。
テストを jest --silent=false --verbose false
にして以下のようにログ出力を仕掛けてから実行。
const { blockTokenizers, blockMethods } = this.Parser.prototype;
console.log(blockTokenizers);
blockTokenizers.fencedBlock = tokenizer;
remark-parse 更新前
console.log
{
blankLine: [Function: blankLine],
indentedCode: [Function: indentedCode],
fencedCode: [Function: fencedCode],
blockquote: [Function: blockquote],
atxHeading: [Function: atxHeading],
thematicBreak: [Function: thematicBreak],
list: [Function: list],
setextHeading: [Function: setextHeading],
html: [Function: blockHtml],
definition: [Function: definition],
table: [Function: table],
paragraph: [Function: paragraph]
}
at Function.exports.mdast (src/plugins/fenced-block.ts:63:11)
9.0.0 へ更新後。
console.log
undefined
at Function.mdast (src/plugins/fenced-block.ts:63:11)
エラーの指摘どおり blockTokenizers
を得られず undefined
となり、そこへプロパティー追加していることが問題。
this.Parser.prototype
を console.dir
で出力してみる。変更前。
console.dir
Parser {
options: {
position: false,
gfm: true,
commonmark: true,
pedantic: false,
blocks: [
'address', 'article', 'aside', 'base',
'basefont', 'blockquote', 'body', 'caption',
'center', 'col', 'colgroup', 'dd',
'details', 'dialog', 'dir', 'div',
'dl', 'dt', 'fieldset', 'figcaption',
'figure', 'footer', 'form', 'frame',
'frameset', 'h1', 'h2', 'h3',
'h4', 'h5', 'h6', 'head',
'header', 'hgroup', 'hr', 'html',
'iframe', 'legend', 'li', 'link',
'main', 'menu', 'menuitem', 'meta',
'nav', 'noframes', 'ol', 'optgroup',
'option', 'p', 'param', 'pre',
'section', 'source', 'title', 'summary',
'table', 'tbody', 'td', 'tfoot',
'th', 'thead', 'title', 'tr',
'track', 'ul'
]
},
interruptParagraph: [
[ 'thematicBreak' ],
[ 'list' ],
[ 'atxHeading' ],
[ 'fencedCode' ],
[ 'blockquote' ],
[ 'html' ],
[ 'setextHeading', [Object] ],
[ 'definition', [Object] ]
],
interruptList: [
[ 'atxHeading', [Object] ],
[ 'fencedCode', [Object] ],
[ 'thematicBreak', [Object] ],
[ 'definition', [Object] ]
],
interruptBlockquote: [
[ 'indentedCode', [Object] ],
[ 'fencedCode', [Object] ],
[ 'atxHeading', [Object] ],
[ 'setextHeading', [Object] ],
[ 'thematicBreak', [Object] ],
[ 'html', [Object] ],
[ 'list', [Object] ],
[ 'definition', [Object] ]
],
blockTokenizers: {
blankLine: [Function: blankLine],
indentedCode: [Function: indentedCode],
fencedCode: [Function: fencedCode],
blockquote: [Function: blockquote],
atxHeading: [Function: atxHeading],
thematicBreak: [Function: thematicBreak],
list: [Function: list],
setextHeading: [Function: setextHeading],
html: [Function: blockHtml],
definition: [Function: definition],
table: [Function: table],
paragraph: [Function: paragraph]
},
inlineTokenizers: {
escape: [Function: escape] { locator: [Function: locate] },
autoLink: [Function: autoLink] {
locator: [Function: locate],
notInLink: true
},
url: [Function: url] { locator: [Function: locate], notInLink: true },
email: [Function: email] { locator: [Function: locate], notInLink: true },
html: [Function: inlineHTML] { locator: [Function: locate] },
link: [Function: link] { locator: [Function: locate] },
reference: [Function: reference] { locator: [Function: locate] },
strong: [Function: strong] { locator: [Function: locate] },
emphasis: [Function: emphasis] { locator: [Function: locate] },
deletion: [Function: strikethrough] { locator: [Function: locate] },
code: [Function: inlineCode] { locator: [Function: locate] },
break: [Function: hardBreak] { locator: [Function: locate] },
text: [Function: text]
},
blockMethods: [
'blankLine', 'indentedCode',
'fencedCode', 'blockquote',
'atxHeading', 'thematicBreak',
'list', 'setextHeading',
'html', 'definition',
'table', 'paragraph'
],
inlineMethods: [
'escape', 'autoLink',
'url', 'email',
'html', 'link',
'reference', 'strong',
'emphasis', 'deletion',
'code', 'break',
'text'
]
}
at Function.exports.mdast (src/plugins/fenced-block.ts:63:11)
9.0.0 へ更新後。
console.dir
{}
at Function.mdast (src/plugins/fenced-block.ts:63:11)
空のオブジェクトになっている。根本的な構造が変化しているようだ。そのため revive-parse
で呼び出しているものは処理を変更する必要あり。
remark-parse は micromark へ移行したのでプラグイン実装の変更が必要。
ここにリストアップされてるものでも動作しないプラグインは作者への対応を呼びかけているようだ。対応済のプラグインについてコミット履歴を読めば VFM 修正の参考になるだろう。
ruby 以外の全般的な対応が必要なため新 remark-parse 関連は #45 で対応します。そちらが完了することで最新の remark-parse と独立化された remark-gfm を採用することになり、結果として村上さんの調査結果となる
これは remark のバグで、そのissueが最近登録されていました:
https://github.com/remarkjs/remark/issues/583 (Backslashes appear in output when escaping inline code in a table cell)
とりあえずremark側でこれが解決されたら、「テーブル内では
|
はセルの区切り文字なので代わりに\|
と書くこと」と言えるようになります。
が反映されるはずです。
前述のように本件は v2.0 へ見送ります。
Issue Details
Describe the bug
以下のようなMarkdownをVFMでパースします。
すると以下のようなHTMLが出力されます。
ルビの構文の {内容|ないよう} の
|
がテーブルのセルの区切りになってしまってます。create-book で出力されたPDFは下記のとおり。