Configuration.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  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 Configuration options for the TexParser.
  19. *
  20. * @author v.sorge@mathjax.org (Volker Sorge)
  21. */
  22. import {HandlerConfig, FallbackConfig} from './MapHandler.js';
  23. import {StackItemClass} from './StackItem.js';
  24. import {TagsClass} from './Tags.js';
  25. import {userOptions, defaultOptions, OptionList} from '../../util/Options.js';
  26. import {SubHandlers} from './MapHandler.js';
  27. import {FunctionList} from '../../util/FunctionList.js';
  28. import {TeX} from '../tex.js';
  29. import {PrioritizedList} from '../../util/PrioritizedList.js';
  30. import {TagsFactory} from './Tags.js';
  31. export type StackItemConfig = {[kind: string]: StackItemClass};
  32. export type TagsConfig = {[kind: string]: TagsClass};
  33. export type Processor<T> = [T, number];
  34. export type ProtoProcessor<T> = Processor<T> | T;
  35. export type ProcessorList = Processor<Function>[];
  36. export type ConfigMethod = (c: ParserConfiguration, j: TeX<any, any, any>) => void;
  37. export type InitMethod = (c: ParserConfiguration) => void;
  38. export class Configuration {
  39. /**
  40. * Creates a function priority pair.
  41. * @param {ProtoProcessor<T>} func The function or processor.
  42. * @param {number} priority The default priority.
  43. * @return {Processor} The processor pair.
  44. * @template T
  45. */
  46. private static makeProcessor<T>(func: ProtoProcessor<T>, priority: number): Processor<T> {
  47. return Array.isArray(func) ? func : [func, priority];
  48. }
  49. /**
  50. * Creates a configuration for a package.
  51. * @param {string} name The package name or empty string.
  52. * @param {Object} config See `create` method.
  53. * @return {Configuration} The newly generated configuration.
  54. */
  55. private static _create(name: string,
  56. config: {handler?: HandlerConfig,
  57. fallback?: FallbackConfig,
  58. items?: StackItemConfig,
  59. tags?: TagsConfig,
  60. options?: OptionList,
  61. nodes?: {[key: string]: any},
  62. preprocessors?: ProtoProcessor<Function>[],
  63. postprocessors?: ProtoProcessor<Function>[],
  64. init?: ProtoProcessor<InitMethod>,
  65. config?: ProtoProcessor<ConfigMethod>,
  66. priority?: number,
  67. parser?: string,
  68. } = {}): Configuration {
  69. let priority = config.priority || PrioritizedList.DEFAULTPRIORITY;
  70. let init = config.init ? this.makeProcessor(config.init, priority) : null;
  71. let conf = config.config ? this.makeProcessor(config.config, priority) : null;
  72. let preprocessors = (config.preprocessors || []).map(
  73. pre => this.makeProcessor(pre, priority));
  74. let postprocessors = (config.postprocessors || []).map(
  75. post => this.makeProcessor(post, priority));
  76. let parser = config.parser || 'tex';
  77. return new Configuration(
  78. name,
  79. config.handler || {},
  80. config.fallback || {},
  81. config.items || {},
  82. config.tags || {},
  83. config.options || {},
  84. config.nodes || {},
  85. preprocessors, postprocessors, init, conf, priority,
  86. parser
  87. );
  88. }
  89. /**
  90. * Creator pattern for creating a named package configuration. This will be
  91. * administered in the configuration handler and can be retrieved again.
  92. * @param {string} name The package name.
  93. * @param {Object} config The configuration parameters:
  94. * Configuration for the TexParser consist of the following:
  95. * * _handler_ configuration mapping handler types to lists of symbol mappings.
  96. * * _fallback_ configuration mapping handler types to fallback methods.
  97. * * _items_ for the StackItem factory.
  98. * * _tags_ mapping tagging configurations to tagging objects.
  99. * * _options_ parse options for the packages.
  100. * * _nodes_ for the Node factory.
  101. * * _preprocessors_ list of functions for preprocessing the LaTeX
  102. * string wrt. to given parse options. Can contain a priority.
  103. * * _postprocessors_ list of functions for postprocessing the MmlNode
  104. * wrt. to given parse options. Can contain a priority.
  105. * * _init_ init method and optionally its priority.
  106. * * _config_ config method and optionally its priority.
  107. * * _priority_ default priority of the configuration.
  108. * * _parser_ the name of the parser that this configuration targets.
  109. * @return {Configuration} The newly generated configuration.
  110. */
  111. public static create(name: string,
  112. config: {handler?: HandlerConfig,
  113. fallback?: FallbackConfig,
  114. items?: StackItemConfig,
  115. tags?: TagsConfig,
  116. options?: OptionList,
  117. nodes?: {[key: string]: any},
  118. preprocessors?: ProtoProcessor<Function>[],
  119. postprocessors?: ProtoProcessor<Function>[],
  120. init?: ProtoProcessor<InitMethod>,
  121. config?: ProtoProcessor<ConfigMethod>,
  122. priority?: number,
  123. parser?: string,
  124. } = {}): Configuration {
  125. let configuration = Configuration._create(name, config);
  126. ConfigurationHandler.set(name, configuration);
  127. return configuration;
  128. }
  129. /**
  130. * Creates an unnamed, ephemeral package configuration. It will not added to
  131. * the configuration handler.
  132. * @param {Object} config See `create` method.
  133. * @return {Configuration} The ephemeral package configuration.
  134. */
  135. public static local(config: {handler?: HandlerConfig,
  136. fallback?: FallbackConfig,
  137. items?: StackItemConfig,
  138. tags?: TagsConfig,
  139. options?: OptionList,
  140. nodes?: {[key: string]: any},
  141. preprocessors?: ProtoProcessor<Function>[],
  142. postprocessors?: ProtoProcessor<Function>[],
  143. init?: ProtoProcessor<InitMethod>,
  144. config?: ProtoProcessor<ConfigMethod>,
  145. priority?: number,
  146. parser?: string,
  147. } = {}): Configuration {
  148. return Configuration._create('', config);
  149. }
  150. /**
  151. * @constructor
  152. */
  153. private constructor(readonly name: string,
  154. readonly handler: HandlerConfig = {},
  155. readonly fallback: FallbackConfig = {},
  156. readonly items: StackItemConfig = {},
  157. readonly tags: TagsConfig = {},
  158. readonly options: OptionList = {},
  159. readonly nodes: {[key: string]: any} = {},
  160. readonly preprocessors: ProcessorList = [],
  161. readonly postprocessors: ProcessorList = [],
  162. readonly initMethod: Processor<InitMethod> = null,
  163. readonly configMethod: Processor<ConfigMethod> = null,
  164. public priority: number,
  165. readonly parser: string
  166. ) {
  167. this.handler = Object.assign(
  168. {character: [], delimiter: [], macro: [], environment: []}, handler);
  169. }
  170. /**
  171. * The init method.
  172. * @type {Function}
  173. */
  174. public get init(): InitMethod {
  175. return this.initMethod ? this.initMethod[0] : null;
  176. }
  177. /**
  178. * The config method to call once jax is ready.
  179. * @type {FunctionList}
  180. */
  181. public get config(): ConfigMethod {
  182. return this.configMethod ? this.configMethod[0] : null;
  183. }
  184. }
  185. export namespace ConfigurationHandler {
  186. let maps: Map<string, Configuration> = new Map();
  187. /**
  188. * Adds a new configuration to the handler overwriting old ones.
  189. *
  190. * @param {string} name The name of the configuration.
  191. * @param {Configuration} map The configuration mapping.
  192. */
  193. export let set = function(name: string, map: Configuration): void {
  194. maps.set(name, map);
  195. };
  196. /**
  197. * Looks up a configuration.
  198. *
  199. * @param {string} name The name of the configuration.
  200. * @return {Configuration} The configuration with the given name or null.
  201. */
  202. export let get = function(name: string): Configuration {
  203. return maps.get(name);
  204. };
  205. /**
  206. * @return {string[]} All configurations in the handler.
  207. */
  208. export let keys = function(): IterableIterator<string> {
  209. return maps.keys();
  210. };
  211. }
  212. /**
  213. * Parser configuration combines the configurations of the currently selected
  214. * packages.
  215. * @constructor
  216. */
  217. export class ParserConfiguration {
  218. /**
  219. * Priority list of init methods.
  220. * @type {FunctionList}
  221. */
  222. protected initMethod: FunctionList = new FunctionList();
  223. /**
  224. * Priority list of init methods to call once jax is ready.
  225. * @type {FunctionList}
  226. */
  227. protected configMethod: FunctionList = new FunctionList();
  228. /**
  229. * An ordered list of cofigurations.
  230. * @type {PrioritizedList<Configuration>}
  231. */
  232. protected configurations: PrioritizedList<Configuration> = new PrioritizedList();
  233. /**
  234. * The list of parsers this configuration targets
  235. */
  236. protected parsers: string[] = [];
  237. /**
  238. * The subhandlers for this configuration.
  239. * @type {SubHandlers}
  240. */
  241. public handlers: SubHandlers = new SubHandlers();
  242. /**
  243. * The collated stack items.
  244. * @type {StackItemConfig}
  245. */
  246. public items: StackItemConfig = {};
  247. /**
  248. * The collated tag configurations.
  249. * @type {TagsConfig}
  250. */
  251. public tags: TagsConfig = {};
  252. /**
  253. * The collated options.
  254. * @type {OptionList}
  255. */
  256. public options: OptionList = {};
  257. /**
  258. * The collated node creators.
  259. * @type {{[key: string]: any}}
  260. */
  261. public nodes: {[key: string]: any} = {};
  262. /**
  263. * @constructor
  264. * @param {(string|[string,number])[]} packages A list of packages with
  265. * optional priorities.
  266. * @parm {string[]} parsers The names of the parsers this package targets
  267. */
  268. constructor(packages: (string | [string, number])[], parsers: string[] = ['tex']) {
  269. this.parsers = parsers;
  270. for (const pkg of packages.slice().reverse()) {
  271. this.addPackage(pkg);
  272. }
  273. for (let {item: config, priority: priority} of this.configurations) {
  274. this.append(config, priority);
  275. }
  276. }
  277. /**
  278. * Init method for the configuration;
  279. */
  280. public init() {
  281. this.initMethod.execute(this);
  282. }
  283. /**
  284. * Init method for when the jax is ready
  285. * @param {TeX} jax The TeX jax for this configuration
  286. */
  287. public config(jax: TeX<any, any, any>) {
  288. this.configMethod.execute(this, jax);
  289. for (const config of this.configurations) {
  290. this.addFilters(jax, config.item);
  291. }
  292. }
  293. /**
  294. * Retrieves and adds configuration for a package with priority.
  295. * @param {(string | [string, number]} pkg Package with priority.
  296. */
  297. public addPackage(pkg: (string | [string, number])) {
  298. const name = typeof pkg === 'string' ? pkg : pkg[0];
  299. const conf = this.getPackage(name);
  300. conf && this.configurations.add(conf, typeof pkg === 'string' ? conf.priority : pkg[1]);
  301. }
  302. /**
  303. * Adds a configuration after the input jax is created. (Used by \require.)
  304. * Sets items, nodes and runs configuration method explicitly.
  305. *
  306. * @param {string} name The name of the package to add
  307. * @param {TeX} jax The TeX jax where it is being registered
  308. * @param {OptionList=} options The options for the configuration.
  309. */
  310. public add(name: string, jax: TeX<any, any, any>, options: OptionList = {}) {
  311. const config = this.getPackage(name);
  312. this.append(config);
  313. this.configurations.add(config, config.priority);
  314. this.init();
  315. const parser = jax.parseOptions;
  316. parser.nodeFactory.setCreators(config.nodes);
  317. for (const kind of Object.keys(config.items)) {
  318. parser.itemFactory.setNodeClass(kind, config.items[kind]);
  319. }
  320. TagsFactory.addTags(config.tags);
  321. defaultOptions(parser.options, config.options);
  322. userOptions(parser.options, options);
  323. this.addFilters(jax, config);
  324. if (config.config) {
  325. config.config(this, jax);
  326. }
  327. }
  328. /**
  329. * Find a package and check that it is for the targeted parser
  330. *
  331. * @param {string} name The name of the package to check
  332. * @return {Configuration} The configuration for the package
  333. */
  334. protected getPackage(name: string): Configuration {
  335. const config = ConfigurationHandler.get(name);
  336. if (config && this.parsers.indexOf(config.parser) < 0) {
  337. throw Error(`Package ${name} doesn't target the proper parser`);
  338. }
  339. return config;
  340. }
  341. /**
  342. * Appends a configuration to the overall configuration object.
  343. * @param {Configuration} config A configuration.
  344. * @param {number} priority The configurations optional priority.
  345. */
  346. public append(config: Configuration, priority?: number) {
  347. priority = priority || config.priority;
  348. if (config.initMethod) {
  349. this.initMethod.add(config.initMethod[0], config.initMethod[1]);
  350. }
  351. if (config.configMethod) {
  352. this.configMethod.add(config.configMethod[0], config.configMethod[1]);
  353. }
  354. this.handlers.add(config.handler, config.fallback, priority);
  355. Object.assign(this.items, config.items);
  356. Object.assign(this.tags, config.tags);
  357. defaultOptions(this.options, config.options);
  358. Object.assign(this.nodes, config.nodes);
  359. }
  360. /**
  361. * Adds pre- and postprocessor as filters to the jax.
  362. * @param {TeX<any} jax The TeX Jax.
  363. * @param {Configuration} config The configuration whose processors are added.
  364. */
  365. private addFilters(jax: TeX<any, any, any>, config: Configuration) {
  366. for (const [pre, priority] of config.preprocessors) {
  367. jax.preFilters.add(pre, priority);
  368. }
  369. for (const [post, priority] of config.postprocessors) {
  370. jax.postFilters.add(post, priority);
  371. }
  372. }
  373. }