Как это работает?
Он работает путем чтения фрагмента строки за фрагментом, что может быть не лучшим решением для действительно длинных строк.
Всякий раз, когда анализатор обнаруживает, что читается критический фрагмент, т. '*'
Е. Или любой другой тег уценки, он начинает анализировать фрагменты этого элемента, пока анализатор не найдет свой закрывающий тег.
Он работает на многострочных строках, см. Код, например.
Предостережения
Вы не указали, или я мог бы неправильно понять ваши потребности, если есть необходимость проанализировать теги, выделенные жирным шрифтом и курсивом , мое текущее решение может не сработать в этом случае.
Однако, если вам нужно работать с вышеуказанными условиями, просто прокомментируйте здесь, и я настрою код.
Первое обновление: настройка тегов уценки
Теги больше не жестко закодированы, они представляют собой карту, которую вы можете легко расширить в соответствии со своими потребностями.
Исправлены ошибки, о которых вы упоминали в комментариях, спасибо за указание на эти проблемы = 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
? Каков будет ожидаемый результат? Или это никогда не будет вложенным?