]> git.lyx.org Git - lyx.git/blob - src/mathed/math_extern.C
enable insertion of spaces in all \textxxx modes.
[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 <config.h>
6
7 #include "math_amsarrayinset.h"
8 #include "math_arrayinset.h"
9 #include "math_charinset.h"
10 #include "math_deliminset.h"
11 #include "math_diffinset.h"
12 #include "math_exfuncinset.h"
13 #include "math_exintinset.h"
14 #include "math_fracinset.h"
15 #include "math_liminset.h"
16 #include "math_matrixinset.h"
17 #include "math_mathmlstream.h"
18 #include "math_numberinset.h"
19 #include "math_scriptinset.h"
20 #include "math_stringinset.h"
21 #include "math_symbolinset.h"
22 #include "math_unknowninset.h"
23 #include "math_parser.h"
24 #include "Lsstream.h"
25 #include "debug.h"
26 #include "support/lyxlib.h"
27 #include "support/systemcall.h"
28 #include "support/filetools.h"
29
30 #include <algorithm>
31
32 using std::ostream;
33 using std::istringstream;
34 using std::find_if;
35 using std::endl;
36
37
38 ostream & operator<<(ostream & os, MathArray const & ar)
39 {
40         NormalStream ns(os);
41         ns << ar;
42         return os;
43 }
44
45
46 // define a function for tests
47 typedef bool TestItemFunc(MathInset *);
48
49 // define a function for replacing subexpressions
50 typedef MathInset * ReplaceArgumentFunc(const MathArray & ar);
51
52
53
54 // try to extract a super/subscript
55 // modify iterator position to point behind the thing
56 bool extractScript(MathArray & ar,
57         MathArray::iterator & pos, MathArray::iterator last)
58 {
59         // nothing to get here
60         if (pos == last)
61                 return false;
62
63         // is this a scriptinset?
64         if (!(*pos)->asScriptInset())
65                 return false;
66
67         // it is a scriptinset, use it.
68         ar.push_back(*pos);
69         ++pos;
70         return true;
71 }
72
73
74 // try to extract an "argument" to some function.
75 // returns position behind the argument
76 MathArray::iterator extractArgument(MathArray & ar,
77         MathArray::iterator pos, MathArray::iterator last, string const & = "")
78 {
79         // nothing to get here
80         if (pos == last)
81                 return pos;
82
83         // something deliminited _is_ an argument
84         if ((*pos)->asDelimInset()) {
85                 ar.push_back(*pos);
86                 return pos + 1;
87         }
88
89         // always take the first thing, no matter what it is
90         ar.push_back(*pos);
91
92         // go ahead if possible
93         ++pos;
94         if (pos == last)
95                 return pos;
96
97         // if the next item is a subscript, it most certainly belongs to the
98         // thing we have
99         extractScript(ar, pos, last);
100         if (pos == last)
101                 return pos;
102
103         // but it might be more than that.
104         // FIXME: not implemented
105         //for (MathArray::iterator it = pos + 1; it != last; ++it) {
106         //      // always take the first thing, no matter
107         //      if (it == pos) {
108         //              ar.push_back(*it);
109         //              continue;
110         //      }
111         //}
112         return pos;
113 }
114
115
116 MathScriptInset const * asScript(MathArray::const_iterator it)
117 {
118         if (!it->nucleus())
119                 return 0;
120         if (it->nucleus()->asScriptInset())
121                 return 0;
122         ++it;
123         if (!it->nucleus())
124                 return 0;
125         return it->nucleus()->asScriptInset();
126 }
127
128
129
130 // returns sequence of char with same code starting at it up to end
131 // it might be less, though...
132 string charSequence
133         (MathArray::const_iterator it, MathArray::const_iterator end)
134 {
135         string s;
136         for (; it != end && (*it)->asCharInset(); ++it)
137                 s += (*it)->getChar();
138         return s;
139 }
140
141
142 void extractStrings(MathArray & ar)
143 {
144         //lyxerr << "\nStrings from: " << ar << "\n";
145         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
146                 if (!ar[i]->asCharInset())
147                         continue;
148                 string s = charSequence(ar.begin() + i, ar.end());
149                 ar[i].reset(new MathStringInset(s));
150                 ar.erase(i + 1, i + s.size());
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         // first pass for explicitly delimited stuff
166         for (MathArray::iterator it = ar.begin(); it != ar.end(); ++it) {
167                 MathDelimInset * del = (*it)->asDelimInset();
168                 if (!del)
169                         continue;
170                 MathInset * arr = singleItem(del->cell(0));
171                 if (!arr || !arr->asGridInset())
172                         continue;
173                 *it = MathAtom(new MathMatrixInset(*(arr->asGridInset())));
174         }
175
176         // second pass for AMS "pmatrix" etc
177         for (MathArray::iterator it = ar.begin(); it != ar.end(); ++it) {
178                 MathAMSArrayInset * ams = (*it)->asAMSArrayInset();
179                 if (!ams)
180                         continue;
181                 *it = MathAtom(new MathMatrixInset(*ams));
182         }
183         //lyxerr << "\nMatrices to: " << ar << "\n";
184 }
185
186
187 // convert this inset somehow to a string
188 bool extractString(MathInset * p, string & str)
189 {
190         if (!p)
191                 return false;
192         if (p->getChar()) {
193                 str = string(1, p->getChar());
194                 return true;
195         }
196         if (p->asStringInset()) {
197                 str = p->asStringInset()->str();
198                 return true;
199         }
200         return false;
201 }
202
203
204 // convert this inset somehow to a number
205 bool extractNumber(MathArray const & ar, int & i)
206 {
207         istringstream is(charSequence(ar.begin(), ar.end()).c_str());
208         is >> i;
209         return is;
210 }
211
212
213 bool extractNumber(MathArray const & ar, double & d)
214 {
215         istringstream is(charSequence(ar.begin(), ar.end()).c_str());
216         is >> d;
217         return is;
218 }
219
220
221 bool testString(MathInset * p, const string & str)
222 {
223         string s;
224         return extractString(p, s) && str == s;
225 }
226
227
228 // search end of nested sequence
229 MathArray::iterator endNestSearch(
230         MathArray::iterator it,
231         MathArray::iterator last,
232         TestItemFunc testOpen,
233         TestItemFunc testClose
234 )
235 {
236         for (int level = 0; it != last; ++it) {
237                 if (testOpen(it->nucleus()))
238                         ++level;
239                 if (testClose(it->nucleus()))
240                         --level;
241                 if (level == 0)
242                         break;
243         }
244         return it;
245 }
246
247
248 // replace nested sequences by a real Insets
249 void replaceNested(
250         MathArray & ar,
251         TestItemFunc testOpen,
252         TestItemFunc testClose,
253         ReplaceArgumentFunc replaceArg
254 )
255 {
256         // use indices rather than iterators for the loop  because we are going
257         // to modify the array.
258         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
259                 // check whether this is the begin of the sequence
260                 MathArray::iterator it = ar.begin() + i;
261                 if (!testOpen(it->nucleus()))
262                         continue;
263
264                 // search end of sequence
265                 MathArray::iterator jt = endNestSearch(it, ar.end(), testOpen, testClose);
266                 if (jt == ar.end())
267                         continue;
268
269                 // create a proper inset as replacement
270                 MathInset * p = replaceArg(MathArray(it + 1, jt));
271
272                 // replace the original stuff by the new inset
273                 ar.erase(it + 1, jt + 1);
274                 (*it).reset(p);
275         }
276 }
277
278
279
280 //
281 // split scripts into seperate super- and subscript insets. sub goes in
282 // front of super...
283 //
284
285 void splitScripts(MathArray & ar)
286 {
287         //lyxerr << "\nScripts from: " << ar << "\n";
288         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
289                 MathArray::iterator it = ar.begin() + i;
290
291                 // is this script inset?
292                 MathScriptInset * p = (*it)->asScriptInset();
293                 if (!p)
294                         continue;
295
296                 // no problem if we don't have both...
297                 if (!p->hasUp() || !p->hasDown())
298                         continue;
299
300                 // create extra script inset and move superscript over
301                 MathScriptInset * q = new MathScriptInset;
302                 q->ensure(true);
303                 q->up().data().swap(p->up().data());
304                 p->removeScript(true);
305
306                 // insert new inset behind
307                 ++i;
308                 ar.insert(i, MathAtom(q));
309         }
310         //lyxerr << "\nScripts to: " << ar << "\n";
311 }
312
313
314 //
315 // extract exp(...)
316 //
317
318 void extractExps(MathArray & ar)
319 {
320         //lyxerr << "\nExps from: " << ar << "\n";
321
322         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
323                 MathArray::iterator it = ar.begin() + i;
324
325                 // is this 'e'?
326                 MathCharInset const * p = (*it)->asCharInset();
327                 if (!p || p->getChar() != 'e')
328                         continue;
329
330                 // we need an exponent but no subscript
331                 MathScriptInset * sup = (*(it + 1))->asScriptInset();
332                 if (!sup || sup->hasDown())
333                         continue;
334
335                 // create a proper exp-inset as replacement 
336                 *it = new MathExFuncInset("exp", sup->cell(1));
337                 ar.erase(it + 1);
338         }
339         //lyxerr << "\nExps to: " << ar << "\n";
340 }
341
342
343 //
344 // extract det(...)  from |matrix|
345 //
346 void extractDets(MathArray & ar)
347 {
348         //lyxerr << "\ndet from: " << ar << "\n";
349         for (MathArray::iterator it = ar.begin(); it != ar.end(); ++it) {
350                 MathDelimInset * del = (*it)->asDelimInset();
351                 if (!del)
352                         continue;
353                 if (!del->isAbs())
354                         continue;
355                 *it = new MathExFuncInset("det", del->cell(0));
356         }
357         //lyxerr << "\ndet to: " << ar << "\n";
358 }
359
360
361 //
362 // search numbers
363 //
364
365 bool isDigitOrSimilar(char c)
366 {
367         return ('0' <= c && c <= '9') || c == '.';
368 }
369
370
371 // returns sequence of digits
372 string digitSequence
373         (MathArray::const_iterator it, MathArray::const_iterator end)
374 {
375         string s;
376         for (; it != end && (*it)->asCharInset(); ++it) {
377                 if (!isDigitOrSimilar((*it)->getChar()))
378                         break;
379                 s += (*it)->getChar();
380         }
381         return s;
382 }
383
384
385 void extractNumbers(MathArray & ar)
386 {
387         //lyxerr << "\nNumbers from: " << ar << "\n";
388         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
389                 if (!ar[i]->asCharInset())
390                         continue;
391                 if (!isDigitOrSimilar(ar[i]->asCharInset()->getChar()))
392                         continue;
393
394                 string s = digitSequence(ar.begin() + i, ar.end());
395
396                 ar[i].reset(new MathNumberInset(s));
397                 ar.erase(i + 1, i + s.size());
398         }
399         //lyxerr << "\nNumbers to: " << ar << "\n";
400 }
401
402
403
404 //
405 // search deliminiters
406 //
407
408 bool testOpenParan(MathInset * p)
409 {
410         return testString(p, "(");
411 }
412
413
414 bool testCloseParan(MathInset * p)
415 {
416         return testString(p, ")");
417 }
418
419
420 MathInset * replaceDelims(const MathArray & ar)
421 {
422         return new MathDelimInset("(", ")", ar);
423 }
424
425
426 // replace '('...')' sequences by a real MathDelimInset
427 void extractDelims(MathArray & ar)
428 {
429         //lyxerr << "\nDelims from: " << ar << "\n";
430         replaceNested(ar, testOpenParan, testCloseParan, replaceDelims);
431         //lyxerr << "\nDelims to: " << ar << "\n";
432 }
433
434
435
436 //
437 // search well-known functions
438 //
439
440
441 // replace 'f' '(...)' and 'f' '^n' '(...)' sequences by a real MathExFuncInset
442 // assume 'extractDelims' ran before
443 void extractFunctions(MathArray & ar)
444 {
445         // we need at least two items...
446         if (ar.size() < 2)
447                 return;
448
449         //lyxerr << "\nFunctions from: " << ar << "\n";
450         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
451                 MathArray::iterator it = ar.begin() + i;
452                 MathArray::iterator jt = it + 1;
453
454                 string name;
455                 // is it a function?
456                 if ((*it)->asUnknownInset()) {
457                         // it certainly is if it is well known...
458                         name = (*it)->asUnknownInset()->name();
459                 } else {
460                         // is this a user defined function?
461                         // it it probably not, if it doesn't have a name.
462                         if (!extractString((*it).nucleus(), name))
463                                 continue;
464                         // it is not if it has no argument
465                         if (jt == ar.end())
466                                 continue;
467                         // guess so, if this is followed by
468                         // a DelimInset with a single item in the cell
469                         MathDelimInset * del = (*jt)->asDelimInset();
470                         if (!del || del->cell(0).size() != 1)
471                                 continue;
472                         // fall trough into main branch
473                 }
474
475                 // do we have an exponent like in
476                 // 'sin' '^2' 'x' -> 'sin(x)' '^2'
477                 MathArray exp;
478                 extractScript(exp, jt, ar.end());
479
480                 // create a proper inset as replacement
481                 MathExFuncInset * p = new MathExFuncInset(name);
482
483                 // jt points to the "argument". Get hold of this.
484                 MathArray::iterator st = extractArgument(p->cell(0), jt, ar.end());
485
486                 // replace the function name by a real function inset
487                 (*it).reset(p);
488
489                 // remove the source of the argument from the array
490                 ar.erase(it + 1, st);
491
492                 // re-insert exponent
493                 ar.insert(i + 1, exp);
494                 //lyxerr << "\nFunctions to: " << ar << "\n";
495         }
496 }
497
498
499 //
500 // search integrals
501 //
502
503 bool testSymbol(MathInset * p, string const & name)
504 {
505         return p->asSymbolInset() && p->asSymbolInset()->name() == name;
506 }
507
508
509 bool testIntSymbol(MathInset * p)
510 {
511         return testSymbol(p, "int");
512 }
513
514
515 bool testIntDiff(MathInset * p)
516 {
517         return testString(p, "d");
518 }
519
520
521 // replace '\int' ['_^'] x 'd''x'(...)' sequences by a real MathExIntInset
522 // assume 'extractDelims' ran before
523 void extractIntegrals(MathArray & ar)
524 {
525         // we need at least three items...
526         if (ar.size() < 3)
527                 return;
528
529         //lyxerr << "\nIntegrals from: " << ar << "\n";
530         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
531                 MathArray::iterator it = ar.begin() + i;
532
533                 // is this a integral name?
534                 if (!testIntSymbol(it->nucleus()))
535                         continue;
536
537                 // search 'd'
538                 MathArray::iterator jt =
539                         endNestSearch(it, ar.end(), testIntSymbol, testIntDiff);
540
541                 // something sensible found?
542                 if (jt == ar.end())
543                         continue;
544
545                 // create a proper inset as replacement
546                 MathExIntInset * p = new MathExIntInset("int");
547
548                 // collect subscript if any
549                 MathArray::iterator st = it + 1;
550                 if (st != ar.end())
551                         if (MathScriptInset * sub = (*st)->asScriptInset())
552                                 if (sub->hasDown()) {
553                                         p->cell(2) = sub->down().data();
554                                         ++st;
555                                 }
556
557                 // collect superscript if any
558                 if (st != ar.end())
559                         if (MathScriptInset * sup = (*st)->asScriptInset())
560                                 if (sup->hasUp()) {
561                                         p->cell(3) = sup->up().data();
562                                         ++st;
563                                 }
564
565                 // core ist part from behind the scripts to the 'd'
566                 p->cell(0) = MathArray(st, jt);
567
568                 // use the "thing" behind the 'd' as differential
569                 MathArray::iterator tt = extractArgument(p->cell(1), jt + 1, ar.end());
570
571                 // remove used parts
572                 ar.erase(it + 1, tt);
573                 (*it).reset(p);
574         }
575         //lyxerr << "\nIntegrals to: " << ar << "\n";
576 }
577
578
579 //
580 // search sums
581 //
582
583
584 bool testEqualSign(MathAtom const & at)
585 {
586         return testString(at.nucleus(), "=");
587 }
588
589
590
591 // replace '\sum' ['_^'] f(x) sequences by a real MathExIntInset
592 // assume 'extractDelims' ran before
593 void extractSums(MathArray & ar)
594 {
595         // we need at least two items...
596         if (ar.size() < 2)
597                 return;
598
599         //lyxerr << "\nSums from: " << ar << "\n";
600         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
601                 MathArray::iterator it = ar.begin() + i;
602
603                 // is this a sum name?
604                 if (!testSymbol(it->nucleus(), "sum"))
605                         continue;
606
607                 // create a proper inset as replacement
608                 MathExIntInset * p = new MathExIntInset("sum");
609
610                 // collect lower bound and summation index
611                 MathArray::iterator st = it + 1;
612                 if (st != ar.end())
613                         if (MathScriptInset * sub = (*st)->asScriptInset())
614                                 if (sub->hasDown()) {
615                                         // try to figure out the summation index from the subscript
616                                         MathArray & ar = sub->down().data();
617                                         MathArray::iterator it =
618                                                 find_if(ar.begin(), ar.end(), &testEqualSign);
619                                         if (it != ar.end()) {
620                                                 // we found a '=', use everything in front of that as index,
621                                                 // and everything behind as lower index
622                                                 p->cell(1) = MathArray(ar.begin(), it);
623                                                 p->cell(2) = MathArray(it + 1, ar.end());
624                                         } else {
625                                                 // use everything as summation index, don't use scripts.
626                                                 p->cell(1) = ar;
627                                         }
628                                         ++st;
629                                 }
630
631                 // collect upper bound
632                 if (st != ar.end())
633                         if (MathScriptInset * sup = (*st)->asScriptInset())
634                                 if (sup->hasUp()) {
635                                         p->cell(3) = sup->up().data();
636                                         ++st;
637                                 }
638
639                 // use some  behind the script as core
640                 MathArray::iterator tt = extractArgument(p->cell(0), st, ar.end());
641
642                 // cleanup
643                 ar.erase(it + 1, tt);
644                 (*it).reset(p);
645         }
646         //lyxerr << "\nSums to: " << ar << "\n";
647 }
648
649
650 //
651 // search differential stuff
652 //
653
654 // tests for 'd' or '\partial'
655 bool testDiffItem(MathAtom const & at)
656 {
657         return testString(at.nucleus(), "d");
658 }
659
660
661 bool testDiffArray(MathArray const & ar)
662 {
663         return ar.size() && testDiffItem(ar.front());
664 }
665
666
667 bool testDiffFrac(MathInset * p)
668 {
669         MathFracInset * f = p->asFracInset();
670         return f && testDiffArray(f->cell(0)) && testDiffArray(f->cell(1));
671 }
672
673
674 // is this something like ^number?
675 bool extractDiffExponent(MathArray::iterator it, int & i)
676 {
677         if (!(*it)->asScriptInset())
678                 return false;
679
680         string s;
681         if (!extractString((*it).nucleus(), s))
682                 return false;
683         istringstream is(s.c_str());
684         is >> i;
685         return is;
686 }
687
688
689 void extractDiff(MathArray & ar)
690 {
691         //lyxerr << "\nDiffs from: " << ar << "\n";
692         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
693                 MathArray::iterator it = ar.begin() + i;
694
695                 // is this a "differential fraction"?
696                 if (!testDiffFrac(it->nucleus()))
697                         continue;
698
699                 MathFracInset * f = (*it)->asFracInset();
700                 if (!f) {
701                         lyxerr << "should not happen\n";
702                         continue;
703                 }
704
705                 // create a proper diff inset
706                 MathDiffInset * diff = new MathDiffInset;
707
708                 // collect function, let jt point behind last used item
709                 MathArray::iterator jt = it + 1;
710                 //int n = 1;
711                 MathArray & numer = f->cell(0);
712                 if (numer.size() > 1 && numer[1]->asScriptInset()) {
713                         // this is something like  d^n f(x) / d... or  d^n / d...
714                         // FIXME
715                         //n = 1;
716                         if (numer.size() > 2)
717                                 diff->cell(0) = MathArray(numer.begin() + 2, numer.end());
718                         else
719                                 jt = extractArgument(diff->cell(0), jt, ar.end());
720                 } else {
721                         // simply d f(x) / d... or  d/d...
722                         if (numer.size() > 1)
723                                 diff->cell(0) = MathArray(numer.begin() + 1, numer.end());
724                         else
725                                 jt = extractArgument(diff->cell(0), jt, ar.end());
726                 }
727
728                 // collect denominator parts
729                 MathArray & denom = f->cell(1);
730                 for (MathArray::iterator dt = denom.begin(); dt != denom.end();) {
731                         // find the next 'd'
732                         MathArray::iterator et = find_if(dt + 1, denom.end(), &testDiffItem);
733
734                         // point before this
735                         MathArray::iterator st = et - 1;
736                         MathScriptInset * script = (*st)->asScriptInset();
737                         if (script && script->hasUp()) {
738                                 // things like   d.../dx^n
739                                 int mult = 1;
740                                 if (extractNumber(script->up().data(), mult)) {
741                                         //lyxerr << "mult: " << mult << endl;
742                                         for (int i = 0; i < mult; ++i)
743                                                 diff->addDer(MathArray(dt + 1, st));
744                                 }
745                         } else {
746                                 // just  d.../dx
747                                 diff->addDer(MathArray(dt + 1, et));
748                         }
749                         dt = et;
750                 }
751
752                 // cleanup
753                 ar.erase(it + 1, jt);
754                 (*it).reset(diff);
755         }
756         //lyxerr << "\nDiffs to: " << ar << "\n";
757 }
758
759
760 //
761 // search limits
762 //
763
764
765 bool testRightArrow(MathAtom const & at)
766 {
767         return
768                 testSymbol(at.nucleus(), "to") ||
769                 testSymbol(at.nucleus(), "rightarrow");
770 }
771
772
773
774 // replace '\lim_{x->x0} f(x)' sequences by a real MathLimInset
775 // assume 'extractDelims' ran before
776 void extractLims(MathArray & ar)
777 {
778         // we need at least three items...
779         if (ar.size() < 3)
780                 return;
781
782         //lyxerr << "\nLimits from: " << ar << "\n";
783         for (MathArray::size_type i = 0; i + 2 < ar.size(); ++i) {
784                 MathArray::iterator it = ar.begin() + i;
785
786                 // is this a limit function?
787                 if (!testSymbol(it->nucleus(), "lim")) 
788                         continue;
789
790                 // the next one must be a subscript (without superscript)
791                 MathScriptInset * sub = (*(it + 1))->asScriptInset();
792                 if (!sub || !sub->hasDown() || sub->hasUp())
793                         continue;
794
795                 // and it must contain a -> symbol
796                 MathArray & s = sub->down().data();
797                 MathArray::iterator st = find_if(s.begin(), s.end(), &testRightArrow);
798                 if (st == s.end())
799                         continue;
800
801                 // the -> splits the subscript int x and x0
802                 MathArray x  = MathArray(s, 0, st - s.begin());
803                 MathArray x0 = MathArray(s, st - s.begin() + 1, s.size());
804                 
805                 // use something behind the script as core
806                 MathArray f;
807                 MathArray::iterator tt = extractArgument(f, it + 2, ar.end());
808
809                 // create a proper inset as replacement
810                 MathLimInset * p = new MathLimInset(f, x, x0);
811
812                 // cleanup
813                 ar.erase(it + 1, tt);
814                 (*it).reset(p);
815         }
816         //lyxerr << "\nLimits to: " << ar << "\n";
817 }
818
819
820 //
821 // combine searches
822 //
823
824 void extractStructure(MathArray & ar)
825 {
826         splitScripts(ar);
827         extractNumbers(ar);
828         extractMatrices(ar);
829         extractDelims(ar);
830         extractFunctions(ar);
831         extractDets(ar);
832         extractIntegrals(ar);
833         extractSums(ar);
834         extractDiff(ar);
835         extractExps(ar);
836         extractLims(ar);
837         extractStrings(ar);
838 }
839
840
841 void write(MathArray const & dat, WriteStream & wi)
842 {
843         MathArray ar = dat;
844         extractStrings(ar);
845         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
846                 wi.firstitem() = (it == ar.begin());
847                 MathInset const * p = it->nucleus();
848                 if (it + 1 != ar.end()) {
849                         if (MathScriptInset const * q = asScript(it)) {
850                                 q->write2(p, wi);
851                                 ++it;
852                                 continue;
853                         }
854                 }
855                 p->write(wi);
856         }
857 }
858
859
860 void normalize(MathArray const & ar, NormalStream & os)
861 {
862         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it)
863                 (*it)->normalize(os);
864 }
865
866
867 void octavize(MathArray const & dat, OctaveStream & os)
868 {
869         MathArray ar = dat;
870         extractStructure(ar);
871         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
872                 MathInset const * p = it->nucleus();
873                 if (it + 1 != ar.end()) {
874                         if (MathScriptInset const * q = asScript(it)) {
875                                 q->octavize2(p, os);
876                                 ++it;
877                                 continue;
878                         }
879                 }
880                 p->octavize(os);
881         }
882 }
883
884
885 void maplize(MathArray const & dat, MapleStream & os)
886 {
887         MathArray ar = dat;
888         extractStructure(ar);
889         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
890                 MathInset const * p = it->nucleus();
891                 if (it + 1 != ar.end()) {
892                         if (MathScriptInset const * q = asScript(it)) {
893                                 q->maplize2(p, os);
894                                 ++it;
895                                 continue;
896                         }
897                 }
898                 p->maplize(os);
899         }
900 }
901
902
903 void mathematicize(MathArray const & dat, MathematicaStream & os)
904 {
905         MathArray ar = dat;
906         extractStructure(ar);
907         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
908                 MathInset const * p = it->nucleus();
909                 if (it + 1 != ar.end()) {
910                         if (MathScriptInset const * q = asScript(it)) {
911                                 q->mathematicize2(p, os);
912                                 ++it;
913                                 continue;
914                         }
915                 }
916                 p->mathematicize(os);
917         }
918 }
919
920
921 void mathmlize(MathArray const & dat, MathMLStream & os)
922 {
923         MathArray ar = dat;
924         extractStructure(ar);
925         if (ar.size() == 0)
926                 os << "<mrow/>";
927         else if (ar.size() == 1)
928                 os << ar.begin()->nucleus();
929         else {
930                 os << MTag("mrow");
931                 for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
932                         MathInset const * p = it->nucleus();
933                         if (it + 1 != ar.end()) {
934                                 if (MathScriptInset const * q = asScript(it)) {
935                                         q->mathmlize2(p, os);
936                                         ++it;
937                                         continue;
938                                 }
939                         }
940                         p->mathmlize(os);
941                 }
942                 os << ETag("mrow");
943         }
944 }
945
946
947
948
949 namespace {
950
951         string captureOutput(string const & cmd, string const & data)
952         {
953                 string outfile = lyx::tempName(string(), "mathextern");
954                 string full =  "echo '" + data + "' | (" + cmd + ") > " + outfile;
955                 lyxerr << "calling: " << full << endl;
956                 Systemcall dummy;
957                 dummy.startscript(Systemcall::Wait, full);
958                 string out = GetFileContents(outfile);
959                 lyx::unlink(outfile);
960                 lyxerr << "result: '" << out << "'" << endl;
961                 return out;
962         }
963
964
965         MathArray pipeThroughMaple(string const & extra, MathArray const & ar)
966         {
967                 string header = "readlib(latex):\n";
968
969                 // remove the \\it for variable names
970                 //"#`latex/csname_font` := `\\it `:"
971                 header +=
972                         "`latex/csname_font` := ``:\n";
973
974                 // export matrices in (...) instead of [...]
975                 header +=
976                         "`latex/latex/matrix` := "
977                                 "subs(`[`=`(`, `]`=`)`,"
978                                         "eval(`latex/latex/matrix`)):\n";
979
980                 // replace \\cdots with proper '*'
981                 header +=
982                         "`latex/latex/*` := "
983                                 "subs(`\\,`=`\\cdot `,"
984                                         "eval(`latex/latex/*`)):\n";
985
986                 // remove spurious \\noalign{\\medskip} in matrix output
987                 header +=
988                         "`latex/latex/matrix`:= "
989                                 "subs(`\\\\\\\\\\\\noalign{\\\\medskip}` = `\\\\\\\\`,"
990                                         "eval(`latex/latex/matrix`)):\n";
991
992                 //"#`latex/latex/symbol` "
993                 //      " := subs((\\'_\\' = \\'`\\_`\\',eval(`latex/latex/symbol`)): ";
994
995                 string trailer = "quit;";
996                 ostringstream os;
997                 MapleStream ms(os);
998                 ms << ar;
999                 string expr = os.str().c_str();
1000                 lyxerr << "ar: '" << ar << "'\n";
1001                 lyxerr << "ms: '" << os.str() << "'\n";
1002
1003                 for (int i = 0; i < 100; ++i) { // at most 100 attempts
1004                         // try to fix missing '*' the hard way by using mint
1005                         //
1006                         // ... > echo "1A;" | mint -i 1 -S -s -q
1007                         // on line     1: 1A;
1008                         //                 ^ syntax error -
1009                         //                   Probably missing an operator such as * p
1010                         //
1011                         lyxerr << "checking expr: '" << expr << "'\n";
1012                         string out = captureOutput("mint -i 1 -S -s -q -q", expr + ";");
1013                         if (out.empty())
1014                                 break; // expression syntax is ok
1015                         istringstream is(out.c_str());
1016                         string line;
1017                         getline(is, line);
1018                         if (line.find("on line") != 0)
1019                                 break; // error message not identified
1020                         getline(is, line);
1021                         string::size_type pos = line.find('^');
1022                         if (pos == string::npos || pos < 15)
1023                                 break; // caret position not found
1024                         pos -= 15; // skip the "on line ..." part
1025                         if (expr[pos] == '*' || (pos > 0 && expr[pos - 1] == '*'))
1026                                 break; // two '*' in a row are definitely bad
1027                         expr.insert(pos,  "*");
1028                 }
1029
1030                 string full = "latex(" +  extra + '(' + expr + "));";
1031                 string out = captureOutput("maple -q", header + full + trailer);
1032
1033                 // change \_ into _
1034
1035                 //
1036                 MathArray res;
1037                 mathed_parse_cell(res, out);
1038                 return res;
1039         }
1040
1041
1042         MathArray pipeThroughOctave(string const &, MathArray const & ar)
1043         {
1044                 ostringstream os;
1045                 OctaveStream vs(os);
1046                 vs << ar;
1047                 string expr = os.str().c_str();
1048                 string out;
1049
1050                 lyxerr << "pipe: ar: '" << ar << "'\n";
1051                 lyxerr << "pipe: expr: '" << expr << "'\n";
1052
1053                 for (int i = 0; i < 100; ++i) { // at most 100 attempts
1054                         //
1055                         // try to fix missing '*' the hard way
1056                         // parse error:
1057                         // >>> ([[1 2 3 ];[2 3 1 ];[3 1 2 ]])([[1 2 3 ];[2 3 1 ];[3 1 2 ]])
1058                         //                                   ^
1059                         //
1060                         lyxerr << "checking expr: '" << expr << "'\n";
1061                         out = captureOutput("octave -q 2>&1", expr);
1062                         lyxerr << "checking out: '" << out << "'\n";
1063
1064                         // leave loop if expression syntax is probably ok
1065                         if (out.find("parse error:") == string::npos)
1066                                 break;
1067
1068                         // search line with single caret
1069                         istringstream is(out.c_str());
1070                         string line;
1071                         while (is) {
1072                                 getline(is, line);
1073                                 lyxerr << "skipping line: '" << line << "'\n";
1074                                 if (line.find(">>> ") != string::npos)
1075                                         break;
1076                         }
1077
1078                         // found line with error, next line is the one with caret
1079                         getline(is, line);
1080                         string::size_type pos = line.find('^');
1081                         lyxerr << "caret line: '" << line << "'\n";
1082                         lyxerr << "found caret at pos: '" << pos << "'\n";
1083                         if (pos == string::npos || pos < 4)
1084                                 break; // caret position not found
1085                         pos -= 4; // skip the ">>> " part
1086                         if (expr[pos] == '*')
1087                                 break; // two '*' in a row are definitely bad
1088                         expr.insert(pos,  "*");
1089                 }
1090
1091                 if (out.size() < 6)
1092                         return MathArray();
1093
1094                 // remove 'ans = '
1095                 out = out.substr(6);
1096
1097                 // parse output as matrix or single number
1098                 MathAtom at(new MathArrayInset("array", out));
1099                 MathArrayInset const * mat = at.nucleus()->asArrayInset();
1100                 MathArray res;
1101                 if (mat->ncols() == 1 && mat->nrows() == 1)
1102                         res.push_back(mat->cell(0));
1103                 else {
1104                         res.push_back(MathAtom(new MathDelimInset("(", ")")));
1105                         res.back()->cell(0).push_back(at);
1106                 }
1107                 return res;
1108         }
1109
1110 }
1111
1112
1113 MathArray pipeThroughExtern(string const & lang, string const & extra,
1114         MathArray const & ar)
1115 {
1116         if (lang == "octave")
1117                 return pipeThroughOctave(extra, ar);
1118
1119         if (lang == "maple")
1120                 return pipeThroughMaple(extra, ar);
1121
1122         // create normalized expression
1123         ostringstream os;
1124         NormalStream ns(os);
1125         os << "[" << extra << ' ';
1126         ns << ar;
1127         os << "]";
1128         string data = os.str().c_str();
1129
1130         // search external script
1131         string file = LibFileSearch("mathed", "extern_" + lang);
1132         if (file.empty()) {
1133                 lyxerr << "converter to '" << lang << "' not found\n";
1134                 return MathArray();
1135         }
1136
1137         // run external sript
1138         string out = captureOutput(file, data);
1139         MathArray res;
1140         mathed_parse_cell(res, out);
1141         return res;
1142 }