找回密码
 快速注册
搜索
查看: 149|回复: 20

[MathJax] 确定光标位置的LaTeX代码所对应的MathML节点

[复制链接]

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

hbghlyj 发表于 2022-8-15 01:47 |阅读模式
修改很长的LaTeX代码时,希望能显示当前光标位置所对应的输出的公式的哪一部分.
思路1: 把光标位置之前的LaTeX代码补全token和{}括号(暂时先不考虑[],\left,\right)使其能够产生有效的输出, 然后用Node.isEqualNode()比较这样输出的MathML与原来的MathML, 找到第一个不同的节点, 就是当前光标位置所对应的输出的公式的部分.
思路2: 在MathJax处理时, 每处理一个token就把原代码中的token和生成的节点加上相同的标号, 比如处理"\frac ab\cdot c"时, 处理完\frac以后把\frac加上position="1"同时把生成的<mfrac>加上position="1", 然后遇到分子的a加上position="2"同时把生成的<mi>加上position="2", 然后遇到分母的b加上position="3"同时把生成的<mi>加上position="3", 然后遇到\cdot把\cdot加上position="4"同时把生成的<mo>加上position="4", 然后遇到c, 加上position="5"同时把生成的<mi>加上position="5". 这便能确定光标位置的LaTeX代码所对应的MathML节点, 比如光标在\cdot后面就知道position="4", 然后就找到对应的MathML节点是那个<mo>. 反过来也可以根据MathML节点找到对应的LaTeX代码位置, 比如点击<mo>就知道position="4", 然后就找到对应的是那个\cdot.
只是一个构想...具体还没有弄出来

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-8-15 02:09
把光标位置之前的LaTeX代码补全token和{}括号(暂时先不考虑[],\left,\right)使其能够产生有效的输出

暂时只考虑仅由a-zA-Z字母组成的Token, 比如\frac (而\, \> \;这些暂时不考虑)
暂时只考虑explicit的{}, 比如\frac{a}{b} (而\frac ab这样的implicit的{}暂时不考虑)
  1. completeToken=(str,pos)=>str.slice(0,pos)+str.slice(pos).match(/[a-zA-Z]*/)[0]+str.slice(pos).match(/[\{\}]/g).join('')
复制代码

测试:
  1. for(let i=0;i<11;i++)console.log(completeToken('\\frac{2a}{b}',i))
复制代码

输出
{}{}
\frac{}{}
\frac{}{}
\frac{}{}
\frac{}{}
\frac{}{}
\frac{}{}
\frac{2a}{}
\frac{2a}{}
\frac{2a}{}
\frac{2a}{b}

把(光标位置)pos前的LaTeX代码补全成了有效的LaTeX代码

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-8-15 02:25
然后用Node.isSameNode()比较这样输出的MathML与原来的MathML, 找到第一个不同的节点

应该可以创建两个TreeWalker
  1. compareMathMLNodes=(tex1,tex2)=>{
  2.     document.write('<table><tr><td>'+MathJax.tex2mml(tex1)+'</td><td>'+MathJax.tex2mml(tex2)+'</td></tr></table>');
  3.     let mathNodes = document.getElementsByTagName('math'),
  4.         walker1 = document.createTreeWalker(mathNodes[0], NodeFilter.SHOW_ELEMENT),
  5.         walker2 = document.createTreeWalker(mathNodes[1], NodeFilter.SHOW_ELEMENT);
  6.     do{var next1=walker1.nextNode(),next2=walker2.nextNode()}
  7.     while(next1.childElementCount&&next2.childElementCount||next1.isEqualNode(next2))
  8.     if(next1)next1.style.background='red';
  9.     if(next2)next2.style.background='red';
  10. }
复制代码

在有MathJax的页面的测试:(浏览器需要支持MathML)
  1. compareMathMLNodes('\\frac{2a}{b}','\\frac{2a}{}')
复制代码
Screenshot 2022-08-14 at 20-03-07 Screenshot.png b变成红色
  1. compareMathMLNodes('\\frac{2a}{b}','\\frac{2a}{c}')
复制代码
Screenshot 2022-08-14 at 20-14-10 Screenshot.png b,c变成红色

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-8-15 04:06
  1. input.value[input.selectionStart]
复制代码

可以得出<input>的光标后一个字符(如果没有后一个字符,返回undefined)

730

主题

1万

回帖

9万

积分

积分
93623
QQ

显示全部楼层

kuing 发表于 2022-8-15 04:22
这个问题我觉得你可以和开发 mathjax 那些人讨论一下啊

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-8-15 05:23
思路1当前的效果:
翻页.gif
  1. var tex=String.raw`\frac{-b\pm\sqrt{b^2-4ac}}{2a}`;
  2. var highlightElement;
  3. document.write(`<input value='${tex}' size='40'/>${MathJax.tex2mml(tex)}<div class='test'></div>`);
  4. function completeToken(str,pos){
  5.    let beforePos=str.slice(0,pos),afterPos=str.slice(pos);
  6.    if(!beforePos)return '';if(!afterPos)return str;
  7.    let afterPosBraces = afterPos.match(/[\{\}]/g).join('');
  8.    if(str[pos-1]=='^'||str[pos-1]=='_')return beforePos+'{}'+afterPosBraces;
  9.    if(afterPos.trimStart()[0]=='}')return completeToken(str,pos-1);
  10.    let completeMacroMatch = afterPos.match(/[a-zA-Z]*/),
  11.    completeMacro=(beforePos.match(/\\[a-zA-Z]*$/) && completeMacroMatch)?completeMacroMatch[0]:'';
  12.    return str.slice(0,pos)+completeMacro+afterPosBraces;
  13. }
  14. function compareMathMLNodes(math1,math2){
  15.     let walker1 = document.createTreeWalker(math1, NodeFilter.SHOW_ELEMENT),
  16.         walker2 = document.createTreeWalker(math2, NodeFilter.SHOW_ELEMENT);
  17.     let next1,next2;
  18.     do{next1=walker1.nextNode(),next2=walker2.nextNode();console.log('next1:');console.log(next1);console.log('next2:');console.log(next2);}
  19.     while(next1&&next2&&(next1.childElementCount&&next2.childElementCount||next1.isEqualNode(next2)));
  20.     if(next1){highlightElement=next1;next1.style.background='red';}
  21. }
  22. document.querySelector('input').addEventListener('click',e=>{
  23.     if(highlightElement)highlightElement.removeAttribute('style');
  24.     document.querySelector('div.test').innerHTML=MathJax.tex2mml(completeToken(tex,e.target.selectionStart));
  25.     compareMathMLNodes(...document.querySelectorAll('math'));
  26. });
复制代码

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-8-15 06:16
hbghlyj 发表于 2022-8-14 22:23
思路1当前的效果:


它会把分子上的第一个负号跳过(当光标贴在这个负号前时,不会把这个负号高亮,而是会把整个分子高亮), 原因是: 当分子有多个字符时, MathJax会把它们包在<mrow>里面(整体作为分子), 当分子只有一个字符时, MathJax只输出这个字符的MathML(本该也是被包在<mrow>里面,但<mrow>被简省了).
所以当光标贴在这个负号前时, next1是整个<mrow>而next2实际上是一个<mo>(本该也是<mrow>,被简省了), 这时就会跳出循环, 把<mrow>高亮出来
  1. MathJax.tex2mml('\\frac{-}{2a}')
复制代码

输出
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
  <mfrac>
    <mo>&#x2212;</mo>
    <mrow>
      <mn>2</mn>
      <mi>a</mi>
    </mrow>
  </mfrac>
</math>
红色部分: 本该也是包在<mrow>里面, 但是因为只有一个元素,所以<mrow>被简省了.
  1. MathJax.tex2mml('\\frac{-b}{2a}')
复制代码

输出
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
  <mfrac>
    <mrow>
      <mo>&#x2212;</mo>
      <mi>b</mi>
    </mrow>
    <mrow>
      <mn>2</mn>
      <mi>a</mi>
    </mrow>
  </mfrac>
</math>

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-8-15 07:42
hbghlyj 发表于 2022-8-14 22:23
思路1当前的效果:


修改了一下, 应该是解决了该问题:
如果光标之前的字符串(beforePos)的结尾是开括号{ , 就深入到里面去.
翻页.gif
  1. var tex=String.raw`\frac{-b\pm\sqrt{b^2-4ac}}{2a}`;
  2. var highlightElement;
  3. document.write(`<input value='${tex}' size='40'/>${MathJax.tex2mml(tex)}<div class='test'></div>`);
  4. function completeToken(str,pos){
  5.    let beforePos=str.slice(0,pos),afterPos=str.slice(pos);
  6.    if(!beforePos)return ['',false];if(!afterPos)return [str,false];
  7.    let afterPosBraces = afterPos.match(/[\{\}]/g).join('');
  8.    if(str[pos-1]=='^'||str[pos-1]=='_')return [beforePos+'{}'+afterPosBraces,false];
  9.    let matchClosingBrace = afterPos.match(/^\s*\}/);if(matchClosingBrace)return completeToken(str,pos-matchClosingBrace[0].length);
  10.    let completeMacroMatch = afterPos.match(/[a-zA-Z]*/),
  11.    completeMacro=(beforePos.match(/\\[a-zA-Z]*$/) && completeMacroMatch)?completeMacroMatch[0]:'';
  12.    return [str.slice(0,pos)+completeMacro+afterPosBraces,Boolean(beforePos.match(/\{\s*$/g))];
  13. }
  14. function compareMathMLNodes(math1,math2,openBrace){
  15.     let walker1 = document.createTreeWalker(math1, NodeFilter.SHOW_ELEMENT),
  16.         walker2 = document.createTreeWalker(math2, NodeFilter.SHOW_ELEMENT);
  17.     let next1,next2;
  18.     do{next1=walker1.nextNode(),next2=walker2.nextNode();
  19.        console.log('next1:',next1,'next2:',next2,openBrace);
  20.     }
  21.     while(next1&&next2&&(next1.childElementCount&&(next2.childElementCount||openBrace)||next1.isEqualNode(next2)));
  22.     if(next1){highlightElement=next1;next1.style.background='red';}
  23. }
  24. document.querySelector('input').addEventListener('click',e=>{   
  25.     let [tex_fragment,openBrace] = completeToken(tex,e.target.selectionStart);
  26.     document.querySelector('div.test').innerHTML = MathJax.tex2mml(tex_fragment);
  27.     if(highlightElement)highlightElement.removeAttribute('style');
  28.     compareMathMLNodes(...document.querySelectorAll('math'),openBrace);
  29. });
复制代码

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-8-15 07:59
hbghlyj 发表于 2022-8-15 00:42
修改了一下, 应该是解决了该问题

还是不行的...
换一个公式
  1. var tex=String.raw`\frac{\frac{\frac{\frac{1}{2}+1}{2}+1}{2}+1}{2}`;
复制代码

就完全乱套了

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-8-31 16:03
本帖最后由 hbghlyj 于 2022-9-5 19:56 编辑 尝试一下思路2: 每生成一个MathML节点, 就记录字符串当前被解析的位置.
首先在开头加上
  1. array=[];
复制代码
用来存储[字符串当前被解析的位置、表示MathML节点的文本、MathML节点、节点对应的源码的长度], 见本楼末尾的array.push([t.sourcePos,t.toString(),o,t.stringlength])

在input/tex/TexParser.ts第61行找到
  1.   public i: number = 0;
复制代码

这是字符串当前被解析的位置。
同一个文件的第196行
  1.   public Push(arg: StackItem | MmlNode) {
  2.     if (arg instanceof AbstractMmlNode && arg.isInferred) {
  3.       this.PushAll(arg.childNodes);
  4.     } else {
  5.       this.stack.Push(arg);
  6.     }
  7.   }
复制代码

在(由上面的TypeScript编译出来的)custom-mathjax.min.js找到对应位置
  1. t instanceof c.AbstractMmlNode&&t.isInferred?this.PushAll(t.childNodes):this.stack.Push(t)
复制代码
改为(关于base_i见13#)
  1. t instanceof c.AbstractMmlNode&&t.isInferred?(t.childNodes[0].stringlength=1,t.childNodes[0].sourcePos=this.i+this.base_i,this.PushAll(t.childNodes)):(t.stringlength=1,t.sourcePos=this.i+this.base_i,this.stack.Push(t))
复制代码



在core/Tree/Factory.ts第156行找到
  1.   public create(kind: string, ...args: any[]) {
  2.     return (this.node[kind] || this.node[this.defaultKind])(...args);
  3.   }
复制代码
这是生成MML节点的函数. 它把参数原封不动传给(this.node[kind] || this.node[this.defaultKind]), 这是分别生成“各种不同的Tag(包括math,mo,mi,mtable⋯)的MML节点”的函数. 它们的定义在ts/core/MmlTree/MmlNodes



core/MmlTree/MathMLVisitor.ts可以把内部的Mml树状结构转换为MathML DOM. 我们看到第92行的visitDefault, 这个函数的最后一行
  1. parent.appendChild(mml);
复制代码
对应着(编译出来的)custom-mathjax.min.js中的
  1. e.appendChild(o)
复制代码
改为
  1. e.appendChild(o);if(t.sourcePos)array.push([t.sourcePos,t.toString(),o,t.stringlength])
复制代码

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-9-1 00:48
本帖最后由 hbghlyj 于 2022-9-2 01:05 编辑 XML namespace小知识:
在HTML里面直接createElement('math')会被认为是HTMLUndefinedElement.而应该用createElementNS才能得到MathML.
比如
  1. math=document.createElement('math');math.innerHTML='<msup><mn>2</mn><mn>2</mn></msup>';document.body.appendChild(math)
复制代码

效果是两个相同的2并在一起
  1. math=document.createElementNS('http://www.w3.org/1998/Math/MathML','math');math.innerHTML='<msup><mn>2</mn><mn>2</mn></msup>';document.body.appendChild(math)
复制代码

这样才能正常显示出MathML(上标).
所以将core/MmlTree/MathMLVisitor.ts第93行
  1.     let mml = this.document.createElement(node.kind);
复制代码
改为
  1.     let mml = this.document.createElementNS('http://www.w3.org/1998/Math/MathML',node.kind);
复制代码
就行了. 但是为解决此问题,需要继续修改为
  1.     let mml = this.document.createElementNS('http://www.w3.org/1998/Math/MathML',node.kind=='TeXAtom'?'mrow':node.kind);
复制代码

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-9-1 07:59
测试了一下$\verb'1+2\times a'$是正常的.
Screenshot 2022-08-27 at 13-35-50 水蛋 - Google Search.png
光标位置 → MathML节点
1    →   mn(1)
2    →   mo(+)
3    →   mn(2)
10   →   mo(×)
11   →   mi(a)

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-9-1 17:20
本帖最后由 hbghlyj 于 2022-9-6 12:13 编辑 TexParser.ts
  1.   public ParseArg(name: string): MmlNode  {
  2.     return new TexParser(this.GetArgument(name), this.stack.env,
  3.                          this.configuration).mml();
  4.   }
复制代码

$\tt parseArg$会返回一个新的TexParser实例 ($\tt this.i$重新从1开始) 用来分析参数. 所以把这个新的TexParser实例设置一个属性$\verb|base_i|$用来存储上一个TexParser的$\tt this.i$
比如$\verb'\frac{111}{22}'$,首先产生一个参数为$\verb'\frac{111}{22}'$,且$\verb|base_i|$为0的TexParser实例,遇到control sequence为$\verb'\frac'$它有两个参数,
扫描到第一个参数,变成$\verb'\frac{111}'$,这时$\tt this.i$是10
产生一个的参数为$\verb'111'$的TexParser实例,$\verb|base_i|$为6因为它是从$\verb|\frac|\tt\{$开始的.
处理完以后弹出这个TexParser回到最初的TexParser;
扫描到第二个参数,变成$\verb'\frac{111}{22}'$,这时$\tt this.i$是14
产生一个的参数为$\verb'22'$的TexParser实例,$\verb|base_i|$为11因为它是从$\verb|\frac{111}|\tt\{$开始的.
处理完以后弹出这个TexParser回到最初的TexParser;
处理完以后弹出, 结束.

找到TexParser.ts76-91行
  1. constructor(private _string: string, env: EnvList, public configuration: ParseOptions) {
  2.   ⋯
  3. }
复制代码
编译出来的位置
  1. function t(t,e,r){var o,i;this._string=t,this.configuration=r,this.macroCount=0,this.i=0,this.currentCS="";var a,s=e.hasOwnProperty("isInner"),u=e.isInner;if(delete e.isInner,e){a={};try{for(var c=n(Object.keys(e)),p=c.next();!p.done;p=c.next()){var f=p.value;a[f]=e[f]}}catch(t){o={error:t}}finally{try{p&&!p.done&&(i=c.return)&&i.call(c)}finally{if(o)throw o.error}}}this.configuration.pushParser(this),this.stack=new l.default(this.itemFactory,a,!s||u),this.Parse(),this.Push(this.itemFactory.create("stop"))}
复制代码

改为
  1. function t(t,e,r,base_i=0){var o,i;this.base_i=base_i;this._string=t,this.configuration=r,this.macroCount=0,this.i=0,this.currentCS="";var a,s=e.hasOwnProperty("isInner"),u=e.isInner;if(delete e.isInner,e){a={};try{for(var c=n(Object.keys(e)),p=c.next();!p.done;p=c.next()){var f=p.value;a[f]=e[f]}}catch(t){o={error:t}}finally{try{p&&!p.done&&(i=c.return)&&i.call(c)}finally{if(o)throw o.error}}}this.configuration.pushParser(this),this.stack=new l.default(this.itemFactory,a,!s||u),this.Parse(),this.Push(this.itemFactory.create("stop"))}
复制代码

找到TexParser.ts314-316行
  1.           if (--parens === 0) {
  2.             return this.string.slice(j, this.i - 1);
  3.           }
复制代码
编译出来的位置
  1. case"}":if(0==--n)return this.string.slice(r,this.i-1)
复制代码

改为
  1. case"}":if(0==--n)return this.openBrace=r,this.string.slice(r,this.i-1)
复制代码

然后找到ParseArg编译出来的位置
  1. t.prototype.ParseArg=function(e){return new t(this.GetArgument(e),this.stack.env,this.configuration).mml()}
复制代码
改为
  1. t.prototype.ParseArg=function(e){const arg=this.GetArgument(e);let base_i;if(this.openBrace){base_i=this.openBrace+this.base_i;this.openBrace=0;}else base_i=this.i-arg.length-(+(this._string.charAt(this.i-1)==' '))+this.base_i;return new t(arg,this.stack.env,this.configuration,base_i).mml()}
复制代码

$\verb|else base_i=this.i-arg.length-(+(this._string.charAt(this.i-1)==' '))+this.base_i;|$  ⟶  若openBrace为0或undefined, 则ParseArg就当作单个字符处理比如$\verb'\sqrt a'$这里$\tt a$是“单个字符”, $\verb'\sqrt \alpha'$或$\verb'\sqrt\alpha'$这里$\verb'\alpha'$也是“单个字符”.

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-9-2 01:59
当MathJax报错的时候, TeX编辑器应该把光标放到错误发生的位置.

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-9-2 23:59
本帖最后由 hbghlyj 于 2022-9-6 01:30 编辑 $$\sqrt\frac12$$MathJax issue #75
input/tex/base/BaseMethods.ts第482-495行
  1.   const n = parser.GetBrackets(name);
  2.   let arg = parser.GetArgument(name);
  3.   if (arg === '\\frac') {
  4.     arg  += '{' + parser.GetArgument(arg) + '}{' + parser.GetArgument(arg) + '}';
  5.   }
  6.   let mml = new TexParser(arg, parser.stack.env, parser.configuration).mml();
  7.   parser.Push(mml);
复制代码

编译成JS:
  1. var r=t.GetBrackets(e),n=t.GetArgument(e);"\\frac"===n&&(n+="{"+t.GetArgument(n)+"}{"+t.GetArgument(n)+"}");var o=new f.default(n,t.stack.env,t.configuration).mml();o=r?t.create("node","mroot",[o,E(t,r)]):t.create("node","msqrt",[o]),t.Push(o)
复制代码

改为
  1. O.Sqrt=function(t,e){let old_old_i=t.i,r=t.GetBrackets(e),old_i=t.i,o=t.ParseArg(e);o=r?t.create("node","mroot",[o,E(t,r,t.openBracket)]):t.create("node","msqrt",[o]),o.stringlength=t.i-old_old_i+e.length+(+(t._string.charAt(old_i-1)==' '))+(+(r!=undefined&&t._string.charAt(old_old_i-1)==' ')),t.Push(o)}
复制代码
此处$\verb|(+(t._string.charAt(old_i-1)==' '))|$为避免当“sqrt后面处理的第一个字符是空格”导致的“少选中一格”(其中+是unary plus). 这是因为, 如果指令名后面紧接着的一个字符是空格, 比如“\sqrt ”那么getCS函数会把这一个空格也匹配上,见input/tex/TexParser.ts行271的正则表达式/^(([a-z]+) ?|[\uD800-\uDBFF].|.)/i
此处$\verb|E(t,r,old_old_i+1)|$是源码中的parseRoot函数, 见17#


input/tex/TexParser.ts行335
  1.   public GetBrackets(_name: string, def?: string): string {
  2.     if (this.GetNext() !== '[') {
  3.       return def;
  4.     }
  5.     let j = ++this.i, parens = 0;
  6.     while (this.i < this.string.length) {
  7.       switch (this.string.charAt(this.i++)) {
  8.       case '{':   parens++; break;
  9.       case '\\':  this.i++; break;
  10.       case '}':
  11.         if (parens-- <= 0) {
  12.           // @test ExtraCloseLooking1
  13.           throw new TexError('ExtraCloseLooking',
  14.                               'Extra close brace while looking for %1', '\']\'');
  15.         }
  16.         break;
  17.       case ']':
  18.         if (parens === 0) {
  19.           return this.string.slice(j, this.i - 1);
  20.         }
  21.         break;
  22.       }
  23.     }
  24.     // @test MissingCloseBracket
  25.     throw new TexError('MissingCloseBracket',
  26.                         'Could not find closing \']\' for argument to %1', this.currentCS);
  27.   }
复制代码
编译成JS:
  1. t.prototype.GetBrackets=function(t,e){if("["!==this.GetNext())return e;for(var r=++this.i,n=0;this.i<this.string.length;)switch(this.string.charAt(this.i++)){case"{":n++;break;case"\\":this.i++;break;case"}":if(n--<=0)throw new u.default("ExtraCloseLooking","Extra close brace while looking for %1","']'");break;case"]":if(0===n)return this.string.slice(r,this.i-1)}throw new u.default("MissingCloseBracket","Could not find closing ']' for argument to %1",this.currentCS)}
复制代码

改为
  1. t.prototype.GetBrackets=function(t,e){if("["!==this.GetNext())return e;for(var r=++this.i,n=0;this.i<this.string.length;)switch(this.string.charAt(this.i++)){case"{":n++;break;case"\\":this.i++;break;case"}":if(n--<=0)throw new u.default("ExtraCloseLooking","Extra close brace while looking for %1","']'");break;case"]":if(0===n)return this.openBracket=r,this.string.slice(r,this.i-1)}throw new u.default("MissingCloseBracket","Could not find closing ']' for argument to %1",this.currentCS)}
复制代码

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-9-3 16:05
本帖最后由 hbghlyj 于 2022-9-5 05:43 编辑 首先在create处,用$\verb|debugger;|$设置一个断点:
  1.         t.prototype.create = function (t) {
  2.           for (var e, r = [
  3.           ], n = 1; n < arguments.length; n++) r[n - 1] = arguments[n];
  4.           debugger;
  5.           return (e = this.configuration.nodeFactory).create.apply(e, i([t], o(r), !1))
  6.         },
  7.         t
  8.       }();
复制代码

用MathJax处理$\frac ab$,第一次断开$\verb|_string|$是"a",第二次断开是"b",第三次是"\\frac ab",看Call stack发现 “分数” 没有调用input/tex/base/BaseMethods.ts第468行的BaseMethods.Frac而是调用了input/tex/ams/AmsMethods.ts第510行的e.AmsMethods.Genfrac,这是因为ams(MathJax Extension)重定义了$\verb|\frac|$:
input/tex/ams/AmsMappings.ts第82行
  1.   frac:       ['Genfrac', '', '', '', ''],
复制代码
找到Genfrac的定义,(和15楼的改法类似)改为
  1. e.AmsMethods.Genfrac=function(t,e,r,n,o,i){let old_i=t.i;null==r&&(r=t.GetDelimiterArg(e)),null==n&&(n=t.GetDelimiterArg(e)),null==o&&(o=t.GetArgument(e)),null==i&&(i=a.default.trimSpaces(t.GetArgument(e)));var s=t.ParseArg(e),u=t.ParseArg(e),c=t.create("node","mfrac",[s,u]);if(""!==o&&l.default.setAttribute(c,"linethickness",o),(r||n)&&(l.default.setProperty(c,"withDelims",!0),c=a.default.fixedFence(t.configuration,r,c,n)),""!==i){var f=parseInt(i,10),h=["D","T","S","SS"][f];if(null==h)throw new p.default("BadMathStyleFor","Bad math style for %1",t.currentCS);c=t.create("node","mstyle",[c]),"D"===h?l.default.setProperties(c,{displaystyle:!0,scriptlevel:0}):l.default.setProperties(c,{displaystyle:!1,scriptlevel:f-1})}c.stringlength=t.i-old_i+e.length+(+(t._string.charAt(old_i-1)==' '));t.Push(c)}
复制代码

input/tex/base/BaseMethods.ts第363行
找到
  1. O.NamedFn=function(t,e,r){r||(r=e.substr(1));var n=t.create("token","mi",{texClass:y.TEXCLASS.OP},r);t.Push(t.itemFactory.create("fn",n))}
复制代码

改为
  1. O.NamedFn=function(t,e,r){r||(r=e.substr(1));let n=t.create("token","mi",{texClass:y.TEXCLASS.OP},r),c=t.itemFactory.create("fn",n);n.stringlength=e.length;t.Push(c)}
复制代码


第161行
  1. BaseMethods.Subscript = function(parser: TexParser, _c: string) {
  2.   if (parser.GetNext().match(/\d/)) {
  3.     // don't treat numbers as a unit
  4.     parser.string =
  5.       parser.string.substr(0, parser.i + 1) + ' ' +
  6.       parser.string.substr(parser.i + 1);
  7.   }
  8.   let primes, base;
  9.   const top = parser.stack.Top();
  10.   if (top.isKind('prime')) {
  11.     // @test Prime on Sub
  12.     [base, primes] = top.Peek(2);
  13.     parser.stack.Pop();
  14.   } else {
  15.     base = parser.stack.Prev();
  16.     if (!base) {
  17.       // @test Empty Base Index
  18.       base = parser.create('token', 'mi', {}, '');
  19.     }
  20.   }
  21.   const movesupsub = NodeUtil.getProperty(base, 'movesupsub');
  22.   let position = NodeUtil.isType(base, 'msubsup') ?
  23.     (base as MmlMsubsup).sub : (base as MmlMunderover).under;
  24.   if ((NodeUtil.isType(base, 'msubsup') && !NodeUtil.isType(base, 'msup') &&
  25.        NodeUtil.getChildAt(base, (base as MmlMsubsup).sub)) ||
  26.       (NodeUtil.isType(base, 'munderover') && !NodeUtil.isType(base, 'mover') &&
  27.        NodeUtil.getChildAt(base, (base as MmlMunderover).under) &&
  28.        !NodeUtil.getProperty(base, 'subsupOK'))) {
  29.     // @test Double-sub-error, Double-under-error
  30.     throw new TexError('DoubleSubscripts', 'Double subscripts: use braces to clarify');
  31.   }
  32.   if (!NodeUtil.isType(base, 'msubsup') || NodeUtil.isType(base, 'msup')) {
  33.     if (movesupsub) {
  34.       // @test Large Operator, Move Superscript
  35.       if (!NodeUtil.isType(base, 'munderover') || NodeUtil.isType(base, 'mover') ||
  36.           NodeUtil.getChildAt(base, (base as MmlMunderover).under)) {
  37.         // @test Move Superscript
  38.         base = parser.create('node', 'munderover', [base], {movesupsub: true});
  39.       }
  40.       position = (base as MmlMunderover).under;
  41.     } else {
  42.       // @test Empty Base Index, Empty Base Index2, Index
  43.       base = parser.create('node', 'msubsup', [base]);
  44.       position = (base as MmlMsubsup).sub;
  45.     }
  46.   }
  47.   parser.Push(
  48.     parser.itemFactory.create('subsup', base).setProperties({
  49.       position: position, primes: primes, movesupsub: movesupsub
  50.     }) );
  51. };
复制代码

对应于JS
  1. O.Subscript=function(t,e){var r,n,o;t.GetNext().match(/\d/)&&(t.string=t.string.substr(0,t.i+1)+" "+t.string.substr(t.i+1));var i=t.stack.Top();i.isKind("prime")?(o=(r=s(i.Peek(2),2))[0],n=r[1],t.stack.Pop()):(o=t.stack.Prev())||(o=t.create("token","mi",{},""));var a=c.default.getProperty(o,"movesupsub"),l=c.default.isType(o,"msubsup")?o.sub:o.under;if(c.default.isType(o,"msubsup")&&!c.default.isType(o,"msup")&&c.default.getChildAt(o,o.sub)||c.default.isType(o,"munderover")&&!c.default.isType(o,"mover")&&c.default.getChildAt(o,o.under)&&!c.default.getProperty(o,"subsupOK"))throw new p.default("DoubleSubscripts","Double subscripts: use braces to clarify");c.default.isType(o,"msubsup")&&!c.default.isType(o,"msup")||(a?((!c.default.isType(o,"munderover")||c.default.isType(o,"mover")||c.default.getChildAt(o,o.under))&&(o=t.create("node","munderover",[o],{movesupsub:!0})),l=o.under):l=(o=t.create("node","msubsup",[o])).sub),t.Push(t.itemFactory.create("subsup",o).setProperties({position:l,primes:n,movesupsub:a}))}
复制代码

don't treat numbers as a unit是什么意思? 对于a_25, MathJax的parser正常来说会把25当成整体放到<mn>里面, 但是subscript(同样对于superscript也是)在这一步增加了一个空格, 变成a_2 5就可以“don't treat as a unit”了
但这会导致后面的定位出错, 我们把$\verb't.GetNext().match(/\d/)&&(t.string=t.string.substr(0,t.i+1)+" "+t.string.substr(t.i+1));'$改为直接create那个<mn>就行了(在结尾) :
  1. O.Subscript=function(t,e){let r,n,o,d=t.GetNext();var i=t.stack.Top();i.isKind("prime")?(o=(r=s(i.Peek(2),2))[0],n=r[1],t.stack.Pop()):(o=t.stack.Prev())||(o=t.create("token","mi",{},""));var a=c.default.getProperty(o,"movesupsub"),l=c.default.isType(o,"msubsup")?o.sub:o.under;if(c.default.isType(o,"msubsup")&&!c.default.isType(o,"msup")&&c.default.getChildAt(o,o.sub)||c.default.isType(o,"munderover")&&!c.default.isType(o,"mover")&&c.default.getChildAt(o,o.under)&&!c.default.getProperty(o,"subsupOK"))throw new p.default("DoubleSubscripts","Double subscripts: use braces to clarify");c.default.isType(o,"msubsup")&&!c.default.isType(o,"msup")||(a?((!c.default.isType(o,"munderover")||c.default.isType(o,"mover")||c.default.getChildAt(o,o.under))&&(o=t.create("node","munderover",[o],{movesupsub:!0})),l=o.under):l=(o=t.create("node","msubsup",[o])).sub),t.Push(t.itemFactory.create("subsup",o).setProperties({position:l,primes:n,movesupsub:a}));if(d.match(/\d/)){t.i++;t.Push(t.create("token","mn",{},d))}}
复制代码
同样地,对于Superscript,
  1. O.Superscript=function(t,e){var r,n,o;t.GetNext().match(/\d/)&&(t.string=t.string.substr(0,t.i+1)+" "+t.string.substr(t.i+1));var i=t.stack.Top();i.isKind("prime")?(o=(r=s(i.Peek(2),2))[0],n=r[1],t.stack.Pop()):(o=t.stack.Prev())||(o=t.create("token","mi",{},""));var a=c.default.getProperty(o,"movesupsub"),l=c.default.isType(o,"msubsup")?o.sup:o.over;if(c.default.isType(o,"msubsup")&&!c.default.isType(o,"msup")&&c.default.getChildAt(o,o.sup)||c.default.isType(o,"munderover")&&!c.default.isType(o,"mover")&&c.default.getChildAt(o,o.over)&&!c.default.getProperty(o,"subsupOK"))throw new p.default("DoubleExponent","Double exponent: use braces to clarify");c.default.isType(o,"msubsup")&&!c.default.isType(o,"msup")||(a?((!c.default.isType(o,"munderover")||c.default.isType(o,"mover")||c.default.getChildAt(o,o.over))&&(o=t.create("node","munderover",[o],{movesupsub:!0})),l=o.over):l=(o=t.create("node","msubsup",[o])).sup),t.Push(t.itemFactory.create("subsup",o).setProperties({position:l,primes:n,movesupsub:a}))}
复制代码
把“加空格”那一步改掉,变成
  1. O.Superscript=function(t,e){let r,n,o,d=t.GetNext();var i=t.stack.Top();i.isKind("prime")?(o=(r=s(i.Peek(2),2))[0],n=r[1],t.stack.Pop()):(o=t.stack.Prev())||(o=t.create("token","mi",{},""));var a=c.default.getProperty(o,"movesupsub"),l=c.default.isType(o,"msubsup")?o.sup:o.over;if(c.default.isType(o,"msubsup")&&!c.default.isType(o,"msup")&&c.default.getChildAt(o,o.sup)||c.default.isType(o,"munderover")&&!c.default.isType(o,"mover")&&c.default.getChildAt(o,o.over)&&!c.default.getProperty(o,"subsupOK"))throw new p.default("DoubleExponent","Double exponent: use braces to clarify");c.default.isType(o,"msubsup")&&!c.default.isType(o,"msup")||(a?((!c.default.isType(o,"munderover")||c.default.isType(o,"mover")||c.default.getChildAt(o,o.over))&&(o=t.create("node","munderover",[o],{movesupsub:!0})),l=o.over):l=(o=t.create("node","msubsup",[o])).sup),t.Push(t.itemFactory.create("subsup",o).setProperties({position:l,primes:n,movesupsub:a}));if(d.match(/\d/)){t.i++;t.Push(t.create("token","mn",{},d))}}
复制代码

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-9-5 11:23
本帖最后由 hbghlyj 于 2022-9-5 20:01 编辑 input/tex/base/BaseMethods.ts行280对应JS
  1. O.MathFont=function(t,e,r){var o=t.GetArgument(e),i=new f.default(o,n(n({},t.stack.env),{font:r,multiLetterIdentifiers:/^[a-zA-Z]+/,noAutoOP:!0}),t.configuration).mml();t.Push(t.create("node","TeXAtom",[i]))},O.SetFont=function(t,e,r){t.stack.env.font=r},O.SetStyle=function(t,e,r,n,o){t.stack.env.style=r,t.stack.env.level=o,t.Push(t.itemFactory.create("style").setProperty("styles",{displaystyle:n,scriptlevel:o}))}
复制代码

改为
  1. O.MathFont=function(t,e,r){let old_i=t.i,o=t.GetArgument(e),base_i;if(t.openBrace){base_i=t.openBrace+t.base_i;t.openBrace=0}else base_i=t.i-1+t.base_i;let i=new f.default(o,n(n({},t.stack.env),{font:r,multiLetterIdentifiers:/^[a-zA-Z]+/,noAutoOP:!0}),t.configuration,base_i).mml(),c=t.create("node","TeXAtom",[i]);c.stringlength=t.i-old_i+e.length+(+(t._string.charAt(old_i-1)==' '));t.Push(c)}
复制代码


input/tex/base/BaseMethods.ts行505
  1. function parseRoot(parser: TexParser, n: string) {
  2.   // @test General Root, Explicit Root
  3.   const env = parser.stack.env;
  4.   const inRoot = env['inRoot'];
  5.   env['inRoot'] = true;
  6.   const newParser = new TexParser(n, env, parser.configuration);
  7.   let node = newParser.mml();
  8.   const global = newParser.stack.global;
  9.   if (global['leftRoot'] || global['upRoot']) {
  10.     // @test Tweaked Root
  11.     const def: EnvList = {};
  12.     if (global['leftRoot']) {
  13.       def['width'] = global['leftRoot'];
  14.     }
  15.     if (global['upRoot']) {
  16.       def['voffset'] = global['upRoot'];
  17.       def['height'] = global['upRoot'];
  18.     }
  19.     node = parser.create('node', 'mpadded', [node], def);
  20.   }
  21.   env['inRoot'] = inRoot;
  22.   return node;
  23. }
复制代码
对应于JS
  1. function E(t,e){var r=t.stack.env,n=r.inRoot;r.inRoot=!0;var o=new f.default(e,r,t.configuration),i=o.mml(),a=o.stack.global;if(a.leftRoot||a.upRoot){var s={};a.leftRoot&&(s.width=a.leftRoot),a.upRoot&&(s.voffset=a.upRoot,s.height=a.upRoot),i=t.create("node","mpadded",[i],s)}return r.inRoot=n,i}
复制代码
改为
  1. function E(t,e,old_i){let r=t.stack.env,n=r.inRoot;r.inRoot=!0;let o=new f.default(e,r,t.configuration,old_i+t.base_i),i=o.mml();if(a.leftRoot||a.upRoot){var s={};a.leftRoot&&(s.width=a.leftRoot),a.upRoot&&(s.voffset=a.upRoot,s.height=a.upRoot),i=t.create("node","mpadded",[i],s)}return r.inRoot=n,i}
复制代码


535行
  1. BaseMethods.Root = function(parser: TexParser, name: string) {
  2.   const n = parser.GetUpTo(name, '\\of');
  3.   const arg = parser.ParseArg(name);
  4.   const node = parser.create('node', 'mroot', [arg, parseRoot(parser, n)]);
  5.   parser.Push(node);
  6. };
复制代码

对应于JS
  1. O.Root=function(t,e){var r=t.GetUpTo(e,"\\of"),n=t.ParseArg(e),o=t.create("node","mroot",[n,E(t,r)]);t.Push(o)}
复制代码

改为
  1. O.Root=function(t,e){let old_i=t.i,r=t.GetUpTo(e,"\\of"),o=t.create("node","mroot",[t.ParseArg(e),E(t,r,old_i)]);o.stringlength=t.i-old_i+e.length+(+(t._string.charAt(old_i-1)==' '));t.Push(o)}
复制代码


input/tex/ParseMethods.ts行63
  1.   export function digit(parser: TexParser, c: string) {
  2.     let mml: MmlNode;
  3.     const pattern = parser.configuration.options['digits'];
  4.     const n = parser.string.slice(parser.i - 1).match(pattern);
  5.     // @test Integer Font
  6.     const def = ParseUtil.getFontDef(parser);
  7.     if (n) {
  8.       // @test Integer, Number, Decimal (European)
  9.       mml = parser.create('token', 'mn', def, n[0].replace(/[{}]/g, ''));
  10.       parser.i += n[0].length - 1;
  11.     } else {
  12.       // @test Decimal Point, Decimal Point European
  13.       mml = parser.create('token', 'mo', def, c);
  14.     }
  15.     parser.Push(mml);
  16.   }
复制代码
对应JS为
  1. t.digit=function(t,e){var r,n=t.configuration.options.digits,o=t.string.slice(t.i-1).match(n),i=u.default.getFontDef(t);o?(r=t.create("token","mn",i,o[0].replace(/[{}]/g,"")),t.i+=o[0].length-1):r=t.create("token","mo",i,e),t.Push(r)}
复制代码

改为
  1. t.digit=function(t,e){var r,n=t.configuration.options.digits,o=t.string.slice(t.i-1).match(n),i=u.default.getFontDef(t);o?(r=t.create("token","mn",i,o[0].replace(/[{}]/g,"")),t.i+=(r.stringlength=o[0].length)-1):r=t.create("token","mo",i,e),t.Push(r)}
复制代码



行420对应JS
  1. t.prototype.GetUpTo=function(t,e){for(;this.nextIsSpace();)this.i++;for(var r=this.i,n=0;this.i<this.string.length;){var o=this.i,i=this.GetNext();switch(this.i+=i.length,i){case"\\":i+=this.GetCS();break;case"{":n++;break;case"}":if(0===n)throw new u.default("ExtraCloseLooking","Extra close brace while looking for %1",e);n--}if(0===n&&i===e)return this.string.slice(r,o)}throw new u.default("TokenNotFoundForCommand","Could not find %1 for %2",e,this.currentCS)}
复制代码
改为
  1. t.prototype.GetUpTo=function(t,e){for(var r=this.i,n=0;this.i<this.string.length;){var o=this.i,i=this.GetNext();switch(this.i+=i.length,i){case"\\":i+=this.GetCS();break;case"{":n++;break;case"}":if(0===n)throw new u.default("ExtraCloseLooking","Extra close brace while looking for %1",e);n--}if(0===n&&i===e)return this.string.slice(r,o)}throw new u.default("TokenNotFoundForCommand","Could not find %1 for %2",e,this.currentCS)}
复制代码

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-9-6 08:42
input/tex/ParseMethods.ts行96
  1.   export function mathchar0mi(parser: TexParser, mchar: Symbol) {
  2.     const def = mchar.attributes || {mathvariant: TexConstant.Variant.ITALIC};
  3.     // @test Greek
  4.     const node = parser.create('token', 'mi', def, mchar.char);
  5.     parser.Push(node);
  6.   }
复制代码
对应JS
  1. t.mathchar0mi=function(t,e){var r=e.attributes||{mathvariant:l.TexConstant.Variant.ITALIC},n=t.create("token","mi",r,e.char);t.Push(n)}
复制代码
改为
  1. t.mathchar0mi=function(t,e){var r=e.attributes||{mathvariant:l.TexConstant.Variant.ITALIC},n=t.create("token","mi",r,e.char);n.stringlength=e._symbol.length+1+(+(t._string.charAt(t.i-1)==' '));t.Push(n)}
复制代码
同样地,
  1. t.mathchar0mo=function(t,e){var r=e.attributes||{};r.stretchy=!1;var n=t.create("token","mo",r,e.char);s.default.setProperty(n,"fixStretchy",!0),t.configuration.addNode("fixStretchy",n),t.Push(n)}
复制代码
改为
  1. t.mathchar0mo=function(t,e){var r=e.attributes||{};r.stretchy=!1;var n=t.create("token","mo",r,e.char);n.stringlength=e._symbol.length+1+(+(t._string.charAt(t.i-1)==' '));s.default.setProperty(n,"fixStretchy",!0),t.configuration.addNode("fixStretchy",n),t.Push(n)}
复制代码



关于\mathscr和\mathcal的区分:
core/MmlTree/MathMLVisitor.ts行104对应JS改为
  1. e.prototype.addAttributes=function(t,e){var r,n,o=t.attributes,a=o.getExplicitNames();try{for(var s=i(a),l=s.next();!l.done;l=s.next()){var u=l.value;if(u=="mathvariant"&&o.getExplicit(u)=="-tex-calligraphic"){e.setAttribute(u,"script");e.classList.add('-tex-calligraphy');continue}e.setAttribute(u,o.getExplicit(u).toString())}}catch(t){r={error:t}}finally{try{l&&!l.done&&(n=s.return)&&n.call(s)}finally{if(r)throw r.error}}}
复制代码

并添加CSS
  1. .-tex-calligraphy{font-feature-settings: 'ss01';}
复制代码

参见lists.informatik.uni-erlangen.de/pipermail/latexml/2014-July/001895.html


input/tex/base/BaseMethods.ts行611
  1. BaseMethods.UnderOver = function(parser: TexParser, name: string, c: string, stack: boolean) {
  2.   const entity = NodeUtil.createEntity(c);
  3.   const mo = parser.create('token', 'mo', {stretchy: true, accent: true}, entity);
  4.   const pos = (name.charAt(1) === 'o' ? 'over' : 'under');
  5.   const base = parser.ParseArg(name);
  6.   parser.Push(ParseUtil.underOver(parser, base, mo, pos, stack));
  7. };
复制代码
对应JS
  1. O.UnderOver=function(t,e,r,n){var o=c.default.createEntity(r),i=t.create("token","mo",{stretchy:!0,accent:!0},o),a="o"===e.charAt(1)?"over":"under",s=t.ParseArg(e);t.Push(d.default.underOver(t,s,i,a,n))}
复制代码
改为
  1. O.UnderOver=function(t,e,r,n){let old_i=t.i,o=c.default.createEntity(r),i=t.create("token","mo",{stretchy:!0,accent:!0},o),a="o"===e.charAt(1)?"over":"under",s=t.ParseArg(e),l=d.default.underOver(t,s,i,a,n);l.stringlength=t.i-old_i+e.length+(+(t._string.charAt(old_i-1)==' '));t.Push(l)}
复制代码
同样地,
  1. O.Overset=function(t,e){var r=t.ParseArg(e),n=t.ParseArg(e);d.default.checkMovableLimits(n),r.isKind("mo")&&c.default.setAttribute(r,"accent",!1);var o=t.create("node","mover",[n,r]);t.Push(o)}
复制代码
改为
  1. O.Overset=function(t,e){let old_i=t.i,r=t.ParseArg(e),n=t.ParseArg(e);d.default.checkMovableLimits(n),r.isKind("mo")&&c.default.setAttribute(r,"accent",!1);let o=t.create("node","mover",[n,r]);o.stringlength=t.i-old_i+e.length+(+(t._string.charAt(old_i-1)==' '));t.Push(o)}
复制代码
同样地,
  1. O.Underset=function(t,e){var r=t.ParseArg(e),n=t.ParseArg(e);d.default.checkMovableLimits(n),r.isKind("mo")&&c.default.setAttribute(r,"accent",!1);var o=t.create("node","munder",[n,r],{accentunder:!1});t.Push(o)}
复制代码
改为
  1. O.Underset=function(t,e){let old_i=t.i,r=t.ParseArg(e),n=t.ParseArg(e);d.default.checkMovableLimits(n),r.isKind("mo")&&c.default.setAttribute(r,"accent",!1);let o=t.create("node","munder",[n,r],{accentunder:!1});o.stringlength=t.i-old_i+e.length+(+(t._string.charAt(old_i-1)==' '));t.Push(o)}
复制代码
同样地,
  1. O.Overunderset=function(t,e){var r=t.ParseArg(e),n=t.ParseArg(e),o=t.ParseArg(e);d.default.checkMovableLimits(o),r.isKind("mo")&&c.default.setAttribute(r,"accent",!1),n.isKind("mo")&&c.default.setAttribute(n,"accent",!1);var i=t.create("node","munderover",[o,n,r],{accent:!1,accentunder:!1});t.Push(i)}
复制代码
改为
  1. O.Overunderset=function(t,e){let old_i=t.i,r=t.ParseArg(e),n=t.ParseArg(e),o=t.ParseArg(e);d.default.checkMovableLimits(o),r.isKind("mo")&&c.default.setAttribute(r,"accent",!1),n.isKind("mo")&&c.default.setAttribute(n,"accent",!1);let i=t.create("node","munderover",[o,n,r],{accent:!1,accentunder:!1});i.stringlength=t.i-old_i+e.length+(+(t._string.charAt(old_i-1)==' '));t.Push(i)}
复制代码

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-9-6 11:06
本帖最后由 hbghlyj 于 2022-9-6 12:34 编辑 input/tex/TexParser.ts 行466
  1.   public ParseUpTo(name: string, token: string): MmlNode {
  2.     return new TexParser(this.GetUpTo(name, token), this.stack.env,
  3.                          this.configuration).mml();
  4.   }
复制代码
对应JS
  1. t.prototype.ParseUpTo=function(e,r){return new t(this.GetUpTo(e,r),this.stack.env,this.configuration).mml()}
复制代码
改为
  1. t.prototype.ParseUpTo=function(e,r,old_i){return new t(this.GetUpTo(e,r),this.stack.env,this.configuration,old_i).mml()}
复制代码



input/tex/base/BaseMethods.ts行991
  1. BaseMethods.BuildRel = function(parser: TexParser, name: string) {
  2.   // @test BuildRel, BuildRel Expression
  3.   const top = parser.ParseUpTo(name, '\\over');
  4.   const bot = parser.ParseArg(name);
  5.   const node = parser.create('node', 'munderover');
  6.   // This is necessary to get the empty element into the children.
  7.   NodeUtil.setChild(node, 0, bot);
  8.   NodeUtil.setChild(node, 1, null);
  9.   NodeUtil.setChild(node, 2, top);
  10.   const atom = parser.create('node', 'TeXAtom', [node], {texClass: TEXCLASS.REL});
  11.   parser.Push(atom);
  12. };
复制代码
对应的JS改为
  1. O.BuildRel=function(t,e){let old_i=t.i,r=t.ParseUpTo(e,"\\over",old_i+t.base_i),n=t.ParseArg(e),o=t.create("node","munderover");c.default.setChild(o,0,n),c.default.setChild(o,1,null),c.default.setChild(o,2,r);var i=t.create("node","TeXAtom",[o],{texClass:y.TEXCLASS.REL});i.stringlength=t.i-old_i+e.length+(+(t._string.charAt(old_i-1)==' '));t.Push(i)}
复制代码



input/tex/base/BaseMethods.ts行683
  1. BaseMethods.TeXAtom = function(parser: TexParser, name: string, mclass: number) {
  2.   let def: EnvList = {texClass: mclass};
  3.   let mml: StackItem | MmlNode;
  4.   let node: MmlNode;
  5.   let parsed: MmlNode;
  6.   if (mclass === TEXCLASS.OP) {
  7.     def['movesupsub'] = def['movablelimits'] = true;
  8.     const arg = parser.GetArgument(name);
  9.     const match = arg.match(/^\s*\\rm\s+([a-zA-Z0-9 ]+)$/);
  10.     if (match) {
  11.       // @test Mathop
  12.       def['mathvariant'] = TexConstant.Variant.NORMAL;
  13.       node = parser.create('token', 'mi', def, match[1]);
  14.     } else {
  15.       // @test Mathop Cal
  16.       parsed = new TexParser(arg, parser.stack.env, parser.configuration).mml();
  17.       node = parser.create('node', 'TeXAtom', [parsed], def);
  18.     }
  19.     mml = parser.itemFactory.create('fn', node);
  20.   } else {
  21.     // @test Mathrel
  22.     parsed = parser.ParseArg(name);
  23.     mml = parser.create('node', 'TeXAtom', [parsed], def);
  24.   }
  25.   parser.Push(mml);
  26. };
复制代码

对应的JS改为
  1. O.TeXAtom=function(t,e,r){let old_i=t.i,base_i,n,o,i,a={texClass:r};if(r===y.TEXCLASS.OP){a.movesupsub=a.movablelimits=!0;var s=t.GetArgument(e),l=s.match(/^\s*\\rm\s+([a-zA-Z0-9 ]+)$/);l?(a.mathvariant=h.TexConstant.Variant.NORMAL,o=t.create("token","mi",a,l[1])):(base_i=t.openBrace?t.openBrace+t.base_i:t.i-1+t.base_i,t.openBrace=0,i=new f.default(s,t.stack.env,t.configuration,base_i).mml(),o=t.create("node","TeXAtom",[i],a)),n=t.itemFactory.create("fn",o)}else i=t.ParseArg(e),n=t.create("node","TeXAtom",[i],a);n.stringlength=t.i-old_i+e.length+(+(t._string.charAt(old_i-1)==' '));t.Push(n)}
复制代码
遇到\stackrel会出错
\stackrel{\rm heat}{\longrightarrow}

3149

主题

8386

回帖

6万

积分

$\style{scale:11;fill:#eff}꩜$

积分
65391
QQ

显示全部楼层

 楼主| hbghlyj 发表于 2022-9-6 20:04
input/tex/ParseUtil.ts行359
  1.   export function internalText(parser: TexParser, text: string, def: EnvList): MmlNode {
  2.     // @test Label, Fbox, Hbox
  3.     text = text.replace(/^\s+/, entities.nbsp).replace(/\s+$/, entities.nbsp);
  4.     let textNode = parser.create('text', text);
  5.     return parser.create('node', 'mtext', [], def, textNode);
  6.   }
复制代码
对应JS
  1. function v(t,e,r){e=e.replace(/^\s+/,p.entities.nbsp).replace(/\s+$/,p.entities.nbsp);var n=t.create("text",e);return t.create("node","mtext",[],r,n)}
复制代码
改为
  1. function v(t,e,r){e=e.replace(/^\s+/,p.entities.nbsp).replace(/\s+$/,p.entities.nbsp);let n=t.create("text",e),c=t.create("node","mtext",[],r,n);c.sourcePos=t.i+t.base_i;t.openBrace=0;return c}
复制代码



input/tex/base/BaseMethods.ts行1012
  1. BaseMethods.HBox = function(parser: TexParser, name: string, style: string, font?: string) {
  2.   // @test Hbox
  3.   parser.PushAll(ParseUtil.internalMath(parser, parser.GetArgument(name), style, font));
  4. };
复制代码

对应JS
  1. O.HBox=function(t,e,r,n){t.PushAll(d.default.internalMath(t,t.GetArgument(e),r,n))}
复制代码

改为
  1. O.HBox=function(t,e,r,n){let old_i=t.i,c=d.default.internalMath(t,t.GetArgument(e),r,n);if(c.length==1)c[0].stringlength=t.i-old_i+e.length+(+(t._string.charAt(old_i-1)==' '));t.PushAll(c)}
复制代码

但是无法处理
  1. \text {1$x$2}
复制代码

这样的text内部包含公式

手机版|悠闲数学娱乐论坛(第3版)

GMT+8, 2025-3-4 15:57

Powered by Discuz!

× 快速回复 返回顶部 返回列表