Ctrl K
ComunidadGuías…

lib/latex-escape.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * Shared LaTeX escaping for career-ops CV scripts.
 */

/**
 * Escape user text for insertion into LaTeX macro arguments.
 *
 * @param {string} text
 * @param {'text'|'url'} [mode='text']
 * @returns {string}
 */
export function escapeLatex(text, mode = 'text') {
  // Blank out only truly absent/structural values, and coerce scalars — the
  // same rule #2641 applied to escapeHtml, which this function mirrors.
  // `typeof text !== 'string' -> ''` silently dropped NUMBERS: a payload with
  // `year: 2019` or `dates: 2024` (JSON numbers, not strings) rendered an empty
  // \resumeSubheading date field while the section stayed present, so the .tex
  // shipped without employment dates or graduation years — and the builder
  // still reported "valid": true and exited 0.
  if (text === null || text === undefined || typeof text === 'object') return '';
  const value = String(text);
  if (mode === 'url') return value;
  const out = [];
  for (const ch of value) {
    switch (ch) {
      case '\\': out.push('\\textbackslash{}'); break;
      case '{': case '}': out.push('\\' + ch); break;
      case '^': out.push('\\textasciicircum{}'); break;
      case '~': out.push('\\textasciitilde{}'); break;
      case '_': out.push('\\_'); break;
      case '&': out.push('\\&'); break;
      case '%': out.push('\\%'); break;
      case '$': out.push('\\$'); break;
      case '#': out.push('\\#'); break;
      // With pdflatex's default OT1 encoding these three have no glyph in the
      // text font: < and > print as inverted ¡/¿ and | as an em-dash, so
      // "p99 <100ms" renders "p99 ¡100ms". The text commands are safe in all
      // encodings.
      case '<': out.push('\\textless{}'); break;
      case '>': out.push('\\textgreater{}'); break;
      case '|': out.push('\\textbar{}'); break;
      case '\u00B1': out.push('$\\pm$'); break;
      case '\u2192': out.push('$\\rightarrow$'); break;
      default: out.push(ch);
    }
  }
  return out.join('');
}

/**
 * Validate and normalize URLs for \\href{} (escaped for hyperref, not with
 * escapeLatex()).
 *
 * `%`, `#`, `~` and `&` are ordinary URL characters (percent-encoding,
 * fragments, `~user` paths, query strings), so they are escaped for hyperref,
 * which writes the literal characters into the link. The first three used to
 * be deleted, which pointed the link somewhere else: `example.com/test%C3%A9`
 * became `example.com/testC3A9`, while the HTML CV kept the real URL. `&`
 * went through raw, and build-cv-latex.mjs puts project links inside the
 * argument of \resumeProjectHeading, where a raw `&` (or `%`, or `#`) stops
 * pdflatex. \href accepts `\%`, `\#`, `\~` and `\&` both there and at top
 * level. `{ } \ ^ $` are still dropped.
 *
 * @param {string} url
 * @returns {string}
 */
export function sanitizeUrl(url) {
  if (typeof url !== 'string') return '';
  url = url.trim();
  if (!url) return '';
  const allowedSchemes = ['mailto:', 'http:', 'https:'];
  const hasScheme = allowedSchemes.some(s => url.toLowerCase().startsWith(s));
  if (!hasScheme) {
    if (url.includes('@') && !url.includes('/')) {
      url = 'mailto:' + url;
    } else {
      url = 'https://' + url;
    }
  }
  return url.replace(/[{}$\\^]/g, '').replace(/[%#~&]/g, (ch) => `\\${ch}`);
}