]> git.lyx.org Git - lyx.git/blob - src/mathed/math_extern.C
make \newcommand{\bb}[1]{\mathbf{#1}} work for read/write/display.
[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.c_str());
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 // extract exp(...)
298 //
299
300 void extractExps(MathArray & ar)
301 {
302         lyxerr << "\nExps from: " << ar << "\n";
303
304         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
305                 MathArray::iterator it = ar.begin() + i;
306
307                 // is this 'e'?
308                 MathCharInset const * p = (*it)->asCharInset();
309                 if (!p || p->getChar() != 'e')
310                         continue;
311
312                 // we need an exponent but no subscript
313                 MathScriptInset * sup = (*(it + 1))->asScriptInset();
314                 if (!sup || sup->hasDown())
315                         continue;
316
317                 // create a proper exp-inset as replacement
318                 MathExFuncInset * func = new MathExFuncInset("exp");
319                 func->cell(0) = sup->cell(1);
320
321                 // clean up
322                 (*it).reset(func);
323                 ar.erase(it + 1);
324         }
325         lyxerr << "\nExps to: " << ar << "\n";
326 }
327
328
329 //
330 // search deliminiters
331 //
332
333 bool testOpenParan(MathInset * p)
334 {
335         return testString(p, "(");
336 }
337
338
339 bool testCloseParan(MathInset * p)
340 {
341         return testString(p, ")");
342 }
343
344
345 MathInset * replaceDelims(const MathArray & ar)
346 {
347         MathDelimInset * del = new MathDelimInset("(", ")");
348         del->cell(0) = ar;
349         return del;
350 }
351
352
353 // replace '('...')' sequences by a real MathDelimInset
354 void extractDelims(MathArray & ar)
355 {
356         lyxerr << "\nDelims from: " << ar << "\n";
357         replaceNested(ar, testOpenParan, testCloseParan, replaceDelims);
358         lyxerr << "\nDelims to: " << ar << "\n";
359 }
360
361
362
363 //
364 // search well-known functions
365 //
366
367
368 // replace 'f' '(...)' and 'f' '^n' '(...)' sequences by a real MathExFuncInset
369 // assume 'extractDelims' ran before
370 void extractFunctions(MathArray & ar)
371 {
372         // we need at least two items...
373         if (ar.size() <= 1)
374                 return;
375
376         lyxerr << "\nFunctions from: " << ar << "\n";
377         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
378                 MathArray::iterator it = ar.begin() + i;
379                 MathArray::iterator jt = it + 1;
380
381                 string name;
382                 // is it a function?
383                 if ((*it)->asFuncInset()) { 
384                         // it certainly is if it is well known...
385                         name = (*it)->asFuncInset()->name();
386                 } else {
387                         // is this a user defined function?
388                         // it it probably not, if it doesn't have a name.
389                         if (!extractString((*it).nucleus(), name))
390                                 continue;
391                         // it is not if it has no argument
392                         if (jt == ar.end())
393                                 continue;
394                         // guess so, if this is followed by
395                         // a DelimInset with a single item in the cell
396                         MathDelimInset * del = (*jt)->asDelimInset();
397                         if (!del || del->cell(0).size() != 1)
398                                 continue;
399                         // fall trough into main branch
400                 }
401
402                 // do we have an exponent like in
403                 // 'sin' '^2' 'x' -> 'sin(x)' '^2'
404                 MathArray exp;
405                 extractScript(exp, jt, ar.end());
406         
407                 // create a proper inset as replacement
408                 MathExFuncInset * p = new MathExFuncInset(name);
409
410                 // jt points to the "argument". Get hold of this.
411                 MathArray::iterator st = extractArgument(p->cell(0), jt, ar.end());
412
413                 // replace the function name by a real function inset
414                 (*it).reset(p);
415                 
416                 // remove the source of the argument from the array
417                 ar.erase(it + 1, st);
418
419                 // re-insert exponent
420                 ar.insert(i + 1, exp);
421                 lyxerr << "\nFunctions to: " << ar << "\n";
422         }
423
424
425
426 //
427 // search integrals
428 //
429
430 bool testSymbol(MathInset * p, string const & name)
431 {
432         return p->asSymbolInset() && p->asSymbolInset()->name() == name;
433 }
434
435
436 bool testIntSymbol(MathInset * p)
437 {
438         return testSymbol(p, "int");
439 }
440
441
442 bool testIntDiff(MathInset * p)
443 {
444         return testString(p, "d");
445 }
446
447
448 // replace '\int' ['_^'] x 'd''x'(...)' sequences by a real MathExIntInset
449 // assume 'extractDelims' ran before
450 void extractIntegrals(MathArray & ar)
451 {
452         // we need at least three items...
453         if (ar.size() <= 2)
454                 return;
455
456         lyxerr << "\nIntegrals from: " << ar << "\n";
457         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
458                 MathArray::iterator it = ar.begin() + i;
459
460                 // is this a integral name?
461                 if (!testIntSymbol(it->nucleus()))
462                         continue;
463
464                 // search 'd'
465                 MathArray::iterator jt =
466                         endNestSearch(it, ar.end(), testIntSymbol, testIntDiff);
467
468                 // something sensible found?
469                 if (jt == ar.end())
470                         continue;
471
472                 // create a proper inset as replacement
473                 MathExIntInset * p = new MathExIntInset("int");
474
475                 // collect subscript if any
476                 MathArray::iterator st = it + 1;
477                 if (st != ar.end())
478                         if (MathScriptInset * sub = (*st)->asScriptInset()) 
479                                 if (sub->hasDown()) {
480                                         p->cell(2) = sub->down().data_;
481                                         ++st;
482                                 }
483
484                 // collect superscript if any
485                 if (st != ar.end())
486                         if (MathScriptInset * sup = (*st)->asScriptInset()) 
487                                 if (sup->hasUp()) {
488                                         p->cell(3) = sup->up().data_;
489                                         ++st;
490                                 }
491
492                 // core ist part from behind the scripts to the 'd'
493                 p->cell(0) = MathArray(st, jt);
494
495                 // use the "thing" behind the 'd' as differential
496                 MathArray::iterator tt = extractArgument(p->cell(1), jt + 1, ar.end());
497                 
498                 // remove used parts
499                 ar.erase(it + 1, tt);
500                 (*it).reset(p);
501         }
502         lyxerr << "\nIntegrals to: " << ar << "\n";
503 }
504
505
506 //
507 // search sums
508 //
509
510 bool testSumSymbol(MathInset * p)
511 {
512         return testSymbol(p, "sum");
513 }
514
515
516 bool testEqualSign(MathAtom const & at)
517 {
518         return testString(at.nucleus(), "=");
519 }
520
521
522
523 // replace '\sum' ['_^'] f(x) sequences by a real MathExIntInset
524 // assume 'extractDelims' ran before
525 void extractSums(MathArray & ar)
526 {
527         // we need at least two items...
528         if (ar.size() <= 1)
529                 return;
530
531         lyxerr << "\nSums from: " << ar << "\n";
532         for (MathArray::size_type i = 0; i + 1< ar.size(); ++i) {
533                 MathArray::iterator it = ar.begin() + i;
534
535                 // is this a sum name?
536                 if (!testSumSymbol(it->nucleus()))
537                         continue;
538
539                 // create a proper inset as replacement
540                 MathExIntInset * p = new MathExIntInset("sum");
541
542                 // collect lower bound and summation index
543                 MathArray::iterator st = it + 1;
544                 if (st != ar.end())
545                         if (MathScriptInset * sub = (*st)->asScriptInset())
546                                 if (sub->hasDown()) {
547                                         // try to figure out the summation index from the subscript
548                                         MathArray & ar = sub->down().data_;
549                                         MathArray::iterator it =
550                                                 std::find_if(ar.begin(), ar.end(), &testEqualSign);
551                                         if (it != ar.end()) {
552                                                 // we found a '=', use everything in front of that as index,
553                                                 // and everything behind as lower index
554                                                 p->cell(1) = MathArray(ar.begin(), it);
555                                                 p->cell(2) = MathArray(it + 1, ar.end());
556                                         } else {
557                                                 // use everything as summation index, don't use scripts.
558                                                 p->cell(1) = ar;
559                                         }
560                                         ++st;
561                                 }
562
563                 // collect upper bound
564                 if (st != ar.end())
565                         if (MathScriptInset * sup = (*st)->asScriptInset())
566                                 if (sup->hasUp()) {
567                                         p->cell(3) = sup->up().data_;
568                                         ++st;
569                                 }
570
571                 // use some  behind the script as core
572                 MathArray::iterator tt = extractArgument(p->cell(0), st, ar.end());
573
574                 // cleanup
575                 ar.erase(it + 1, tt);
576                 (*it).reset(p);
577         }
578         lyxerr << "\nSums to: " << ar << "\n";
579 }
580
581
582 //
583 // search differential stuff
584 //
585
586 // tests for 'd' or '\partial'
587 bool testDiffItem(MathAtom const & at)
588 {
589         return testString(at.nucleus(), "d");
590 }
591
592
593 bool testDiffArray(MathArray const & ar)
594 {
595         return ar.size() && testDiffItem(ar.front());
596 }
597
598
599 bool testDiffFrac(MathInset * p)
600 {
601         MathFracInset * f = p->asFracInset();
602         return f && testDiffArray(f->cell(0)) && testDiffArray(f->cell(1));
603 }
604
605
606 // is this something like ^number?
607 bool extractDiffExponent(MathArray::iterator it, int & i)
608 {
609         if (!(*it)->asScriptInset())
610                 return false;
611
612         string s;
613         if (!extractString((*it).nucleus(), s))
614                 return false;
615         std::istringstream is(s.c_str());
616         is >> i;
617         return is;
618 }
619
620
621 void extractDiff(MathArray & ar)
622 {
623         lyxerr << "\nDiffs from: " << ar << "\n";
624         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
625                 MathArray::iterator it = ar.begin() + i;
626
627                 // is this a "differential fraction"?
628                 if (!testDiffFrac(it->nucleus()))
629                         continue;
630                 
631                 MathFracInset * f = (*it)->asFracInset();
632                 if (!f) {
633                         lyxerr << "should not happen\n";
634                         continue;
635                 }
636
637                 // create a proper diff inset
638                 MathDiffInset * diff = new MathDiffInset;
639
640                 // collect function, let jt point behind last used item
641                 MathArray::iterator jt = it + 1; 
642                 //int n = 1; 
643                 MathArray & numer = f->cell(0);
644                 if (numer.size() > 1 && numer.at(1)->asScriptInset()) {
645                         // this is something like  d^n f(x) / d... or  d^n / d...
646                         // FIXME
647                         //n = 1;        
648                         if (numer.size() > 2) 
649                                 diff->cell(0) = MathArray(numer.begin() + 2, numer.end());
650                         else
651                                 jt = extractArgument(diff->cell(0), jt, ar.end());
652                 } else {
653                         // simply d f(x) / d... or  d/d...
654                         if (numer.size() > 1) 
655                                 diff->cell(0) = MathArray(numer.begin() + 1, numer.end());
656                         else
657                                 jt = extractArgument(diff->cell(0), jt, ar.end());
658                 }
659
660                 // collect denominator parts
661                 MathArray & denom = f->cell(1);
662                 for (MathArray::iterator dt = denom.begin(); dt != denom.end(); ) {
663                         // find the next 'd'
664                         MathArray::iterator et = std::find_if(dt + 1, denom.end(), &testDiffItem);
665
666                         // point before this
667                         MathArray::iterator st = et - 1;
668                         MathScriptInset * script = (*st)->asScriptInset();
669                         if (script && script->hasUp()) {
670                                 // things like   d.../dx^n
671                                 int mult = 1;
672                                 if (extractNumber(script->up().data_, mult)) {
673                                         lyxerr << "mult: " << mult << std::endl;
674                                         for (int i = 0; i < mult; ++i)
675                                                 diff->addDer(MathArray(dt + 1, st));
676                                 }
677                         } else {
678                                 // just  d.../dx
679                                 diff->addDer(MathArray(dt + 1, et));
680                         }
681                         dt = et;
682                 }
683
684                 // cleanup
685                 ar.erase(it + 1, jt);
686                 (*it).reset(diff);
687         }
688         lyxerr << "\nDiffs to: " << ar << "\n";
689 }
690
691
692
693 //
694 // combine searches
695 //
696
697 void extractStructure(MathArray & ar)
698 {
699         splitScripts(ar);
700         extractMatrices(ar);
701         extractDelims(ar);
702         extractFunctions(ar);
703         extractIntegrals(ar);
704         extractSums(ar);
705         extractDiff(ar);
706         extractExps(ar);
707         extractStrings(ar);
708 }
709
710
711 void write(MathArray const & dat, WriteStream & wi)
712 {
713         MathArray ar = dat;
714         extractStrings(ar);
715         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
716                 wi.firstitem() = (it == ar.begin());
717                 MathInset const * p = it->nucleus();
718                 if (it + 1 != ar.end()) {
719                         if (MathScriptInset const * q = asScript(it)) {
720                                 q->write2(p, wi);
721                                 ++it;
722                                 continue;
723                         } 
724                 }
725                 p->write(wi);
726         }
727 }
728
729
730 void normalize(MathArray const & ar, NormalStream & os)
731 {
732         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it)
733                 (*it)->normalize(os);
734 }
735
736
737 void octavize(MathArray const & dat, OctaveStream & os)
738 {
739         MathArray ar = dat;
740         extractStructure(ar);
741         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
742                 MathInset const * p = it->nucleus();
743                 if (it + 1 != ar.end()) {
744                         if (MathScriptInset const * q = asScript(it)) {
745                                 q->octavize2(p, os);
746                                 ++it;   
747                                 continue;
748                         }
749                 }
750                 p->octavize(os);
751         }
752 }
753
754
755 void maplize(MathArray const & dat, MapleStream & os)
756 {
757         MathArray ar = dat;
758         extractStructure(ar);
759         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
760                 MathInset const * p = it->nucleus();
761                 if (it + 1 != ar.end()) {
762                         if (MathScriptInset const * q = asScript(it)) {
763                                 q->maplize2(p, os);
764                                 ++it;   
765                                 continue;
766                         }
767                 }
768                 p->maplize(os);
769         }
770 }
771
772
773 void mathmlize(MathArray const & dat, MathMLStream & os)
774 {
775         MathArray ar = dat;
776         extractStructure(ar);
777         if (ar.size() == 0)
778                 os << "<mrow/>";
779         else if (ar.size() == 1)
780                 os << ar.begin()->nucleus();
781         else {
782                 os << MTag("mrow");
783                 for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
784                         MathInset const * p = it->nucleus();
785                         if (it + 1 != ar.end()) {
786                                 if (MathScriptInset const * q = asScript(it)) {
787                                         q->mathmlize2(p, os);
788                                         ++it;   
789                                         continue;
790                                 }
791                         }
792                         p->mathmlize(os);
793                 }
794                 os << ETag("mrow");
795         }
796 }
797