HTMLAdaptor.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2018-2022 The MathJax Consortium
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /**
  18. * @fileoverview Implements the HTML DOM adaptor
  19. *
  20. * @author dpvc@mathjax.org (Davide Cervone)
  21. */
  22. import {OptionList} from '../util/Options.js';
  23. import {AttributeData, AbstractDOMAdaptor, DOMAdaptor, PageBBox} from '../core/DOMAdaptor.js';
  24. /*****************************************************************/
  25. /**
  26. * The minimum fields needed for a Document
  27. *
  28. * @template N The HTMLElement node class
  29. * @template T The Text node class
  30. */
  31. export interface MinDocument<N, T> {
  32. documentElement: N;
  33. head: N;
  34. body: N;
  35. title: string;
  36. doctype: {name: string};
  37. /* tslint:disable:jsdoc-require */
  38. createElement(kind: string): N;
  39. createElementNS(ns: string, kind: string): N;
  40. createTextNode(text: string): T;
  41. querySelectorAll(selector: string): ArrayLike<N>;
  42. /* tslint:enable */
  43. }
  44. /*****************************************************************/
  45. /**
  46. * The minimum fields needed for an HTML Element
  47. *
  48. * @template N The HTMLElement node class
  49. * @template T The Text node class
  50. */
  51. export interface MinHTMLElement<N, T> {
  52. nodeType: number;
  53. nodeName: string;
  54. nodeValue: string;
  55. textContent: string;
  56. innerHTML: string;
  57. outerHTML: string;
  58. parentNode: N | Node;
  59. nextSibling: N | T | Node;
  60. previousSibling: N | T | Node;
  61. offsetWidth: number;
  62. offsetHeight: number;
  63. attributes: AttributeData[] | NamedNodeMap;
  64. className: string;
  65. classList: DOMTokenList;
  66. style: OptionList;
  67. sheet?: {insertRule: (rule: string, index?: number) => void};
  68. childNodes: (N | T)[] | NodeList;
  69. firstChild: N | T | Node;
  70. lastChild: N | T | Node;
  71. /* tslint:disable:jsdoc-require */
  72. getElementsByTagName(name: string): N[] | HTMLCollectionOf<Element>;
  73. getElementsByTagNameNS(ns: string, name: string): N[] | HTMLCollectionOf<Element>;
  74. contains(child: N | T): boolean;
  75. appendChild(child: N | T): N | T | Node;
  76. removeChild(child: N | T): N | T | Node;
  77. replaceChild(nnode: N | T, onode: N | T): N | T | Node;
  78. insertBefore(nchild: N | T, ochild: N | T): void;
  79. cloneNode(deep: boolean): N | Node;
  80. setAttribute(name: string, value: string): void;
  81. setAttributeNS(ns: string, name: string, value: string): void;
  82. getAttribute(name: string): string;
  83. removeAttribute(name: string): void;
  84. hasAttribute(name: string): boolean;
  85. getBoundingClientRect(): Object;
  86. getBBox?(): {x: number, y: number, width: number, height: number};
  87. /* tslint:endable */
  88. }
  89. /*****************************************************************/
  90. /**
  91. * The minimum fields needed for a Text element
  92. *
  93. * @template N The HTMLElement node class
  94. * @template T The Text node class
  95. */
  96. export interface MinText<N, T> {
  97. nodeType: number;
  98. nodeName: string;
  99. nodeValue: string;
  100. parentNode: N | Node;
  101. nextSibling: N | T | Node;
  102. previousSibling: N | T | Node;
  103. splitText(n: number): T;
  104. }
  105. /*****************************************************************/
  106. /**
  107. * The minimum fields needed for a DOMParser
  108. *
  109. * @template D The Document class
  110. */
  111. export interface MinDOMParser<D> {
  112. parseFromString(text: string, format?: string): D;
  113. }
  114. /*****************************************************************/
  115. /**
  116. * The minimum fields needed for a DOMParser
  117. *
  118. * @template N The HTMLElement node class
  119. */
  120. export interface MinXMLSerializer<N> {
  121. serializeToString(node: N): string;
  122. }
  123. /*****************************************************************/
  124. /**
  125. * The minimum fields needed for a Window
  126. *
  127. * @template N The HTMLElement node class
  128. * @template D The Document class
  129. */
  130. export interface MinWindow<N, D> {
  131. document: D;
  132. DOMParser: {
  133. new(): MinDOMParser<D>
  134. };
  135. XMLSerializer: {
  136. new(): MinXMLSerializer<N>;
  137. };
  138. NodeList: any;
  139. HTMLCollection: any;
  140. HTMLElement: any;
  141. DocumentFragment: any;
  142. Document: any;
  143. getComputedStyle(node: N): any;
  144. }
  145. /*****************************************************************/
  146. /**
  147. * The minimum needed for an HTML Adaptor
  148. *
  149. * @template N The HTMLElement node class
  150. * @template T The Text node class
  151. * @template D The Document class
  152. */
  153. export interface MinHTMLAdaptor<N, T, D> extends DOMAdaptor<N, T, D> {
  154. window: MinWindow<N, D>;
  155. }
  156. /*****************************************************************/
  157. /**
  158. * Abstract HTMLAdaptor class for manipulating HTML elements
  159. * (subclass of AbstractDOMAdaptor)
  160. *
  161. * N = HTMLElement node class
  162. * T = Text node class
  163. * D = Document class
  164. *
  165. * @template N The HTMLElement node class
  166. * @template T The Text node class
  167. * @template D The Document class
  168. */
  169. export class HTMLAdaptor<N extends MinHTMLElement<N, T>, T extends MinText<N, T>, D extends MinDocument<N, T>> extends
  170. AbstractDOMAdaptor<N, T, D> implements MinHTMLAdaptor<N, T, D> {
  171. /**
  172. * The window object for this adaptor
  173. */
  174. public window: MinWindow<N, D>;
  175. /**
  176. * The DOMParser used to parse a string into a DOM tree
  177. */
  178. public parser: MinDOMParser<D>;
  179. /**
  180. * @override
  181. * @constructor
  182. */
  183. constructor(window: MinWindow<N, D>) {
  184. super(window.document);
  185. this.window = window;
  186. this.parser = new (window.DOMParser as any)();
  187. }
  188. /**
  189. * @override
  190. */
  191. public parse(text: string, format: string = 'text/html') {
  192. return this.parser.parseFromString(text, format);
  193. }
  194. /**
  195. * @override
  196. */
  197. protected create(kind: string, ns?: string) {
  198. return (ns ?
  199. this.document.createElementNS(ns, kind) :
  200. this.document.createElement(kind));
  201. }
  202. /**
  203. * @override
  204. */
  205. public text(text: string) {
  206. return this.document.createTextNode(text);
  207. }
  208. /**
  209. * @override
  210. */
  211. public head(doc: D) {
  212. return doc.head || (doc as any as N);
  213. }
  214. /**
  215. * @override
  216. */
  217. public body(doc: D) {
  218. return doc.body || (doc as any as N);
  219. }
  220. /**
  221. * @override
  222. */
  223. public root(doc: D) {
  224. return doc.documentElement || (doc as any as N);
  225. }
  226. /**
  227. * @override
  228. */
  229. public doctype(doc: D) {
  230. return (doc.doctype ? `<!DOCTYPE ${doc.doctype.name}>` : '');
  231. }
  232. /**
  233. * @override
  234. */
  235. public tags(node: N, name: string, ns: string = null) {
  236. let nodes = (ns ? node.getElementsByTagNameNS(ns, name) : node.getElementsByTagName(name));
  237. return Array.from(nodes as N[]) as N[];
  238. }
  239. /**
  240. * @override
  241. */
  242. public getElements(nodes: (string | N | N[])[], _document: D) {
  243. let containers: N[] = [];
  244. for (const node of nodes) {
  245. if (typeof(node) === 'string') {
  246. containers = containers.concat(Array.from(this.document.querySelectorAll(node)));
  247. } else if (Array.isArray(node)) {
  248. containers = containers.concat(Array.from(node) as N[]);
  249. } else if (node instanceof this.window.NodeList || node instanceof this.window.HTMLCollection) {
  250. containers = containers.concat(Array.from(node as any as N[]));
  251. } else {
  252. containers.push(node);
  253. }
  254. }
  255. return containers;
  256. }
  257. /**
  258. * @override
  259. */
  260. public contains(container: N, node: N | T) {
  261. return container.contains(node);
  262. }
  263. /**
  264. * @override
  265. */
  266. public parent(node: N | T) {
  267. return node.parentNode as N;
  268. }
  269. /**
  270. * @override
  271. */
  272. public append(node: N, child: N | T) {
  273. return node.appendChild(child) as N | T;
  274. }
  275. /**
  276. * @override
  277. */
  278. public insert(nchild: N | T, ochild: N | T) {
  279. return this.parent(ochild).insertBefore(nchild, ochild);
  280. }
  281. /**
  282. * @override
  283. */
  284. public remove(child: N | T) {
  285. return this.parent(child).removeChild(child) as N | T;
  286. }
  287. /**
  288. * @override
  289. */
  290. public replace(nnode: N | T, onode: N | T) {
  291. return this.parent(onode).replaceChild(nnode, onode) as N | T;
  292. }
  293. /**
  294. * @override
  295. */
  296. public clone(node: N) {
  297. return node.cloneNode(true) as N;
  298. }
  299. /**
  300. * @override
  301. */
  302. public split(node: T, n: number) {
  303. return node.splitText(n);
  304. }
  305. /**
  306. * @override
  307. */
  308. public next(node: N | T) {
  309. return node.nextSibling as N | T;
  310. }
  311. /**
  312. * @override
  313. */
  314. public previous(node: N | T) {
  315. return node.previousSibling as N | T;
  316. }
  317. /**
  318. * @override
  319. */
  320. public firstChild(node: N) {
  321. return node.firstChild as N | T;
  322. }
  323. /**
  324. * @override
  325. */
  326. public lastChild(node: N) {
  327. return node.lastChild as N | T;
  328. }
  329. /**
  330. * @override
  331. */
  332. public childNodes(node: N) {
  333. return Array.from(node.childNodes as (N | T)[]);
  334. }
  335. /**
  336. * @override
  337. */
  338. public childNode(node: N, i: number) {
  339. return node.childNodes[i] as N | T;
  340. }
  341. /**
  342. * @override
  343. */
  344. public kind(node: N | T) {
  345. const n = node.nodeType;
  346. return (n === 1 || n === 3 || n === 8 ? node.nodeName.toLowerCase() : '');
  347. }
  348. /**
  349. * @override
  350. */
  351. public value(node: N | T) {
  352. return node.nodeValue || '';
  353. }
  354. /**
  355. * @override
  356. */
  357. public textContent(node: N) {
  358. return node.textContent;
  359. }
  360. /**
  361. * @override
  362. */
  363. public innerHTML(node: N) {
  364. return node.innerHTML;
  365. }
  366. /**
  367. * @override
  368. */
  369. public outerHTML(node: N) {
  370. return node.outerHTML;
  371. }
  372. public serializeXML(node: N) {
  373. const serializer = new this.window.XMLSerializer();
  374. return serializer.serializeToString(node) as string;
  375. }
  376. /**
  377. * @override
  378. */
  379. public setAttribute(node: N, name: string, value: string, ns: string = null) {
  380. if (!ns) {
  381. return node.setAttribute(name, value);
  382. }
  383. name = ns.replace(/.*\//, '') + ':' + name.replace(/^.*:/, '');
  384. return node.setAttributeNS(ns, name, value);
  385. }
  386. /**
  387. * @override
  388. */
  389. public getAttribute(node: N, name: string) {
  390. return node.getAttribute(name);
  391. }
  392. /**
  393. * @override
  394. */
  395. public removeAttribute(node: N, name: string) {
  396. return node.removeAttribute(name);
  397. }
  398. /**
  399. * @override
  400. */
  401. public hasAttribute(node: N, name: string) {
  402. return node.hasAttribute(name);
  403. }
  404. /**
  405. * @override
  406. */
  407. public allAttributes(node: N) {
  408. return Array.from(node.attributes).map(
  409. (x: AttributeData) => {
  410. return {name: x.name, value: x.value} as AttributeData;
  411. }
  412. );
  413. }
  414. /**
  415. * @override
  416. */
  417. public addClass(node: N, name: string) {
  418. if (node.classList) {
  419. node.classList.add(name);
  420. } else {
  421. node.className = (node.className + ' ' + name).trim();
  422. }
  423. }
  424. /**
  425. * @override
  426. */
  427. public removeClass(node: N, name: string) {
  428. if (node.classList) {
  429. node.classList.remove(name);
  430. } else {
  431. node.className = node.className.split(/ /).filter((c) => c !== name).join(' ');
  432. }
  433. }
  434. /**
  435. * @override
  436. */
  437. public hasClass(node: N, name: string) {
  438. if (node.classList) {
  439. return node.classList.contains(name);
  440. }
  441. return node.className.split(/ /).indexOf(name) >= 0;
  442. }
  443. /**
  444. * @override
  445. */
  446. public setStyle(node: N, name: string, value: string) {
  447. (node.style as OptionList)[name] = value;
  448. }
  449. /**
  450. * @override
  451. */
  452. public getStyle(node: N, name: string) {
  453. return (node.style as OptionList)[name];
  454. }
  455. /**
  456. * @override
  457. */
  458. public allStyles(node: N) {
  459. return node.style.cssText;
  460. }
  461. /**
  462. * @override
  463. */
  464. public insertRules(node: N, rules: string[]) {
  465. for (const rule of rules.reverse()) {
  466. try {
  467. node.sheet.insertRule(rule, 0);
  468. } catch (e) {
  469. console.warn(`MathJax: can't insert css rule '${rule}': ${e.message}`);
  470. }
  471. }
  472. }
  473. /**
  474. * @override
  475. */
  476. public fontSize(node: N) {
  477. const style = this.window.getComputedStyle(node);
  478. return parseFloat(style.fontSize);
  479. }
  480. /**
  481. * @override
  482. */
  483. public fontFamily(node: N) {
  484. const style = this.window.getComputedStyle(node);
  485. return style.fontFamily || '';
  486. }
  487. /**
  488. * @override
  489. */
  490. public nodeSize(node: N, em: number = 1, local: boolean = false) {
  491. if (local && node.getBBox) {
  492. let {width, height} = node.getBBox();
  493. return [width / em , height / em] as [number, number];
  494. }
  495. return [node.offsetWidth / em, node.offsetHeight / em] as [number, number];
  496. }
  497. /**
  498. * @override
  499. */
  500. public nodeBBox(node: N) {
  501. const {left, right, top, bottom} = node.getBoundingClientRect() as PageBBox;
  502. return {left, right, top, bottom};
  503. }
  504. }