Come funziona?
Funziona leggendo un pezzo di stringa per pezzo, che potrebbe non essere la soluzione migliore per stringhe molto lunghe.
Ogni volta che il parser rileva che viene letto un pezzo critico, ovvero '*'
qualsiasi altro tag markdown, inizia ad analizzare blocchi di questo elemento fino a quando il parser trova il suo tag di chiusura.
Funziona su stringhe multilinea, ad esempio il codice.
Avvertenze
Non hai specificato, o avrei potuto fraintendere le tue esigenze, se c'è la necessità di analizzare tag sia in grassetto che in corsivo , la mia soluzione attuale potrebbe non funzionare in questo caso.
Se hai bisogno, tuttavia, di lavorare con le condizioni di cui sopra, commenta qui e ottimizzerò il codice.
Primo aggiornamento: modifica il modo in cui vengono trattati i tag markdown
I tag non sono più codificati, ma sono una mappa in cui è possibile estenderli facilmente per adattarli alle proprie esigenze.
Risolti i bug che hai citato nei commenti, grazie per aver segnalato questo problema = p
Secondo aggiornamento: tag di markdown multi-lunghezza
Il modo più semplice per raggiungere questo obiettivo: sostituire i caratteri multi-lunghezza con un unicode usato raramente
Sebbene il metodo parseMarkdown
non supporti ancora i tag multi-lunghezza, possiamo facilmente sostituire quei tag multi-lunghezza con un semplice string.replace
quando inviamo il nostro rawMarkdown
prop.
Per vedere un esempio di questo in pratica, guarda ReactDOM.render
, situato alla fine del codice.
Anche se l'applicazione non supportare più lingue, ci sono caratteri Unicode validi che JavaScript rileva ancora, es .: "\uFFFF"
non è un unicode valida, se ricordo bene, ma JS saranno ancora in grado di confrontarlo ( "\uFFFF" === "\uFFFF" = true
)
All'inizio potrebbe sembrare hack-y ma, a seconda del tuo caso d'uso, non vedo alcun problema importante usando questo percorso.
Un altro modo per raggiungere questo obiettivo
Bene, potremmo facilmente rintracciare gli ultimi pezzi N
(dove N
corrisponde alla lunghezza del tag multi-lunghezza più lungo).
Ci sarebbero alcune modifiche da apportare al modo in cui parseMarkdown
si comporta il metodo del ciclo interno
, vale a dire verificare se il blocco corrente fa parte di un tag multi-lunghezza, se lo si utilizza come tag; altrimenti, in casi del genere ``k
, avremmo bisogno di contrassegnarlo come notMultiLength
o qualcosa di simile e spingere quel pezzo come contenuto.
Codice
// 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 al codice (TypeScript) https://codepen.io/ludanin/pen/GRgNWPv
Link al codice (vaniglia / babele) https://codepen.io/ludanin/pen/eYmBvXw
font _italic *and bold* then only italic_ and normal
? Quale sarebbe il risultato atteso? O non sarà mai nidificato?