Jak to działa?
Działa poprzez czytanie fragmentu łańcucha po kawałku, co może nie być najlepszym rozwiązaniem dla naprawdę długich łańcuchów.
Ilekroć parser wykryje, że krytyczny fragment jest czytany, tj. '*'
Lub jakikolwiek inny znacznik, zaczyna analizować fragmenty tego elementu, aż parser znajdzie znacznik zamykający.
Działa na ciągach wieloliniowych, patrz na przykład kod.
Ostrzeżenia
Nie określiłeś, lub mógłbym źle zrozumieć twoje potrzeby, jeśli istnieje konieczność parsowania tagów pogrubionych i kursywnych , moje obecne rozwiązanie może nie działać w tym przypadku.
Jeśli jednak potrzebujesz pracować z powyższymi warunkami, po prostu skomentuj tutaj, a ja poprawię kod.
Pierwsza aktualizacja: poprawia sposób traktowania znaczników przeceny
Tagi nie są już zakodowane na stałe, ale są mapą, na której można łatwo rozszerzyć w celu dopasowania do swoich potrzeb.
Naprawiono błędy, o których wspomniałeś w komentarzach, dzięki za wskazanie tych problemów = p
Druga aktualizacja: znaczniki przeceny o wielu długościach
Najłatwiejszy sposób na osiągnięcie tego: zastąpienie znaków o wielu długościach rzadko używanym Unicode
Chociaż metoda parseMarkdown
nie obsługuje jeszcze tagów o wielu długościach, możemy z łatwością zastąpić te znaczniki o dużej długości prostymi string.replace
podczas wysyłania naszego rawMarkdown
rekwizytu.
Aby zobaczyć przykład tego w praktyce, spójrz na ReactDOM.render
, znajdujący się na końcu kodu.
Nawet jeśli aplikacja nie obsługuje wiele języków, są nieprawidłowe znaki Unicode że JavaScript nadal wykrywa, np .:"\uFFFF"
nie jest poprawnym Unicode, jeśli dobrze pamiętam, ale JS nadal będzie mógł je porównać ( "\uFFFF" === "\uFFFF" = true
)
Na początku może wydawać się hack-y, ale w zależności od przypadku użycia nie widzę większych problemów z korzystaniem z tej trasy.
Kolejny sposób na osiągnięcie tego
Cóż, możemy łatwo śledzić ostatnie N
(gdzieN
odpowiada długości najdłuższego tagu o wielu długościach).
Należy wprowadzić pewne poprawki w sposobie, w jaki parseMarkdown
zachowuje się metoda wewnątrz pętli
, tj. Sprawdzanie, czy bieżący fragment jest częścią znacznika o wielu długościach, jeśli jest on używany jako znacznik; w przeciwnym razie ``k
musielibyśmy to oznaczyć jakonotMultiLength
coś podobnego i przesunąć ten fragment jako treść.
Kod
// 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'));
Link do kodu (TypeScript) https://codepen.io/ludanin/pen/GRgNWPv
Link do kodu (waniliowy / babel) https://codepen.io/ludanin/pen/eYmBvXw
font _italic *and bold* then only italic_ and normal
? Jaki byłby oczekiwany wynik? Czy nigdy nie będzie zagnieżdżony?