Як це працює?
Це працює, читаючи шматок струнних фрагментів, який може бути не найкращим рішенням для дійсно довгих рядків.
Щоразу, коли аналізатор виявляє, що зчитується критичний фрагмент, тобто '*'
будь-який інший тег розмітки, він починає розбирати фрагменти цього елемента, поки аналізатор не знайде його тег завершення.
Він працює на багаторядкових рядках, див. Код, наприклад.
Коваджі
Ви не вказали, або я міг неправильно зрозуміти ваші потреби, якщо є необхідність розбирати теги, які є жирними та курсивними , моє поточне рішення може не працювати в цьому випадку.
Якщо вам потрібно працювати для вищезазначених умов, просто прокоментуйте тут, і я підправити код.
Перше оновлення: налаштування способів розмітки тегів
Теги більше не кодуються, натомість вони є картою, на якій ви можете легко розширити, щоб відповідати вашим потребам.
Виправлені помилки, про які ви згадали в коментарях, дякую, що вказали на ці проблеми = p
Друге оновлення: багатозначні теги розмітки
Найпростіший спосіб досягти цього: заміна багатозначних символів рідко використовуваним унікодом
Хоча метод parseMarkdown
ще не підтримує теги з декількома довжинами, ми можемо легко замінити ці теги з декількома довжинами простими string.replace
при відправці rawMarkdown
опори.
Щоб побачити приклад цього на практиці, подивіться на ReactDOM.render
, розташований в кінці коду.
Навіть якщо ваш додаток робить підтримку декількох мов, є неприпустимі символи Юнікоду , що JavaScript все ще виявляє, напр.: "\uFFFF"
Не є допустимим юнікода, якщо я правильно пам'ятаю, але 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
? Яким був би очікуваний результат? Або він ніколи не вкладеться?