SetOptionsConfiguration.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2021-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 setoptions package.
  19. *
  20. * @author dpvc@mathjax.org (Davide P. Cervone)
  21. */
  22. import {Configuration, ConfigurationHandler, ParserConfiguration} from '../Configuration.js';
  23. import {TeX} from '../../tex.js';
  24. import TexParser from '../TexParser.js';
  25. import {CommandMap} from '../SymbolMap.js';
  26. import TexError from '../TexError.js';
  27. import ParseUtil from '../ParseUtil.js';
  28. import {Macro} from '../Symbol.js';
  29. import BaseMethods from '../base/BaseMethods.js';
  30. import {expandable, isObject} from '../../../util/Options.js';
  31. export const SetOptionsUtil = {
  32. /**
  33. * Check if options can be set for a given pacakge, and error otherwise.
  34. *
  35. * @param {TexParser} parser The active tex parser.
  36. * @param {string} extension The name of the package whose option is being set.
  37. * @return {boolean} True when options can be set for this package.
  38. */
  39. filterPackage(parser: TexParser, extension: string): boolean {
  40. if (extension !== 'tex' && !ConfigurationHandler.get(extension)) {
  41. throw new TexError('NotAPackage', 'Not a defined package: %1', extension);
  42. }
  43. const config = parser.options.setoptions;
  44. const options = config.allowOptions[extension];
  45. if ((options === undefined && !config.allowPackageDefault) || options === false) {
  46. throw new TexError('PackageNotSettable', 'Options can\'t be set for package "%1"', extension);
  47. }
  48. return true;
  49. },
  50. /**
  51. * Check if an option can be set and error otherwise.
  52. *
  53. * @param {TexParser} parser The active tex parser.
  54. * @param {string} extension The name of the package whose option is being set.
  55. * @param {string} option The name of the option being set.
  56. * @return {boolean} True when the option can be set.
  57. */
  58. filterOption(parser: TexParser, extension: string, option: string): boolean {
  59. const config = parser.options.setoptions;
  60. const options = config.allowOptions[extension] || {};
  61. const allow = (options.hasOwnProperty(option) && !isObject(options[option]) ? options[option] : null);
  62. if (allow === false || (allow === null && !config.allowOptionsDefault)) {
  63. throw new TexError('OptionNotSettable', 'Option "%1" is not allowed to be set', option);
  64. }
  65. if (!(extension === 'tex' ? parser.options : parser.options[extension])?.hasOwnProperty(option)) {
  66. if (extension === 'tex') {
  67. throw new TexError('InvalidTexOption', 'Invalid TeX option "%1"', option);
  68. } else {
  69. throw new TexError('InvalidOptionKey', 'Invalid option "%1" for package "%2"', option, extension);
  70. }
  71. }
  72. return true;
  73. },
  74. /**
  75. * Verify an option's value before setting it.
  76. *
  77. * @param {TexParser} parser The active tex parser.
  78. * @param {string} extension The name of the package whose option this is.
  79. * @param {string} option The name of the option being set.
  80. * @param {string} value The value to give to the option.
  81. * @return {string} The (possibly modified) value for the option
  82. */
  83. filterValue(_parser: TexParser, _extension: string, _option: string, value: string): string {
  84. return value;
  85. }
  86. };
  87. const setOptionsMap = new CommandMap('setoptions', {
  88. setOptions: 'SetOptions'
  89. }, {
  90. /**
  91. * Implements \setOptions[package]{option-values}
  92. *
  93. * @param {TexParser} parser The active tex parser.
  94. * @param {string} name The name of the macro being processed.
  95. */
  96. SetOptions(parser: TexParser, name: string) {
  97. const extension = parser.GetBrackets(name) || 'tex';
  98. const options = ParseUtil.keyvalOptions(parser.GetArgument(name));
  99. const config = parser.options.setoptions;
  100. if (!config.filterPackage(parser, extension)) return;
  101. for (const key of Object.keys(options)) {
  102. if (config.filterOption(parser, extension, key)) {
  103. (extension === 'tex' ? parser.options : parser.options[extension])[key] =
  104. config.filterValue(parser, extension, key, options[key]);
  105. }
  106. }
  107. }
  108. });
  109. /**
  110. * If the require package is available, save the original require,
  111. * and define a macro that loads the extension and sets
  112. * its options, if any.
  113. *
  114. * @param {ParserConfiguration} config The current configuration.
  115. * @param {TeX} jax The active tex input jax.
  116. */
  117. function setoptionsConfig(_config: ParserConfiguration, jax: TeX<any, any, any>) {
  118. const require = jax.parseOptions.handlers.get('macro').lookup('require') as any;
  119. if (require) {
  120. setOptionsMap.add('Require', new Macro('Require', require._func));
  121. setOptionsMap.add('require', new Macro('require', BaseMethods.Macro,
  122. ['\\Require{#2}\\setOptions[#2]{#1}', 2, '']));
  123. }
  124. }
  125. export const SetOptionsConfiguration = Configuration.create(
  126. 'setoptions', {
  127. handler: {macro: ['setoptions']},
  128. config: setoptionsConfig,
  129. priority: 3, // must be less than the priority of the require package (which is 5).
  130. options: {
  131. setoptions: {
  132. filterPackage: SetOptionsUtil.filterPackage, // filter for whether a package can be configured
  133. filterOption: SetOptionsUtil.filterOption, // filter for whether an option can be set
  134. filterValue: SetOptionsUtil.filterValue, // filter for the value to assign to an option
  135. allowPackageDefault: true, // default for allowing packages when not explicitly set in allowOptions
  136. allowOptionsDefault: true, // default for allowing option that isn't explicitly set in allowOptions
  137. allowOptions: expandable({ // list of packages to allow/disallow, and their options to allow/disallow
  138. //
  139. // top-level tex items can be set, but not these
  140. // (that leaves digits and the tagging options)
  141. //
  142. tex: {
  143. FindTeX: false,
  144. formatError: false,
  145. package: false,
  146. baseURL: false,
  147. tags: false,
  148. maxBuffer: false,
  149. maxMaxros: false,
  150. macros: false,
  151. environments: false
  152. },
  153. //
  154. // These packages can't be configured at all
  155. //
  156. setoptions: false,
  157. autoload: false,
  158. require: false,
  159. configmacros: false,
  160. tagformat: false
  161. })
  162. }
  163. }
  164. }
  165. );