Como funciona?
Ele funciona lendo uma sequência de partes por parte, o que pode não ser a melhor solução para seqüências realmente longas.
Sempre que o analisador detectar que uma parte crítica está sendo lida, '*'
ou seja, qualquer outra tag de redução, ele começa a analisar partes desse elemento até que o analisador encontre sua marca de fechamento.
Funciona em cadeias de linhas múltiplas, veja o código por exemplo.
Ressalvas
Você não especificou, ou eu poderia ter interpretado mal suas necessidades, se houver a necessidade de analisar tags em negrito e itálico , minha solução atual pode não funcionar nesse caso.
Se você precisar, no entanto, trabalhar com as condições acima, basta comentar aqui e eu ajustarei o código.
Primeira atualização: aprimora o tratamento das tags de remarcação
As tags não são mais codificadas, mas são um mapa onde você pode estender facilmente para atender às suas necessidades.
Corrigidos os erros que você mencionou nos comentários, obrigado por apontar esses problemas = p
Segunda atualização: tags de remarcação de vários comprimentos
A maneira mais fácil de conseguir isso: substituindo caracteres de vários comprimentos por um unicode raramente usado
Embora o método parseMarkdown
ainda não suporte tags de vários comprimentos, podemos facilmente substituí-las por uma simples string.replace
ao enviar nossarawMarkdown
suporte.
Para ver um exemplo disso na prática, veja o ReactDOM.render
, localizado no final do código.
Mesmo que a sua aplicação não suporte a vários idiomas, há caracteres Unicode inválidos se o JavaScript ainda detecta, ex .: "\uFFFF"
não é um unicode válido, se bem me lembro, mas JS ainda será capaz de compará-lo ("\uFFFF" === "\uFFFF" = true
)
Pode parecer hackeado no começo, mas, dependendo do seu caso de uso, não vejo grandes problemas ao usar esta rota.
Outra maneira de conseguir isso
Bem, poderíamos rastrear facilmente o último N
(ondeN
pedaços corresponde ao comprimento da tag mais longa).
Haveria alguns ajustes a serem feitos na maneira como o loop dentro do método
parseMarkdown
se comporta, ou seja, verificar se o pedaço atual faz parte de uma tag de vários comprimentos, se é usado como tag; caso contrário, em casos como ``k
, precisaríamos marcá-lo comonotMultiLength
ou algo semelhante e colocar esse pedaço como conteúdo.
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'));
Link para o código (TypeScript) https://codepen.io/ludanin/pen/GRgNWPv
Link para o código (vanilla / babel) https://codepen.io/ludanin/pen/eYmBvXw
font _italic *and bold* then only italic_ and normal
? Qual seria o resultado esperado? Ou nunca será aninhado?