Merge branch 'pryor'
[lambda.git] / jsMath / extensions / eqn-number.js
1 /*
2  *  extensions/eqn-number.js
3  *  
4  *  Part of the jsMath package for mathematics on the web.
5  *
6  *  This file causes jsMath to add equation numbers to displayed
7  *  equations.  These are displayed at the right, but the styles can
8  *  be controlled through the jsMath.EqnNumber object.  Equations
9  *  are numbered if they include a \label{xxx} call, and the macro
10  *  \ref{xxx} can be used to refer to the equation number elsewhere
11  *  in the document (it must appear by itself in a math formula,
12  *  e.g., $\ref{xxx}$).  The "label-ref" CSS style can be used to
13  *  style the references.
14  *  
15  *  If jsMath.EqnNumber.autonumber is set to 1, then ALL displayed
16  *  equations will be numberd.  Use the \nolabel macro to prevent
17  *  equation numbering on an equation.
18  *  
19  *  You can activate eqn-numbering by calling
20  *  
21  *    jsMath.Extension.Require('eqn-number');
22  *    
23  *  once jsMath.js has been loaded, or by adding "extensions/eqn-number.js"
24  *  to the loadFiles array in jsMath/easy/load.js.
25  *  
26  *  ---------------------------------------------------------------------
27  *
28  *  Copyright 2008 by Davide P. Cervone
29  * 
30  *  Licensed under the Apache License, Version 2.0 (the "License");
31  *  you may not use this file except in compliance with the License.
32  *  You may obtain a copy of the License at
33  * 
34  *      http://www.apache.org/licenses/LICENSE-2.0
35  * 
36  *  Unless required by applicable law or agreed to in writing, software
37  *  distributed under the License is distributed on an "AS IS" BASIS,
38  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
39  *  See the License for the specific language governing permissions and
40  *  limitations under the License.
41  */
42
43 /********************************************************************/
44
45 if (jsMath.EqnNumber) {jsMath.EqnNumber_old = jsMath.EqnNumber}
46
47 jsMath.EqnNumber = {
48   
49   styles: {
50     '.jsMath_displayBox, .tex2math_div': {position: 'relative'},
51     '.jsMath_number': {
52         position: 'absolute',
53         right: '2em', top: '50%', 'margin-top': '-.5em',
54         height: 'auto', width: 'auto'
55     },
56     '.jsMath_ref': {'text-decoration': 'none'}
57   },
58
59   autonumber: 0,  // set to 1 to have ALL equations numbered
60
61   number: 0,
62   format: function (n) {return n},
63   formatLabel: function (n) {return '<A NAME="eqn-'+n+'">('+n+')</A>'},
64   formatRef: function (n) {return '(<A CLASS="jsMath_ref" HREF="#eqn-'+n+'">'+n+'</A>)'},
65   
66   _label: null,   // flag set when \label{x} is used
67   _labels: {},    // stores label-name => label-value pairs
68   _refs: {},      // stores elements referring to undefined labels
69   _nolabel: 0,    // set by \nolabel
70   
71   nextNumber: function () {
72     var ref = this.format(++this.number);
73     if (this._label) {
74       this._labels[this._label] = ref;
75       if (this._refs[this._label]) this.fixRefs(this._label);
76     }
77     return this.formatLabel(ref);
78   },
79   
80   isRef: function (element) {
81     var tex = element.innerHTML;
82     var result = tex.match(/^\s*\\ref\s*\{([^\}]+)\}\s*$/);
83     if (!result) {return 0}
84     var ref = result[1];
85     if (this._labels[ref]) {
86       this.setRef(element,ref);
87     } else {
88       if (!this._refs[ref]) {this._refs[ref] = []}
89       this._refs[ref][this._refs[ref].length] = element;
90     }
91     return 1;
92   },
93   
94   setRef: function (element,ref) {
95     element.innerHTML = this.formatRef(this._labels[ref]);
96     element.className = "label-ref";
97   },
98   
99   fixRefs: function (label) {
100     for (var i = 0; i < this._refs[label].length; i++)
101       {this.setRef(this._refs[label][i],label)}
102     delete this._refs[label];
103   },
104   
105   badRefs: function () {
106     for (var label in this._refs) {
107       for (var i = 0; i < this._refs[label].length; i++) {
108         var element = this._refs[label][i];
109         element.className = "typeset";
110         element.innerHTML = "<span class='error'>Reference '"+label+"' is undefined</span>";
111       }
112     }
113   },
114   
115   makeDIV: function (element) {
116     var div = document.createElement('div');
117     div.className = 'jsMath_displayBox';
118     div.innerHTML = '<div class="jsMath_number">' + this.nextNumber() + '</div>';
119     element.parentNode.insertBefore(div,element);
120     element.parentNode.removeChild(element);
121     div.appendChild(element);
122   },
123   
124   makeSPAN: function (element) {
125     var span = document.createElement('span');
126     span.className = 'jsMath_number';
127     span.style.display = 'inline-block';
128     span.innerHTML = jsMath.EqnNumber.nextNumber();
129     element.parentNode.insertBefore(span,element);
130   },
131
132   ConvertMath: function (style,element,nocache) {
133     var EqnNumber = jsMath.EqnNumber;
134     if (EqnNumber.isRef(element)) return;
135     EqnNumber._label = null; EqnNumber._nolabel = 0;
136     this.ConvertMath_old(style,element,nocache);
137     if (EqnNumber._label || (EqnNumber.autonumber && !EqnNumber._nolabel)) {
138       if (element.tagName.toLowerCase() == 'div') {
139         EqnNumber.makeDIV(element);
140       } else if (element.parentNode.className == 'tex2math_div') {
141         EqnNumber.makeSPAN(element);
142       }
143     }
144   },
145   
146   ProcessComplete: function () {
147     jsMath.EqnNumber.badRefs();
148     this.ProcessComplete_old.apply(this,arguments);
149   },
150   
151   Init: function () {
152     jsMath.Setup.Styles(this.styles);
153     jsMath.Translate.ConvertMath_old = jsMath.Translate.ConvertMath;
154     jsMath.Translate.ConvertMath = this.ConvertMath;
155     jsMath.Translate.ProcessComplete_old = jsMath.Translate.ProcessComplete;
156     jsMath.Translate.ProcessComplete = this.ProcessComplete;
157   },
158   
159   environments: {
160     'equation*': 'Star',
161     'eqnarray*': 'Star',
162     'align*':    'Star',
163     'multline*': 'Star',
164     'gather*':   'Star',
165     align:       ['StarExtension','AMSmath'],
166     multline:    ['StarExtension','AMSmath'],
167     gather:      ['StarExtension','AMSmath']
168   },
169   
170   ResetStarEnvironments: function () {
171     var Nenv = jsMath.EqnNumber.environments;
172     var Penv = jsMath.Parser.prototype.environments;
173     for (var name in Nenv) {
174       if (name.match(/\*$/)) {Penv[name] = Nenv[name]}
175     }
176   }
177
178 };
179
180 if (jsMath.EqnNumber_old) {
181   jsMath.Insert(jsMath.EqnNumber,jsMath.EqnNumber_old);
182   delete jsMath.EqnNumber_old;
183 }
184
185 jsMath.Package(jsMath.Parser,{
186   macros: {
187     label:    'Label',
188     nolabel:  'NoLabel',
189     nonumber: 'NoLabel',
190     ref:      'Ref'
191   },
192   
193   environments: jsMath.EqnNumber.environments,
194   
195   Label: function (name) {
196     var label = this.GetArgument(this.cmd+name); if (this.error) return;
197     var EqnNumber = jsMath.EqnNumber;
198     if (!EqnNumber._label) {
199       if (!EqnNumber._labels[label]) {
200         EqnNumber._label = label;
201         EqnNumber._nolabel = 0;
202       } else {
203         this.Error("Label '"+label+"' is already defined");
204       }
205     } else {
206       this.Error(this.cmd+name+' can only be used once in an equation');
207     }
208   },
209   
210   NoLabel: function (name) {
211     var EqnNumber = jsMath.EqnNumber;
212     EqnNumber._label = null; EqnNumber._nolabel = 1;
213   },
214   
215   Ref: function (name) {
216     this.Error(this.cmd+name+' must be used by itself');
217   },
218   
219   Star: function (name) {
220     this.NoLabel();
221     var cmd = this.environments[name.substr(0,name.length-1)];
222     if (typeof(cmd) === 'string') {cmd = [cmd]}
223     this[cmd[0]](name,cmd.slice(1));
224   },
225   
226   StarExtension: function (name,data) {
227     try {this.Extension(name,data)} catch (e) {}
228     jsMath.Synchronize(jsMath.EqnNumber.ResetStarEnvironments);
229     throw "restart";
230   }
231
232 });
233
234 jsMath.EqnNumber.Init();