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