RequireConfiguration.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2019-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 file for the require package.
  19. *
  20. * @author dpvc@mathjax.org (Davide P. Cervone)
  21. */
  22. import {Configuration, ParserConfiguration, ConfigurationHandler} from '../Configuration.js';
  23. import TexParser from '../TexParser.js';
  24. import {CommandMap} from '../SymbolMap.js';
  25. import {ParseMethod} from '../Types.js';
  26. import TexError from '../TexError.js';
  27. import {TeX} from '../../tex.js';
  28. import {MathJax} from '../../../components/global.js';
  29. import {Package} from '../../../components/package.js';
  30. import {Loader, CONFIG as LOADERCONFIG} from '../../../components/loader.js';
  31. import {mathjax} from '../../../mathjax.js';
  32. import {expandable} from '../../../util/Options.js';
  33. /**
  34. * The MathJax configuration block (for looking up user-defined package options)
  35. */
  36. const MJCONFIG = MathJax.config;
  37. /**
  38. * Add an extension to the configuration, and configure its user options
  39. *
  40. * @param {TeX} jax The TeX jax whose configuration is to be modified
  41. * @param {string} name The name of the extension being added (e.g., '[tex]/amscd')
  42. */
  43. function RegisterExtension(jax: TeX<any, any, any>, name: string) {
  44. const require = jax.parseOptions.options.require;
  45. const required = jax.parseOptions.packageData.get('require').required as string[];
  46. const extension = name.substr(require.prefix.length);
  47. if (required.indexOf(extension) < 0) {
  48. required.push(extension);
  49. //
  50. // Register any dependencies that were loaded to handle this one
  51. //
  52. RegisterDependencies(jax, LOADERCONFIG.dependencies[name]);
  53. //
  54. // If the required file loaded an extension...
  55. //
  56. const handler = ConfigurationHandler.get(extension);
  57. if (handler) {
  58. //
  59. // Check if there are user-supplied options
  60. // (place them in a block for the extension, if needed)
  61. //
  62. let options = MJCONFIG[name] || {};
  63. if (handler.options && Object.keys(handler.options).length === 1 && handler.options[extension]) {
  64. options = {[extension]: options};
  65. }
  66. //
  67. // Register the extension with the jax's configuration
  68. //
  69. (jax as any).configuration.add(extension, jax, options);
  70. //
  71. // If there are preprocessors, restart so that they run
  72. // (we don't have access to the document or MathItem needed to call
  73. // the preprocessors from here)
  74. //
  75. const configured = jax.parseOptions.packageData.get('require').configured;
  76. if (handler.preprocessors.length && !configured.has(extension)) {
  77. configured.set(extension, true);
  78. mathjax.retryAfter(Promise.resolve());
  79. }
  80. }
  81. }
  82. }
  83. /**
  84. * Register any dependencies for the loaded extension
  85. *
  86. * @param {TeX} jax The jax whose configuration is being modified
  87. * @param {string[]} names The names of the dependencies to register
  88. */
  89. function RegisterDependencies(jax: TeX<any, any, any>, names: string[] = []) {
  90. const prefix = jax.parseOptions.options.require.prefix;
  91. for (const name of names) {
  92. if (name.substr(0, prefix.length) === prefix) {
  93. RegisterExtension(jax, name);
  94. }
  95. }
  96. }
  97. /**
  98. * Load a required package
  99. *
  100. * @param {TexParser} parser The current tex parser.
  101. * @param {string} name The name of the package to load.
  102. */
  103. export function RequireLoad(parser: TexParser, name: string) {
  104. const options = parser.options.require;
  105. const allow = options.allow;
  106. const extension = (name.substr(0, 1) === '[' ? '' : options.prefix) + name;
  107. const allowed = (allow.hasOwnProperty(extension) ? allow[extension] :
  108. allow.hasOwnProperty(name) ? allow[name] : options.defaultAllow);
  109. if (!allowed) {
  110. throw new TexError('BadRequire', 'Extension "%1" is not allowed to be loaded', extension);
  111. }
  112. if (Package.packages.has(extension)) {
  113. RegisterExtension(parser.configuration.packageData.get('require').jax, extension);
  114. } else {
  115. mathjax.retryAfter(Loader.load(extension));
  116. }
  117. }
  118. /**
  119. * Save the jax so that it can be used when \require{} is processed.
  120. */
  121. function config(_config: ParserConfiguration, jax: TeX<any, any, any>) {
  122. jax.parseOptions.packageData.set('require', {
  123. jax: jax, // \require needs access to this
  124. required: [...jax.options.packages], // stores the names of the packages that have been added
  125. configured: new Map() // stores the packages that have been configured
  126. });
  127. const options = jax.parseOptions.options.require;
  128. const prefix = options.prefix;
  129. if (prefix.match(/[^_a-zA-Z0-9]/)) {
  130. throw Error('Illegal characters used in \\require prefix');
  131. }
  132. if (!LOADERCONFIG.paths[prefix]) {
  133. LOADERCONFIG.paths[prefix] = '[mathjax]/input/tex/extensions';
  134. }
  135. options.prefix = '[' + prefix + ']/';
  136. }
  137. /**
  138. * Namespace for \require methods
  139. */
  140. export const RequireMethods: Record<string, ParseMethod> = {
  141. /**
  142. * Implements \require macro to load TeX extensions
  143. *
  144. * @param {TexParser} parser The current tex parser.
  145. * @param {string} name The name of the calling macro.
  146. */
  147. Require(parser: TexParser, name: string) {
  148. const required = parser.GetArgument(name);
  149. if (required.match(/[^_a-zA-Z0-9]/) || required === '') {
  150. throw new TexError('BadPackageName', 'Argument for %1 is not a valid package name', name);
  151. }
  152. RequireLoad(parser, required);
  153. }
  154. };
  155. /**
  156. * The options for the require extension
  157. */
  158. export const options = {
  159. require: {
  160. //
  161. // Specifies which extensions can/can't be required.
  162. // The keys are the names of extensions, and the value is true
  163. // if the extension can be required, and false if it can't
  164. //
  165. allow: expandable({
  166. base: false,
  167. 'all-packages': false,
  168. autoload: false,
  169. configmacros: false,
  170. tagformat: false,
  171. setoptions: false
  172. }),
  173. //
  174. // The default allow value if the extension isn't in the list above
  175. //
  176. defaultAllow: true,
  177. //
  178. // The path prefix to use for exensions: 'tex' means use '[tex]/'
  179. // before the extension name.
  180. //
  181. prefix: 'tex'
  182. }
  183. };
  184. /**
  185. * The command map for the \require macro
  186. */
  187. new CommandMap('require', {require: 'Require'}, RequireMethods);
  188. /**
  189. * The configuration for the \require macro
  190. */
  191. export const RequireConfiguration = Configuration.create(
  192. 'require', {handler: {macro: ['require']}, config, options}
  193. );