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