CasesConfiguration.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import {Configuration} from '../Configuration.js';
  2. import {EnvironmentMap, MacroMap} from '../SymbolMap.js';
  3. import ParseUtil from '../ParseUtil.js';
  4. import BaseMethods from '../base/BaseMethods.js';
  5. import TexParser from '../TexParser.js';
  6. import TexError from '../TexError.js';
  7. import {BeginItem, EqnArrayItem} from '../base/BaseItems.js';
  8. import {AmsTags} from '../ams/AmsConfiguration.js';
  9. import {StackItem, CheckType} from '../StackItem.js';
  10. import {MmlMtable} from '../../../core/MmlTree/MmlNodes/mtable.js';
  11. import {EmpheqUtil} from '../empheq/EmpheqUtil.js';
  12. /**
  13. * The StackItem for the numcases environment.
  14. */
  15. export class CasesBeginItem extends BeginItem {
  16. /**
  17. * @override
  18. */
  19. get kind() {
  20. return 'cases-begin';
  21. }
  22. /**
  23. * @override
  24. */
  25. public checkItem(item: StackItem) {
  26. if (item.isKind('end') && item.getName() === this.getName()) {
  27. if (this.getProperty('end')) {
  28. this.setProperty('end', false);
  29. return [[], true] as CheckType;
  30. }
  31. }
  32. return super.checkItem(item);
  33. }
  34. }
  35. /**
  36. * A tagging class for the subnumcases environment.
  37. */
  38. export class CasesTags extends AmsTags {
  39. /**
  40. * The counter for the subnumber.
  41. */
  42. protected subcounter = 0;
  43. /**
  44. * @override
  45. */
  46. public start(env: string, taggable: boolean, defaultTags: boolean) {
  47. this.subcounter = 0;
  48. super.start(env, taggable, defaultTags);
  49. }
  50. /**
  51. * @override
  52. */
  53. public autoTag() {
  54. if (this.currentTag.tag != null) return;
  55. if (this.currentTag.env === 'subnumcases') {
  56. if (this.subcounter === 0) this.counter++;
  57. this.subcounter++;
  58. this.tag(this.formatNumber(this.counter, this.subcounter), false);
  59. } else {
  60. if (this.subcounter === 0 || this.currentTag.env !== 'numcases-left') this.counter++;
  61. this.tag(this.formatNumber(this.counter), false);
  62. }
  63. }
  64. /**
  65. * @override
  66. */
  67. public formatNumber(n: number, m: number = null) {
  68. return n.toString() + (m === null ? '' : String.fromCharCode(0x60 + m));
  69. }
  70. }
  71. export const CasesMethods = {
  72. /**
  73. * Implements the numcases environment.
  74. *
  75. * @param {TexParser} texparser The active tex parser.
  76. * @param {CasesBeginItem} begin The environment begin item.
  77. */
  78. NumCases(parser: TexParser, begin: CasesBeginItem) {
  79. if (parser.stack.env.closing === begin.getName()) {
  80. delete parser.stack.env.closing;
  81. parser.Push(parser.itemFactory.create('end').setProperty('name', begin.getName())); // finish eqnarray
  82. const cases = parser.stack.Top();
  83. const table = cases.Last as MmlMtable;
  84. const original = ParseUtil.copyNode(table, parser) as MmlMtable;
  85. const left = cases.getProperty('left');
  86. EmpheqUtil.left(table, original, left + '\\empheqlbrace\\,', parser, 'numcases-left');
  87. parser.Push(parser.itemFactory.create('end').setProperty('name', begin.getName()));
  88. return null;
  89. } else {
  90. const left = parser.GetArgument('\\begin{' + begin.getName() + '}');
  91. begin.setProperty('left', left);
  92. const array = BaseMethods.EqnArray(parser, begin, true, true, 'll', ) as EqnArrayItem;
  93. array.arraydef.displaystyle = false;
  94. array.arraydef.rowspacing = '.2em';
  95. array.setProperty('numCases', true);
  96. parser.Push(begin);
  97. return array;
  98. }
  99. },
  100. /**
  101. * Replacement for & in cases environment.
  102. */
  103. Entry(parser: TexParser, name: string) {
  104. if (!parser.stack.Top().getProperty('numCases')) {
  105. return BaseMethods.Entry(parser, name);
  106. }
  107. parser.Push(parser.itemFactory.create('cell').setProperties({isEntry: true, name: name}));
  108. //
  109. // Make second column be in \text{...}
  110. //
  111. const tex = parser.string;
  112. let braces = 0, i = parser.i, m = tex.length;
  113. //
  114. // Look through the string character by character...
  115. //
  116. while (i < m) {
  117. const c = tex.charAt(i);
  118. if (c === '{') {
  119. //
  120. // Increase the nested brace count and go on
  121. //
  122. braces++;
  123. i++;
  124. } else if (c === '}') {
  125. //
  126. // If there are too many close braces, just end (we will get an
  127. // error message later when the rest of the string is parsed)
  128. // Otherwise
  129. // decrease the nested brace count,
  130. // go on to the next character.
  131. //
  132. if (braces === 0) {
  133. break;
  134. } else {
  135. braces--;
  136. i++;
  137. }
  138. } else if (c === '&' && braces === 0) {
  139. //
  140. // Extra alignment tabs are not allowed in cases
  141. //
  142. throw new TexError('ExtraCasesAlignTab', 'Extra alignment tab in text for numcase environment');
  143. } else if (c === '\\' && braces === 0) {
  144. //
  145. // If the macro is \cr or \\, end the search, otherwise skip the macro
  146. // (multi-letter names don't matter, as we will skip the rest of the
  147. // characters in the main loop)
  148. //
  149. const cs = (tex.slice(i + 1).match(/^[a-z]+|./i) || [])[0];
  150. if (cs === '\\' || cs === 'cr' || cs === 'end' || cs === 'label') {
  151. break;
  152. } else {
  153. i += cs.length;
  154. }
  155. } else {
  156. //
  157. // Go on to the next character
  158. //
  159. i++;
  160. }
  161. }
  162. //
  163. // Process the second column as text and continue parsing from there,
  164. //
  165. const text = tex.substr(parser.i, i - parser.i).replace(/^\s*/, '');
  166. parser.PushAll(ParseUtil.internalMath(parser, text, 0));
  167. parser.i = i;
  168. }
  169. };
  170. /**
  171. * The environments for this package
  172. */
  173. new EnvironmentMap('cases-env', EmpheqUtil.environment, {
  174. numcases: ['NumCases', 'cases'],
  175. subnumcases: ['NumCases', 'cases']
  176. }, CasesMethods);
  177. /**
  178. * The macros for this package
  179. */
  180. new MacroMap('cases-macros', {
  181. '&': 'Entry'
  182. }, CasesMethods);
  183. //
  184. // Define the package for our new environment
  185. //
  186. export const CasesConfiguration = Configuration.create('cases', {
  187. handler: {
  188. environment: ['cases-env'],
  189. character: ['cases-macros']
  190. },
  191. items: {
  192. [CasesBeginItem.prototype.kind]: CasesBeginItem
  193. },
  194. tags: {'cases': CasesTags}
  195. });