¿Cómo funciona?
Funciona leyendo un trozo de trozo por trozo, que podría no ser la mejor solución para cadenas realmente largas.
Cada vez que el analizador detecta que se está leyendo un fragmento crítico, es decir '*'
o cualquier otra etiqueta de reducción, comienza a analizar fragmentos de este elemento hasta que el analizador encuentra su etiqueta de cierre.
Funciona en cadenas de varias líneas, consulte el código, por ejemplo.
Advertencias
No ha especificado, o podría haber entendido mal sus necesidades, si existe la necesidad de analizar etiquetas que están en negrita y cursiva , mi solución actual podría no funcionar en este caso.
Sin embargo, si necesita trabajar con las condiciones anteriores, simplemente comente aquí y modificaré el código.
Primera actualización: modifica cómo se tratan las etiquetas de descuento
Las etiquetas ya no están codificadas, sino que son un mapa donde se puede extender fácilmente para satisfacer sus necesidades.
Se corrigieron los errores que mencionaste en los comentarios, gracias por señalar estos problemas = p
Segunda actualización: etiquetas de descuento de longitud múltiple
La forma más fácil de lograr esto: reemplazando caracteres de varias longitudes con un Unicode raramente usado
Aunque el método parseMarkdown
aún no admite etiquetas de varias longitudes, podemos reemplazar fácilmente esas etiquetas de múltiples longitudes con un simple string.replace
al enviar nuestrorawMarkdown
accesorio.
Para ver un ejemplo de esto en la práctica, mire el ReactDOM.render
, ubicado al final del código.
Incluso si su aplicación hace soportar múltiples idiomas, hay caracteres Unicode no válidos que JavaScript sigue detectando, ej .: "\uFFFF"
no es un Unicode válida, si no recuerdo mal, pero JS todavía será capaz de compararlo ("\uFFFF" === "\uFFFF" = true
)
Puede parecer hack-y al principio, pero, dependiendo de su caso de uso, no veo ningún problema importante al usar esta ruta.
Otra forma de lograr esto
Bueno, podríamos rastrear fácilmente el último N
(dondeN
fragmentos corresponde a la longitud de la etiqueta de longitud múltiple más larga).
Habría que hacer algunos ajustes en la forma en que se parseMarkdown
comporta el método del bucle interno
, es decir, verificar si el fragmento actual es parte de una etiqueta de varias longitudes, si se usa como etiqueta; de lo contrario, en casos como ``k
, tendríamos que marcarlo comonotMultiLength
o algo similar e impulsar ese fragmento como contenido.
Código
// 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'));
Enlace al código (TypeScript) https://codepen.io/ludanin/pen/GRgNWPv
Enlace al código (vainilla / babel) https://codepen.io/ludanin/pen/eYmBvXw
font _italic *and bold* then only italic_ and normal
? ¿Cuál sería el resultado esperado? ¿O nunca estará anidado?