JavaScript Python PHP
import * as __biblioteca0 from "unified";
import * as __biblioteca1 from "rehype-parse";
import * as __biblioteca2 from "rehype-remark";
import * as __biblioteca3 from "remark-gfm";
import * as __biblioteca4 from "remark-stringify";
import * as __biblioteca5 from "entities";
function executar(input = '<h1>Olá</h1><p><strong>Bennu</strong> e <a href="https://example.com">link</a></p>') {
const unified = __biblioteca0["unified"];
const rehypeParse = __biblioteca1["default"];
const rehypeRemark = __biblioteca2["default"];
const remarkGfm = __biblioteca3["default"];
const remarkStringify = __biblioteca4["default"];
const decodeHTML = __biblioteca5["decodeHTML"];
const EXECUTABLE = new Set(['script', 'style', 'iframe', 'object', 'embed', 'applet', 'svg', 'math', 'form', 'input', 'button', 'textarea', 'select', 'option', 'link', 'meta', 'base', 'template', 'noscript', 'canvas', 'audio', 'video', 'source', 'track']);
function validateMarkupUrl(url) {
// HTML entities and ASCII controls can obscure a scheme interpreted by a browser.
const normalized = decodeHTML(url).replace(/[\u0000-\u0020\u007f]/g, '');
let protocol;
try {
protocol = new URL(normalized, 'https://converter.invalid/').protocol;
}
catch {
throw new Error('URL inválida: use uma URL relativa, âncora, HTTP, HTTPS ou mailto.');
}
if (!['http:', 'https:', 'mailto:'].includes(protocol))
throw new Error(`Protocolo de URL não permitido: ${protocol.slice(0, -1)}. Use HTTP, HTTPS, mailto, URL relativa ou âncora.`);
}
function checkInput(input) {
if (input.length > 1000000)
throw new Error('A entrada excede o limite de 1 milhão de caracteres.');
}
function prepareHtml(node, depth = 0, state = { count: 0 }) {
if (++state.count > 100000 || depth > 100)
throw new Error('Documento muito complexo: limite de 100 níveis ou 100 mil nós.');
if (node.children)
node.children = node.children.filter(child => child.type !== 'comment' && !EXECUTABLE.has(child.tagName ?? '')).map(child => {
if (child.type === 'element') {
const original = child.properties ?? {};
const properties = Object.create(null);
const allowed = child.tagName === 'a' ? ['href', 'title'] : child.tagName === 'img' ? ['src', 'alt', 'title'] : child.tagName === 'ol' ? ['start'] : child.tagName === 'th' || child.tagName === 'td' ? ['align'] : [];
for (const key of allowed)
if (original[key] !== undefined) {
if (key === 'href' || key === 'src')
validateMarkupUrl(String(original[key]));
properties[key] = original[key];
}
if (child.tagName === 'code' && Array.isArray(original.className)) {
const language = original.className.find(value => typeof value === 'string' && /^language-[a-z0-9_+-]+$/i.test(value));
if (language)
properties.className = [language];
}
child.properties = properties;
}
prepareHtml(child, depth + 1, state);
return child;
});
}
function htmlToMarkdown(input) {
checkInput(input);
if (!input.trim())
return '';
const processor = unified().use(rehypeParse, { fragment: true }).use(rehypeRemark, { handlers: {
pre(state, node) {
const code = node.children.find(child => child.type === 'element' && child.tagName === 'code');
function codeText(child) {
return child.type === 'text' ? child.value ?? '' : (child.children ?? []).map(codeText).join('');
}
const value = codeText(node);
const classes = code?.type === 'element' ? code.properties.className : undefined;
const language = Array.isArray(classes) ? classes.find(item => String(item).startsWith('language-')) : undefined;
const result = { type: 'code', lang: language ? String(language).slice(9) : null, meta: null,
// One final LF is the fence separator; retain all other leading/trailing lines.
value: value.endsWith('\n') ? value.slice(0, -1) : value };
state.patch(node, result);
return result;
}
} }).use(remarkGfm).use(remarkStringify, { fences: true, bullet: '-', emphasis: '*', strong: '*' });
const tree = processor.parse(input);
prepareHtml(tree);
return processor.stringify(processor.runSync(tree));
}
return htmlToMarkdown(input);
}
import html
import re
from urllib.parse import urlsplit
from bs4 import BeautifulSoup, Comment
from markdownify import MarkdownConverter
def executar(input='<h1>Olá</h1><p><strong>Bennu</strong> e <a href="https://example.com">link</a></p>'):
if len(input.encode("utf-16-le",errors="surrogatepass"))//2>1000000: raise ValueError("Limite de 1 milhão de caracteres.")
if not input.strip(): return ""
def safe_url(value):
normalized=re.sub(r"[\x00-\x20\x7f]","",html.unescape(value))
try: protocol=urlsplit(normalized).scheme.lower()
except ValueError as error: raise ValueError("URL inválida.") from error
if protocol and protocol not in ("http","https","mailto"): raise ValueError("Protocolo de URL não permitido: "+protocol)
return True
soup=BeautifulSoup(input,"html.parser")
executable=set("script style iframe object embed applet svg math form input button textarea select option link meta base template noscript canvas audio video source track".split())
count=0
def prepare(node,depth=0):
nonlocal count
count+=1
if count>100000 or depth>100: raise ValueError("Documento muito complexo.")
for child in list(getattr(node,"children",[])):
if isinstance(child,Comment): child.extract(); continue
if getattr(child,"name",None) in executable: child.decompose(); continue
if getattr(child,"name",None):
allowed={"a":["href","title"],"img":["src","alt","title"],"ol":["start"],"th":["align"],"td":["align"],"code":["class"]}.get(child.name,[])
child.attrs={key:value for key,value in child.attrs.items() if key in allowed}
for attribute in ("href","src"):
if attribute in child.attrs: safe_url(str(child.attrs[attribute]))
prepare(child,depth+1)
prepare(soup)
class Converter(MarkdownConverter):
def convert_pre(self,element,text,parent_tags):
code=element.find("code")
classes=code.get("class",[]) if code else []
language=next((c[9:] for c in classes if re.fullmatch(r"language-[a-z0-9_+-]+",c,re.I)),"")
value=element.get_text()
if value.endswith("\n"): value=value[:-1]
fence=chr(96)*max(3,1+max((len(m[0]) for m in re.finditer(chr(96)+r"+",value)),default=0))
return "\n\n"+fence+language+"\n"+value+"\n"+fence+"\n\n"
output=Converter(heading_style="ATX",bullets="-",strong_em_symbol="*",strip_pre=None,autolinks=False).convert_soup(soup)
return output.strip("\n")+"\n" if output.strip("\n") else ""
<?php
use Masterminds\HTML5;
use League\HTMLToMarkdown\HtmlConverter;
use League\HTMLToMarkdown\Converter\TableConverter;
require_once 'vendor/autoload.php';
function executar($input = '<h1>Olá</h1><p><strong>Bennu</strong> e <a href="https://example.com">link</a></p>') {
if (!mb_check_encoding($input,'UTF-8') || strlen(mb_convert_encoding($input,'UTF-16LE','UTF-8'))/2>1000000) throw new InvalidArgumentException('UTF-8 inválido ou limite de entrada.');
if (trim($input)==='') return '';
$safeUrl=function ($value) {
$normalized=preg_replace('/[\x00-\x20\x7f]/','',html_entity_decode($value,ENT_QUOTES | ENT_HTML5,'UTF-8'));
if (preg_match('/^([A-Za-z][A-Za-z0-9+.-]*):/',$normalized,$m) && !in_array(strtolower($m[1]),['http','https','mailto'],true)) throw new InvalidArgumentException('Protocolo de URL não permitido: '.$m[1]);
if (parse_url($normalized)===false) throw new InvalidArgumentException('URL inválida.');
};
$html=new HTML5(); $fragment=$html->loadHTMLFragment($input);
$executable=explode(' ','script style iframe object embed applet svg math form input button textarea select option link meta base template noscript canvas audio video source track');
$count=0;
$prepare=function ($node,$depth=0) use (&$prepare,&$count,$executable,$safeUrl) {
if (++$count>100000 || $depth>100) throw new InvalidArgumentException('Documento muito complexo.');
foreach (iterator_to_array($node->childNodes) as $child) {
if ($child->nodeType===XML_COMMENT_NODE || ($child instanceof DOMElement && in_array(strtolower($child->tagName),$executable,true))) { $node->removeChild($child); continue; }
if ($child instanceof DOMElement) {
$allowed=match(strtolower($child->tagName)) { 'a'=>['href','title'],'img'=>['src','alt','title'],'ol'=>['start'],'th','td'=>['align'],'code'=>['class'],default=>[] };
foreach (iterator_to_array($child->attributes) as $attribute) if (!in_array($attribute->name,$allowed,true)) $child->removeAttribute($attribute->name);
foreach (['href','src'] as $attribute) if ($child->hasAttribute($attribute)) $safeUrl($child->getAttribute($attribute));
}
$prepare($child,$depth+1);
}
};
$prepare($fragment);
$slots=[]; $prefix='BENNUCODE'.hash('sha256',$input);
$protect=function ($node) use (&$protect,&$slots,$prefix) {
foreach (iterator_to_array($node->childNodes) as $child) {
if (!$child instanceof DOMElement) continue;
$tag=strtolower($child->tagName);
if ($tag==='pre' || $tag==='code') {
$value=$child->textContent;
if ($tag==='pre' && str_ends_with($value,"\n")) $value=substr($value,0,-1);
$runs=[]; preg_match_all('/'.chr(96).'+/',$value,$runs);
$longest=$runs[0] ? max(array_map('strlen',$runs[0])) : 0;
$fence=str_repeat(chr(96),max($tag==='pre' ? 3 : 1,$longest+1));
$language='';
if ($tag==='pre') {
$code=$child->getElementsByTagName('code')->item(0);
if ($code && preg_match('/(?:^|\s)language-([a-z0-9_+-]+)(?:\s|$)/i',$code->getAttribute('class'),$m)) $language=$m[1];
}
$token=$prefix.count($slots).'END';
$slots[$token]=$tag==='pre' ? $fence.$language."\n".$value."\n".$fence : $fence.$value.$fence;
$replacement=$child->ownerDocument->createElement($tag==='pre' ? 'p' : 'span');
$replacement->appendChild($child->ownerDocument->createTextNode($token)); $node->replaceChild($replacement,$child);
} else {
$protect($child);
if (in_array($tag,['del','s','strike'],true)) {
$child->insertBefore($child->ownerDocument->createTextNode('~~'),$child->firstChild);
$child->appendChild($child->ownerDocument->createTextNode('~~'));
}
}
}
};
$protect($fragment);
$converter=new HtmlConverter(['header_style'=>'atx','strip_tags'=>true,'list_item_style'=>'-','use_autolinks'=>false]);
$converter->getEnvironment()->addConverter(new TableConverter());
$previous=libxml_use_internal_errors(true);
try { $output=$converter->convert($html->saveHTML($fragment)); }
finally { libxml_clear_errors(); libxml_use_internal_errors($previous); }
$output=strtr($output,$slots);
return trim($output,"\n")==='' ? '' : trim($output,"\n")."\n";
}