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