]> git.lyx.org Git - lyx.git/blob - src/mathed/math_extern.C
enable entering \mathbf#1 directly
[lyx.git] / src / mathed / math_extern.C
1
2 // This file contains most of the magic that extracts "context
3 // information" from the unstructered layout-oriented stuff in an
4 // MathArray.
5
6 #include <algorithm>
7
8 #include "math_charinset.h"
9 #include "math_deliminset.h"
10 #include "math_diffinset.h"
11 #include "math_exfuncinset.h"
12 #include "math_exintinset.h"
13 #include "math_funcinset.h"
14 #include "math_fracinset.h"
15 #include "math_matrixinset.h"
16 #include "math_mathmlstream.h"
17 #include "math_scriptinset.h"
18 #include "math_stringinset.h"
19 #include "math_symbolinset.h"
20 #include "Lsstream.h"
21 #include "debug.h"
22
23
24 using std::ostream;
25 using std::istringstream;
26 using std::find_if;
27
28
29 ostream & operator<<(ostream & os, MathArray const & ar)
30 {
31         NormalStream ns(os);    
32         ns << ar;
33         return os;
34 }
35
36
37 // define a function for tests
38 typedef bool TestItemFunc(MathInset *);
39
40 // define a function for replacing subexpressions
41 typedef MathInset * ReplaceArgumentFunc(const MathArray & ar);
42
43
44
45 // try to extract a super/subscript 
46 // modify iterator position to point behind the thing
47 bool extractScript(MathArray & ar,
48         MathArray::iterator & pos, MathArray::iterator last)
49 {
50         // nothing to get here
51         if (pos == last)
52                 return false;
53
54         // is this a scriptinset?
55         if (!(*pos)->asScriptInset())
56                 return false;
57
58         // it is a scriptinset, use it.
59         ar.push_back(*pos);
60         ++pos;
61         return true;
62 }
63
64
65 // try to extract an "argument" to some function.
66 // returns position behind the argument
67 MathArray::iterator extractArgument(MathArray & ar,
68         MathArray::iterator pos, MathArray::iterator last, string const & = "")
69 {
70         // nothing to get here
71         if (pos == last)
72                 return pos;
73
74         // something deliminited _is_ an argument
75         if ((*pos)->asDelimInset()) {
76                 ar.push_back(*pos);
77                 return pos + 1;
78         }
79
80         // always take the first thing, no matter what it is
81         ar.push_back(*pos);
82
83         // go ahead if possible
84         ++pos;
85         if (pos == last)
86                 return pos;
87
88         // if the next item is a subscript, it most certainly belongs to the
89         // thing we have
90         extractScript(ar, pos, last);
91         if (pos == last)
92                 return pos;
93
94         // but it might be more than that.
95         // FIXME: not implemented
96         //for (MathArray::iterator it = pos + 1; it != last; ++it) {
97         //      // always take the first thing, no matter
98         //      if (it == pos) {
99         //              ar.push_back(*it);
100         //              continue;
101         //      }
102         //}
103         return pos;
104 }
105
106
107 MathScriptInset const * asScript(MathArray::const_iterator it)
108 {
109         if (it->nucleus()->asScriptInset())
110                 return 0;
111         ++it;
112         if (!it->nucleus())
113                 return 0;
114         return it->nucleus()->asScriptInset();
115 }
116
117
118
119 // returns sequence of char with same code starting at it up to end
120 // it might be less, though...
121 MathArray::const_iterator charSequence(MathArray::const_iterator it,
122         MathArray::const_iterator end, string & s, MathTextCodes & c)
123 {
124         MathCharInset const * p = (*it)->asCharInset();
125         c = p->code();
126         for (; it != end; ++it) {
127                 p = (*it)->asCharInset();
128                 if (!p || p->code() != c)
129                         break;
130                 s += p->getChar();
131         }
132         return it;
133 }
134
135
136 void extractStrings(MathArray & ar)
137 {
138         //lyxerr << "\nStrings from: " << ar << "\n";
139         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
140                 MathArray::iterator it = ar.begin() + i;
141                 if (!(*it)->asCharInset())
142                         continue;
143
144                 // create proper string inset
145                 MathStringInset * p = new MathStringInset;
146                 MathArray::const_iterator
147                         jt = charSequence(it, ar.end(), p->str_, p->code_);
148
149                 // clean up
150                 (*it).reset(p);
151                 ar.erase(i + 1, jt - ar.begin());
152         }
153         //lyxerr << "\nStrings to: " << ar << "\n";
154 }
155
156
157 MathInset * singleItem(MathArray & ar)
158 {
159         return ar.size() == 1 ? ar.begin()->nucleus() : 0;
160 }
161
162
163 void extractMatrices(MathArray & ar)
164 {
165         //lyxerr << "\nMatrices from: " << ar << "\n";
166         for (MathArray::iterator it = ar.begin(); it != ar.end(); ++it) {
167                 MathDelimInset * del = (*it)->asDelimInset();
168                 if (!del)
169                         continue;
170                 MathInset * arr = singleItem(del->cell(0));
171                 if (!arr || !arr->asArrayInset())
172                         continue;
173                 *it = MathAtom(new MathMatrixInset(*(arr->asArrayInset())));
174         }
175         //lyxerr << "\nMatrices to: " << ar << "\n";
176 }
177
178
179 // convert this inset somehow to a string
180 bool extractString(MathInset * p, string & str)
181 {
182         if (!p)
183                 return false;
184         if (p->getChar()) {
185                 str = string(1, p->getChar());
186                 return true;
187         }
188         if (p->asStringInset()) {
189                 str = p->asStringInset()->str();
190                 return true;
191         }
192         return false;
193 }
194
195
196 // convert this inset somehow to a number
197 bool extractNumber(MathArray const & ar, int & i)
198 {
199         string s;
200         MathTextCodes c;
201         charSequence(ar.begin(), ar.end(), s, c);
202         istringstream is(s.c_str());
203         is >> i;
204         return is;
205 }
206
207
208 bool testString(MathInset * p, const string & str)
209 {
210         string s;
211         return extractString(p, s) && str == s;
212 }
213
214
215 // search end of nested sequence
216 MathArray::iterator endNestSearch(
217         MathArray::iterator it,
218         MathArray::iterator last,
219         TestItemFunc testOpen,
220         TestItemFunc testClose
221 )
222 {
223         for (int level = 0; it != last; ++it) {
224                 if (testOpen(it->nucleus()))
225                         ++level;
226                 if (testClose(it->nucleus()))
227                         --level;
228                 if (level == 0)
229                         break;
230         }
231         return it;
232 }
233
234
235 // replace nested sequences by a real Insets
236 void replaceNested(
237         MathArray & ar,
238         TestItemFunc testOpen,
239         TestItemFunc testClose,
240         ReplaceArgumentFunc replaceArg
241 )
242 {
243         // use indices rather than iterators for the loop  because we are going
244         // to modify the array.
245         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
246                 // check whether this is the begin of the sequence
247                 MathArray::iterator it = ar.begin() + i;
248                 if (!testOpen(it->nucleus()))
249                         continue;
250
251                 // search end of sequence
252                 MathArray::iterator jt = endNestSearch(it, ar.end(), testOpen, testClose);
253                 if (jt == ar.end())
254                         continue;
255
256                 // create a proper inset as replacement
257                 MathInset * p = replaceArg(MathArray(it + 1, jt));
258
259                 // replace the original stuff by the new inset
260                 ar.erase(it + 1, jt + 1);
261                 (*it).reset(p);
262         }
263
264
265
266
267 //
268 // split scripts into seperate super- and subscript insets. sub goes in
269 // front of super... 
270 //
271
272 void splitScripts(MathArray & ar)
273 {
274         //lyxerr << "\nScripts from: " << ar << "\n";
275         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
276                 MathArray::iterator it = ar.begin() + i;
277
278                 // is this script inset?
279                 MathScriptInset * p = (*it)->asScriptInset();
280                 if (!p)
281                         continue;
282
283                 // no problem if we don't have both...
284                 if (!p->hasUp() || !p->hasDown())
285                         continue;
286
287                 // create extra script inset and move superscript over
288                 MathScriptInset * q = new MathScriptInset;
289                 q->ensure(true); 
290                 q->up().data_.swap(p->up().data_);
291                 p->removeScript(true);
292
293                 // insert new inset behind
294                 ++i;
295                 ar.insert(i, MathAtom(q)); 
296         }
297         //lyxerr << "\nScripts to: " << ar << "\n";
298 }
299
300
301 //
302 // extract exp(...)
303 //
304
305 void extractExps(MathArray & ar)
306 {
307         //lyxerr << "\nExps from: " << ar << "\n";
308
309         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
310                 MathArray::iterator it = ar.begin() + i;
311
312                 // is this 'e'?
313                 MathCharInset const * p = (*it)->asCharInset();
314                 if (!p || p->getChar() != 'e')
315                         continue;
316
317                 // we need an exponent but no subscript
318                 MathScriptInset * sup = (*(it + 1))->asScriptInset();
319                 if (!sup || sup->hasDown())
320                         continue;
321
322                 // create a proper exp-inset as replacement
323                 MathExFuncInset * func = new MathExFuncInset("exp");
324                 func->cell(0) = sup->cell(1);
325
326                 // clean up
327                 (*it).reset(func);
328                 ar.erase(it + 1);
329         }
330         //lyxerr << "\nExps to: " << ar << "\n";
331 }
332
333
334 //
335 // search deliminiters
336 //
337
338 bool testOpenParan(MathInset * p)
339 {
340         return testString(p, "(");
341 }
342
343
344 bool testCloseParan(MathInset * p)
345 {
346         return testString(p, ")");
347 }
348
349
350 MathInset * replaceDelims(const MathArray & ar)
351 {
352         MathDelimInset * del = new MathDelimInset("(", ")");
353         del->cell(0) = ar;
354         return del;
355 }
356
357
358 // replace '('...')' sequences by a real MathDelimInset
359 void extractDelims(MathArray & ar)
360 {
361         //lyxerr << "\nDelims from: " << ar << "\n";
362         replaceNested(ar, testOpenParan, testCloseParan, replaceDelims);
363         //lyxerr << "\nDelims to: " << ar << "\n";
364 }
365
366
367
368 //
369 // search well-known functions
370 //
371
372
373 // replace 'f' '(...)' and 'f' '^n' '(...)' sequences by a real MathExFuncInset
374 // assume 'extractDelims' ran before
375 void extractFunctions(MathArray & ar)
376 {
377         // we need at least two items...
378         if (ar.size() <= 1)
379                 return;
380
381         //lyxerr << "\nFunctions from: " << ar << "\n";
382         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
383                 MathArray::iterator it = ar.begin() + i;
384                 MathArray::iterator jt = it + 1;
385
386                 string name;
387                 // is it a function?
388                 if ((*it)->asFuncInset()) { 
389                         // it certainly is if it is well known...
390                         name = (*it)->asFuncInset()->name();
391                 } else {
392                         // is this a user defined function?
393                         // it it probably not, if it doesn't have a name.
394                         if (!extractString((*it).nucleus(), name))
395                                 continue;
396                         // it is not if it has no argument
397                         if (jt == ar.end())
398                                 continue;
399                         // guess so, if this is followed by
400                         // a DelimInset with a single item in the cell
401                         MathDelimInset * del = (*jt)->asDelimInset();
402                         if (!del || del->cell(0).size() != 1)
403                                 continue;
404                         // fall trough into main branch
405                 }
406
407                 // do we have an exponent like in
408                 // 'sin' '^2' 'x' -> 'sin(x)' '^2'
409                 MathArray exp;
410                 extractScript(exp, jt, ar.end());
411         
412                 // create a proper inset as replacement
413                 MathExFuncInset * p = new MathExFuncInset(name);
414
415                 // jt points to the "argument". Get hold of this.
416                 MathArray::iterator st = extractArgument(p->cell(0), jt, ar.end());
417
418                 // replace the function name by a real function inset
419                 (*it).reset(p);
420                 
421                 // remove the source of the argument from the array
422                 ar.erase(it + 1, st);
423
424                 // re-insert exponent
425                 ar.insert(i + 1, exp);
426                 //lyxerr << "\nFunctions to: " << ar << "\n";
427         }
428
429
430
431 //
432 // search integrals
433 //
434
435 bool testSymbol(MathInset * p, string const & name)
436 {
437         return p->asSymbolInset() && p->asSymbolInset()->name() == name;
438 }
439
440
441 bool testIntSymbol(MathInset * p)
442 {
443         return testSymbol(p, "int");
444 }
445
446
447 bool testIntDiff(MathInset * p)
448 {
449         return testString(p, "d");
450 }
451
452
453 // replace '\int' ['_^'] x 'd''x'(...)' sequences by a real MathExIntInset
454 // assume 'extractDelims' ran before
455 void extractIntegrals(MathArray & ar)
456 {
457         // we need at least three items...
458         if (ar.size() <= 2)
459                 return;
460
461         //lyxerr << "\nIntegrals from: " << ar << "\n";
462         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
463                 MathArray::iterator it = ar.begin() + i;
464
465                 // is this a integral name?
466                 if (!testIntSymbol(it->nucleus()))
467                         continue;
468
469                 // search 'd'
470                 MathArray::iterator jt =
471                         endNestSearch(it, ar.end(), testIntSymbol, testIntDiff);
472
473                 // something sensible found?
474                 if (jt == ar.end())
475                         continue;
476
477                 // create a proper inset as replacement
478                 MathExIntInset * p = new MathExIntInset("int");
479
480                 // collect subscript if any
481                 MathArray::iterator st = it + 1;
482                 if (st != ar.end())
483                         if (MathScriptInset * sub = (*st)->asScriptInset()) 
484                                 if (sub->hasDown()) {
485                                         p->cell(2) = sub->down().data_;
486                                         ++st;
487                                 }
488
489                 // collect superscript if any
490                 if (st != ar.end())
491                         if (MathScriptInset * sup = (*st)->asScriptInset()) 
492                                 if (sup->hasUp()) {
493                                         p->cell(3) = sup->up().data_;
494                                         ++st;
495                                 }
496
497                 // core ist part from behind the scripts to the 'd'
498                 p->cell(0) = MathArray(st, jt);
499
500                 // use the "thing" behind the 'd' as differential
501                 MathArray::iterator tt = extractArgument(p->cell(1), jt + 1, ar.end());
502                 
503                 // remove used parts
504                 ar.erase(it + 1, tt);
505                 (*it).reset(p);
506         }
507         //lyxerr << "\nIntegrals to: " << ar << "\n";
508 }
509
510
511 //
512 // search sums
513 //
514
515 bool testSumSymbol(MathInset * p)
516 {
517         return testSymbol(p, "sum");
518 }
519
520
521 bool testEqualSign(MathAtom const & at)
522 {
523         return testString(at.nucleus(), "=");
524 }
525
526
527
528 // replace '\sum' ['_^'] f(x) sequences by a real MathExIntInset
529 // assume 'extractDelims' ran before
530 void extractSums(MathArray & ar)
531 {
532         // we need at least two items...
533         if (ar.size() <= 1)
534                 return;
535
536         //lyxerr << "\nSums from: " << ar << "\n";
537         for (MathArray::size_type i = 0; i + 1< ar.size(); ++i) {
538                 MathArray::iterator it = ar.begin() + i;
539
540                 // is this a sum name?
541                 if (!testSumSymbol(it->nucleus()))
542                         continue;
543
544                 // create a proper inset as replacement
545                 MathExIntInset * p = new MathExIntInset("sum");
546
547                 // collect lower bound and summation index
548                 MathArray::iterator st = it + 1;
549                 if (st != ar.end())
550                         if (MathScriptInset * sub = (*st)->asScriptInset())
551                                 if (sub->hasDown()) {
552                                         // try to figure out the summation index from the subscript
553                                         MathArray & ar = sub->down().data_;
554                                         MathArray::iterator it =
555                                                 find_if(ar.begin(), ar.end(), &testEqualSign);
556                                         if (it != ar.end()) {
557                                                 // we found a '=', use everything in front of that as index,
558                                                 // and everything behind as lower index
559                                                 p->cell(1) = MathArray(ar.begin(), it);
560                                                 p->cell(2) = MathArray(it + 1, ar.end());
561                                         } else {
562                                                 // use everything as summation index, don't use scripts.
563                                                 p->cell(1) = ar;
564                                         }
565                                         ++st;
566                                 }
567
568                 // collect upper bound
569                 if (st != ar.end())
570                         if (MathScriptInset * sup = (*st)->asScriptInset())
571                                 if (sup->hasUp()) {
572                                         p->cell(3) = sup->up().data_;
573                                         ++st;
574                                 }
575
576                 // use some  behind the script as core
577                 MathArray::iterator tt = extractArgument(p->cell(0), st, ar.end());
578
579                 // cleanup
580                 ar.erase(it + 1, tt);
581                 (*it).reset(p);
582         }
583         //lyxerr << "\nSums to: " << ar << "\n";
584 }
585
586
587 //
588 // search differential stuff
589 //
590
591 // tests for 'd' or '\partial'
592 bool testDiffItem(MathAtom const & at)
593 {
594         return testString(at.nucleus(), "d");
595 }
596
597
598 bool testDiffArray(MathArray const & ar)
599 {
600         return ar.size() && testDiffItem(ar.front());
601 }
602
603
604 bool testDiffFrac(MathInset * p)
605 {
606         MathFracInset * f = p->asFracInset();
607         return f && testDiffArray(f->cell(0)) && testDiffArray(f->cell(1));
608 }
609
610
611 // is this something like ^number?
612 bool extractDiffExponent(MathArray::iterator it, int & i)
613 {
614         if (!(*it)->asScriptInset())
615                 return false;
616
617         string s;
618         if (!extractString((*it).nucleus(), s))
619                 return false;
620         istringstream is(s.c_str());
621         is >> i;
622         return is;
623 }
624
625
626 void extractDiff(MathArray & ar)
627 {
628         //lyxerr << "\nDiffs from: " << ar << "\n";
629         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
630                 MathArray::iterator it = ar.begin() + i;
631
632                 // is this a "differential fraction"?
633                 if (!testDiffFrac(it->nucleus()))
634                         continue;
635                 
636                 MathFracInset * f = (*it)->asFracInset();
637                 if (!f) {
638                         lyxerr << "should not happen\n";
639                         continue;
640                 }
641
642                 // create a proper diff inset
643                 MathDiffInset * diff = new MathDiffInset;
644
645                 // collect function, let jt point behind last used item
646                 MathArray::iterator jt = it + 1; 
647                 //int n = 1; 
648                 MathArray & numer = f->cell(0);
649                 if (numer.size() > 1 && numer.at(1)->asScriptInset()) {
650                         // this is something like  d^n f(x) / d... or  d^n / d...
651                         // FIXME
652                         //n = 1;        
653                         if (numer.size() > 2) 
654                                 diff->cell(0) = MathArray(numer.begin() + 2, numer.end());
655                         else
656                                 jt = extractArgument(diff->cell(0), jt, ar.end());
657                 } else {
658                         // simply d f(x) / d... or  d/d...
659                         if (numer.size() > 1) 
660                                 diff->cell(0) = MathArray(numer.begin() + 1, numer.end());
661                         else
662                                 jt = extractArgument(diff->cell(0), jt, ar.end());
663                 }
664
665                 // collect denominator parts
666                 MathArray & denom = f->cell(1);
667                 for (MathArray::iterator dt = denom.begin(); dt != denom.end();) {
668                         // find the next 'd'
669                         MathArray::iterator et = find_if(dt + 1, denom.end(), &testDiffItem);
670
671                         // point before this
672                         MathArray::iterator st = et - 1;
673                         MathScriptInset * script = (*st)->asScriptInset();
674                         if (script && script->hasUp()) {
675                                 // things like   d.../dx^n
676                                 int mult = 1;
677                                 if (extractNumber(script->up().data_, mult)) {
678                                         //lyxerr << "mult: " << mult << endl;
679                                         for (int i = 0; i < mult; ++i)
680                                                 diff->addDer(MathArray(dt + 1, st));
681                                 }
682                         } else {
683                                 // just  d.../dx
684                                 diff->addDer(MathArray(dt + 1, et));
685                         }
686                         dt = et;
687                 }
688
689                 // cleanup
690                 ar.erase(it + 1, jt);
691                 (*it).reset(diff);
692         }
693         //lyxerr << "\nDiffs to: " << ar << "\n";
694 }
695
696
697
698 //
699 // combine searches
700 //
701
702 void extractStructure(MathArray & ar)
703 {
704         splitScripts(ar);
705         extractMatrices(ar);
706         extractDelims(ar);
707         extractFunctions(ar);
708         extractIntegrals(ar);
709         extractSums(ar);
710         extractDiff(ar);
711         extractExps(ar);
712         extractStrings(ar);
713 }
714
715
716 void write(MathArray const & dat, WriteStream & wi)
717 {
718         MathArray ar = dat;
719         extractStrings(ar);
720         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
721                 wi.firstitem() = (it == ar.begin());
722                 MathInset const * p = it->nucleus();
723                 if (it + 1 != ar.end()) {
724                         if (MathScriptInset const * q = asScript(it)) {
725                                 q->write2(p, wi);
726                                 ++it;
727                                 continue;
728                         } 
729                 }
730                 p->write(wi);
731         }
732 }
733
734
735 void normalize(MathArray const & ar, NormalStream & os)
736 {
737         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it)
738                 (*it)->normalize(os);
739 }
740
741
742 void octavize(MathArray const & dat, OctaveStream & os)
743 {
744         MathArray ar = dat;
745         extractStructure(ar);
746         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
747                 MathInset const * p = it->nucleus();
748                 if (it + 1 != ar.end()) {
749                         if (MathScriptInset const * q = asScript(it)) {
750                                 q->octavize2(p, os);
751                                 ++it;   
752                                 continue;
753                         }
754                 }
755                 p->octavize(os);
756         }
757 }
758
759
760 void maplize(MathArray const & dat, MapleStream & os)
761 {
762         MathArray ar = dat;
763         extractStructure(ar);
764         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
765                 MathInset const * p = it->nucleus();
766                 if (it + 1 != ar.end()) {
767                         if (MathScriptInset const * q = asScript(it)) {
768                                 q->maplize2(p, os);
769                                 ++it;   
770                                 continue;
771                         }
772                 }
773                 p->maplize(os);
774         }
775 }
776
777
778 void mathmlize(MathArray const & dat, MathMLStream & os)
779 {
780         MathArray ar = dat;
781         extractStructure(ar);
782         if (ar.size() == 0)
783                 os << "<mrow/>";
784         else if (ar.size() == 1)
785                 os << ar.begin()->nucleus();
786         else {
787                 os << MTag("mrow");
788                 for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
789                         MathInset const * p = it->nucleus();
790                         if (it + 1 != ar.end()) {
791                                 if (MathScriptInset const * q = asScript(it)) {
792                                         q->mathmlize2(p, os);
793                                         ++it;   
794                                         continue;
795                                 }
796                         }
797                         p->mathmlize(os);
798                 }
799                 os << ETag("mrow");
800         }
801 }
802