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