Làm thế nào nó hoạt động?
Nó hoạt động bằng cách đọc một chuỗi chunk bởi chunk, đây có thể không phải là giải pháp tốt nhất cho các chuỗi thực sự dài.
Bất cứ khi nào trình phân tích cú pháp phát hiện một đoạn quan trọng đang được đọc, tức là '*'
hoặc bất kỳ thẻ đánh dấu nào khác, nó sẽ bắt đầu phân tích cú pháp các phần tử này cho đến khi trình phân tích cú pháp tìm thấy thẻ đóng của nó.
Nó hoạt động trên các chuỗi nhiều dòng, xem mã chẳng hạn.
Hãy cẩn thận
Bạn chưa chỉ định hoặc tôi có thể hiểu sai nhu cầu của bạn, nếu cần phải phân tích các thẻ vừa in đậm vừa in nghiêng , giải pháp hiện tại của tôi có thể không hoạt động trong trường hợp này.
Tuy nhiên, nếu bạn cần làm việc với các điều kiện trên, chỉ cần bình luận ở đây và tôi sẽ điều chỉnh mã.
Cập nhật đầu tiên: điều chỉnh cách xử lý thẻ markdown
Các thẻ không còn được mã hóa cứng, thay vào đó chúng là một bản đồ nơi bạn có thể dễ dàng mở rộng để phù hợp với nhu cầu của mình.
Đã sửa các lỗi bạn đã đề cập trong các nhận xét, cảm ơn vì đã chỉ ra vấn đề này = p
Cập nhật thứ hai: thẻ đánh dấu nhiều chiều dài
Cách dễ nhất để đạt được điều này: thay thế ký tự nhiều chiều dài bằng một unicode hiếm khi được sử dụng
Mặc dù phương thức parseMarkdown
này chưa hỗ trợ các thẻ nhiều độ dài, nhưng chúng ta có thể dễ dàng thay thế các thẻ nhiều độ dài đó một cách đơn giản string.replace
khi gửi rawMarkdown
prop của chúng tôi .
Để xem một ví dụ về điều này trong thực tế, hãy nhìn vào ReactDOM.render
, nằm ở cuối mã.
Thậm chí nếu ứng dụng của bạn không hỗ trợ nhiều ngôn ngữ, có các ký tự unicode không hợp lệ mà JavaScript vẫn phát hiện, ex .: "\uFFFF"
chưa là unicode hợp lệ, nếu tôi nhớ chính xác, nhưng JS vẫn sẽ có thể so sánh nó ( "\uFFFF" === "\uFFFF" = true
)
Ban đầu có vẻ như hack-y, nhưng tùy thuộc vào trường hợp sử dụng của bạn, tôi không thấy bất kỳ vấn đề lớn nào khi sử dụng tuyến đường này.
Một cách khác để đạt được điều này
Chà, chúng ta có thể dễ dàng theo dõi đoạn cuối N
(trong đó N
tương ứng với độ dài của các đoạn nhiều chiều dài nhất).
Sẽ có một số điều chỉnh được thực hiện theo cách vòng lặp bên trong phương thức
parseMarkdown
hoạt động, tức là kiểm tra xem đoạn hiện tại có phải là một phần của thẻ nhiều độ dài hay không, nếu nó được sử dụng làm thẻ; mặt khác, trong các trường hợp như ``k
, chúng ta cần đánh dấu nó là notMultiLength
hoặc một cái gì đó tương tự và đẩy đoạn đó thành nội dung.
Mã
// 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'));
Liên kết đến mã (TypeScript) https://codepen.io/ludanin/pen/GRgNWPv
Liên kết đến mã (vanilla / babel) https://codepen.io/ludanin/pen/eYmBvXw
font _italic *and bold* then only italic_ and normal
nào? Kết quả mong đợi là gì? Hay nó sẽ không bao giờ được lồng nhau?