checkSanitizeOptions.mjs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import crypto from 'node:crypto'
  2. import fs from 'node:fs'
  3. import Path from 'node:path'
  4. import cheerio from 'cheerio'
  5. // checkSanitizeOptions is only used in dev env
  6. // eslint-disable-next-line import/no-extraneous-dependencies
  7. import * as prettier from 'prettier'
  8. import sanitizeHtml from 'sanitize-html'
  9. import { sanitizeOptions } from '../../../modules/learn/app/src/sanitizeOptions.mjs'
  10. import { fileURLToPath } from 'node:url'
  11. const __dirname = Path.dirname(fileURLToPath(import.meta.url))
  12. const EXTRACT_STYLE = process.env.EXTRACT_STYLES === 'true'
  13. const OMIT_STYLE = process.env.OMIT_STYLE !== 'false'
  14. const DUMP_CSS_IN = Path.join(
  15. Path.dirname(Path.dirname(Path.dirname(__dirname))),
  16. 'data',
  17. 'dumpFolder'
  18. )
  19. function hash(blob) {
  20. return crypto.createHash('sha1').update(blob).digest('hex')
  21. }
  22. function normalize(blob, title) {
  23. // styles are dropped in web and kept in wiki pages for previewing there.
  24. blob = blob.replace(/<style>(.+?)<\/style>/gs, (_, match) => {
  25. if (EXTRACT_STYLE) {
  26. // normalize css with prettier
  27. const css = prettier.format(match, { parser: 'css' })
  28. fs.writeFileSync(
  29. Path.join(DUMP_CSS_IN, `${hash(css)}-${encodeURIComponent(title)}.css`),
  30. `/* title: ${title} */\n\n${css}`
  31. )
  32. }
  33. if (OMIT_STYLE) {
  34. return ''
  35. }
  36. return match
  37. })
  38. // strip comments:
  39. // - comment at the bottom of each page
  40. blob = blob.replace(/<!-- \nNewPP limit report.+/s, '')
  41. // - annotation of math characters
  42. blob = blob.replace(/<!-- . -->/g, '')
  43. // wrap for consistent rendering
  44. if (blob.indexOf('<html><head>') !== 0) {
  45. blob = `<html><head>${blob}</head></html>`
  46. }
  47. // normalize inline style:
  48. // - drop trailing ;
  49. blob = blob.replace(/style="([^"]+);"/g, (_, style) => `style="${style}"`)
  50. // - normalize whitespace
  51. blob = blob.replace(
  52. /style="([^"]+)"/g,
  53. (_, style) => `style="${style.trim().replace(/([:;])\s+/g, '$1')}"`
  54. )
  55. // let cherrio do another pass
  56. return cheerio.load(blob).html()
  57. }
  58. function toText(blob) {
  59. return cheerio.load(blob).text()
  60. }
  61. const zoomOut = 50
  62. function peak(content, offset) {
  63. // show some more content before/after the mismatch
  64. if (offset > zoomOut) {
  65. offset -= zoomOut
  66. }
  67. // wrap in JSON to escape new line characters
  68. return JSON.stringify(content.slice(offset, offset + chunkSize + 2 * zoomOut))
  69. }
  70. const chunkSize = 100
  71. function findFirstMismatch(a, b) {
  72. if (a === b) return a.length
  73. let i = 0
  74. while (
  75. a.length > chunkSize &&
  76. b.length > chunkSize &&
  77. a.slice(0, chunkSize) === b.slice(0, chunkSize)
  78. ) {
  79. i++
  80. a = a.slice(chunkSize)
  81. b = b.slice(chunkSize)
  82. }
  83. return i * chunkSize
  84. }
  85. function checkSanitizeOptions(page, title, text) {
  86. text = normalize(text, title)
  87. const sanitized = normalize(sanitizeHtml(text, sanitizeOptions))
  88. if (text === sanitized) return
  89. const offset = findFirstMismatch(text, sanitized)
  90. const textToText = toText(text)
  91. const sanitizedToText = toText(sanitized)
  92. const offsetText = findFirstMismatch(textToText, sanitizedToText)
  93. console.error('---')
  94. console.error('page :', page)
  95. console.error('title :', title)
  96. console.error('match :', text === sanitized)
  97. console.error('toText :', toText(text) === toText(sanitized))
  98. console.error('text :', peak(text, offset))
  99. console.error('sanitized :', peak(sanitized, offset))
  100. console.error('textToText :', peak(textToText, offsetText))
  101. console.error('sanitizedToText:', peak(sanitizedToText, offsetText))
  102. }
  103. export default checkSanitizeOptions