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