使い方?
これは、文字列のチャンクをチャンクごとに読み取ることで機能します。これは、本当に長い文字列には最適なソリューションではない場合があります。
パーサーは、重要なチャンク、つまり'*'
その他のマークダウンタグが読み取られていることを検出すると、パーサーが終了タグを見つけるまで、この要素のチャンクの解析を開始します。
複数行の文字列で機能します。たとえば、コードを参照してください。
注意事項
指定していない場合、または私があなたのニーズを誤解している可能性があります。ボールドとイタリックの両方のタグを解析する必要がある場合、現在の解決策はこの場合機能しない可能性があります。
ただし、上記の条件で作業する必要がある場合は、ここでコメントしてください。コードを調整します。
最初の更新:マークダウンタグの処理方法を微調整
タグはハードコーディングされなくなり、代わりに、ニーズに合わせて簡単に拡張できるマップになります。
コメントで指摘したバグを修正しました。この問題を指摘してくれてありがとう= p
2番目の更新:マルチレングスマークダウンタグ
これを実現する最も簡単な方法:マルチレングス文字をほとんど使用されないユニコードに置き換える
このメソッドparseMarkdown
はマルチレングスタグをまだサポートしていませんがstring.replace
、rawMarkdown
小道具を送信するときに、これらのマルチレングスタグをシンプルタグに簡単に置き換えることができます。
この例を実際に確認ReactDOM.render
するには、コードの最後にあるをご覧ください。
アプリケーションが複数の言語をサポートしている場合でも、JavaScriptがまだ検出する無効なUnicode文字があります。例:"\uFFFF"
正しく思い出せば、有効なUnicodeではありませんが、JSはそれを比較できます("\uFFFF" === "\uFFFF" = true
)
最初はハックっぽく見えるかもしれませんが、ユースケースによっては、このルートを使用しても大きな問題は発生しません。
これを達成する別の方法
さて、最後のN
(N
最長のマルチレングスタグの長さに対応する)チャンクを簡単に追跡できました。
メソッド内のループのparseMarkdown
動作を調整する必要があります
。つまり、現在のチャンクがマルチレングスタグの一部であるかどうかをチェックし、それがタグとして使用されているかどうかを確認します。それ以外の場合は、などの場合``k
、それをnotMultiLength
または類似のものとしてマークし、そのチャンクをコンテンツとしてプッシュする必要があります。
コード
// Instead of creating hardcoded variables, we can make the code more extendable
// by storing all the possible tags we'll work with in a Map. Thus, creating
// more tags will not require additional logic in our code.
const tags = new Map(Object.entries({
"*": "strong", // bold
"!": "button", // action
"_": "em", // emphasis
"\uFFFF": "pre", // Just use a very unlikely to happen unicode character,
// We'll replace our multi-length symbols with that one.
}));
// Might be useful if we need to discover the symbol of a tag
const tagSymbols = new Map();
tags.forEach((v, k) => { tagSymbols.set(v, k ); })
const rawMarkdown = `
This must be *bold*,
This also must be *bo_ld*,
this _entire block must be
emphasized even if it's comprised of multiple lines_,
This is an !action! it should be a button,
\`\`\`
beep, boop, this is code
\`\`\`
This is an asterisk\\*
`;
class App extends React.Component {
parseMarkdown(source) {
let currentTag = "";
let currentContent = "";
const parsedMarkdown = [];
// We create this variable to track possible escape characters, eg. "\"
let before = "";
const pushContent = (
content,
tagValue,
props,
) => {
let children = undefined;
// There's the need to parse for empty lines
if (content.indexOf("\n\n") >= 0) {
let before = "";
const contentJSX = [];
let chunk = "";
for (let i = 0; i < content.length; i++) {
if (i !== 0) before = content[i - 1];
chunk += content[i];
if (before === "\n" && content[i] === "\n") {
contentJSX.push(chunk);
contentJSX.push(<br />);
chunk = "";
}
if (chunk !== "" && i === content.length - 1) {
contentJSX.push(chunk);
}
}
children = contentJSX;
} else {
children = [content];
}
parsedMarkdown.push(React.createElement(tagValue, props, children))
};
for (let i = 0; i < source.length; i++) {
const chunk = source[i];
if (i !== 0) {
before = source[i - 1];
}
// Does our current chunk needs to be treated as a escaped char?
const escaped = before === "\\";
// Detect if we need to start/finish parsing our tags
// We are not parsing anything, however, that could change at current
// chunk
if (currentTag === "" && escaped === false) {
// If our tags array has the chunk, this means a markdown tag has
// just been found. We'll change our current state to reflect this.
if (tags.has(chunk)) {
currentTag = tags.get(chunk);
// We have simple content to push
if (currentContent !== "") {
pushContent(currentContent, "span");
}
currentContent = "";
}
} else if (currentTag !== "" && escaped === false) {
// We'll look if we can finish parsing our tag
if (tags.has(chunk)) {
const symbolValue = tags.get(chunk);
// Just because the current chunk is a symbol it doesn't mean we
// can already finish our currentTag.
//
// We'll need to see if the symbol's value corresponds to the
// value of our currentTag. In case it does, we'll finish parsing it.
if (symbolValue === currentTag) {
pushContent(
currentContent,
currentTag,
undefined, // you could pass props here
);
currentTag = "";
currentContent = "";
}
}
}
// Increment our currentContent
//
// Ideally, we don't want our rendered markdown to contain any '\'
// or undesired '*' or '_' or '!'.
//
// Users can still escape '*', '_', '!' by prefixing them with '\'
if (tags.has(chunk) === false || escaped) {
if (chunk !== "\\" || escaped) {
currentContent += chunk;
}
}
// In case an erroneous, i.e. unfinished tag, is present and the we've
// reached the end of our source (rawMarkdown), we want to make sure
// all our currentContent is pushed as a simple string
if (currentContent !== "" && i === source.length - 1) {
pushContent(
currentContent,
"span",
undefined,
);
}
}
return parsedMarkdown;
}
render() {
return (
<div className="App">
<div>{this.parseMarkdown(this.props.rawMarkdown)}</div>
</div>
);
}
}
ReactDOM.render(<App rawMarkdown={rawMarkdown.replace(/```/g, "\uFFFF")} />, document.getElementById('app'));
コードへのリンク(TypeScript)https://codepen.io/ludanin/pen/GRgNWPv
コードへのリンク(vanilla / babel)https://codepen.io/ludanin/pen/eYmBvXw
font _italic *and bold* then only italic_ and normal
か?期待される結果は何でしょうか?それともネストされることはありませんか?