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