]> git.lyx.org Git - lyx.git/blob - src/mathed/math_extern.C
make cursor less eager to leave the formula when pressing 'up' or 'down'
[lyx.git] / src / mathed / math_extern.C
1
2 // This file contains most of the magic that extracts "context
3 // information" from the unstructered layout-oriented stuff in an
4 // MathArray.
5
6 #include <algorithm>
7
8 #include "math_charinset.h"
9 #include "math_deliminset.h"
10 #include "math_diffinset.h"
11 #include "math_exfuncinset.h"
12 #include "math_exintinset.h"
13 #include "math_funcinset.h"
14 #include "math_fracinset.h"
15 #include "math_matrixinset.h"
16 #include "math_mathmlstream.h"
17 #include "math_scriptinset.h"
18 #include "math_stringinset.h"
19 #include "math_symbolinset.h"
20 #include "debug.h"
21
22
23 std::ostream & operator<<(std::ostream & os, MathArray const & ar)
24 {
25         NormalStream ns(os);    
26         ns << ar;
27         return os;
28 }
29
30
31 // define a function for tests
32 typedef bool TestItemFunc(MathInset *);
33
34 // define a function for replacing subexpressions
35 typedef MathInset * ReplaceArgumentFunc(const MathArray & ar);
36
37
38
39 // try to extract an "argument" to some function.
40 // returns position behind the argument
41 MathArray::iterator extractArgument(MathArray & ar,
42         MathArray::iterator pos, MathArray::iterator last, string const & = "")
43 {
44         // nothing to get here
45         if (pos == last)
46                 return pos;
47
48         // something deliminited _is_ an argument
49         if ((*pos)->asDelimInset()) {
50                 ar.push_back(*pos);
51                 return pos + 1;
52         }
53
54         // always take the first thing, no matter what it is
55         ar.push_back(*pos);
56
57         // go ahead if possible
58         ++pos;
59         if (pos == last)
60                 return pos;
61
62         // if the next item is a subscript, it most certainly belongs to the
63         // thing we have
64         if ((*pos)->asScriptInset()) {
65                 ar.push_back(*pos);
66                 // go ahead if possible
67                 ++pos;
68                 if (pos == last)
69                         return pos;
70         }
71
72         // but it might be more than that.
73         // FIXME: not implemented
74         //for (MathArray::iterator it = pos + 1; it != last; ++it) {
75         //      // always take the first thing, no matter
76         //      if (it == pos) {
77         //              ar.push_back(*it);
78         //              continue;
79         //      }
80         //}
81         return pos;
82 }
83
84
85 MathScriptInset const * asScript(MathArray::const_iterator it)
86 {
87         if (it->nucleus()->asScriptInset())
88                 return 0;
89         ++it;
90         if (!it->nucleus())
91                 return 0;
92         return it->nucleus()->asScriptInset();
93 }
94
95
96
97 // returns sequence of char with same code starting at it up to end
98 // it might be less, though...
99 string charSequence(MathArray::const_iterator it, MathArray::const_iterator end)
100 {
101         string s;
102         MathCharInset const * p = it->nucleus()->asCharInset();
103         if (p) {
104                 for (MathTextCodes c = p->code(); it != end; ++it) {
105                         p = it->nucleus()->asCharInset();
106                         if (!p || p->code() != c)
107                                 break;
108                         s += p->getChar();
109                 }
110         }
111         return s;
112 }
113
114
115 void extractStrings(MathArray & dat)
116 {
117         //lyxerr << "\nStrings from: " << ar << "\n";
118         MathArray ar;
119         MathArray::const_iterator it = dat.begin();
120         while (it != dat.end()) {
121                 if (it->nucleus() && it->nucleus()->asCharInset()) {
122                         string s = charSequence(it, dat.end());
123                         MathTextCodes c = it->nucleus()->asCharInset()->code();
124                         ar.push_back(MathAtom(new MathStringInset(s, c)));
125                         it += s.size();
126                 } else {
127                         ar.push_back(*it);
128                         ++it;
129                 }
130         }
131         ar.swap(dat);
132         //lyxerr << "\nStrings to: " << ar << "\n";
133 }
134
135
136 MathInset * singleItem(MathArray & ar)
137 {
138         return ar.size() == 1 ? ar.begin()->nucleus() : 0;
139 }
140
141
142 void extractMatrices(MathArray & ar)
143 {
144         lyxerr << "\nMatrices from: " << ar << "\n";
145         for (MathArray::iterator it = ar.begin(); it != ar.end(); ++it) {
146                 MathDelimInset * del = (*it)->asDelimInset();
147                 if (!del)
148                         continue;
149                 MathInset * arr = singleItem(del->cell(0));
150                 if (!arr || !arr->asArrayInset())
151                         continue;
152                 *it = MathAtom(new MathMatrixInset(*(arr->asArrayInset())));
153         }
154         lyxerr << "\nMatrices to: " << ar << "\n";
155 }
156
157
158 // convert this inset somehow to a string
159 string extractString(MathInset * p)
160 {
161         if (p && p->getChar())
162                 return string(1, p->getChar());
163         if (p && p->asStringInset())
164                 return p->asStringInset()->str();
165         return string();
166 }
167
168
169 bool stringTest(MathInset * p, const string & str)
170 {
171         return extractString(p) == str;
172 }
173
174
175 // search end of nested sequence
176 MathArray::iterator endNestSearch(
177         MathArray::iterator it,
178         MathArray::iterator last,
179         TestItemFunc testOpen,
180         TestItemFunc testClose
181 )
182 {
183         for (int level = 0; it != last; ++it) {
184                 if (testOpen(it->nucleus()))
185                         ++level;
186                 if (testClose(it->nucleus()))
187                         --level;
188                 if (level == 0)
189                         break;
190         }
191         return it;
192 }
193
194
195 // replace nested sequences by a real Insets
196 void replaceNested(
197         MathArray & ar,
198         TestItemFunc testOpen,
199         TestItemFunc testClose,
200         ReplaceArgumentFunc replaceArg
201 )
202 {
203         // use indices rather than iterators for the loop  because we are going
204         // to modify the array.
205         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
206                 // check whether this is the begin of the sequence
207                 MathArray::iterator it = ar.begin() + i;
208                 if (!testOpen(it->nucleus()))
209                         continue;
210
211                 // search end of sequence
212                 MathArray::iterator jt = endNestSearch(it, ar.end(), testOpen, testClose);
213                 if (jt == ar.end())
214                         continue;
215
216                 // create a proper inset as replacement
217                 MathInset * p = replaceArg(MathArray(it + 1, jt));
218
219                 // replace the original stuff by the new inset
220                 ar.erase(it + 1, jt + 1);
221                 (*it).reset(p);
222         }
223
224
225
226 //
227 // search deliminiters
228 //
229
230 bool openParanTest(MathInset * p)
231 {
232         return stringTest(p, "(");
233 }
234
235
236 bool closeParanTest(MathInset * p)
237 {
238         return stringTest(p, ")");
239 }
240
241
242 MathInset * delimReplacement(const MathArray & ar)
243 {
244         MathDelimInset * del = new MathDelimInset("(", ")");
245         del->cell(0) = ar;
246         return del;
247 }
248
249
250 // replace '('...')' sequences by a real MathDelimInset
251 void extractDelims(MathArray & ar)
252 {
253         lyxerr << "\nDelims from: " << ar << "\n";
254         replaceNested(ar, openParanTest, closeParanTest, delimReplacement);
255         lyxerr << "\nDelims to: " << ar << "\n";
256 }
257
258
259
260 //
261 // search well-known functions
262 //
263
264
265 // replace 'f' '(...)' and 'f' '^n' '(...)' sequences by a real MathExFuncInset
266 // assume 'extractDelims' ran before
267 void extractFunctions(MathArray & ar)
268 {
269         // we need at least two items...
270         if (ar.size() <= 1)
271                 return;
272
273         lyxerr << "\nFunctions from: " << ar << "\n";
274         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
275                 MathArray::iterator it = ar.begin() + i;
276
277                 // is this a well known function name?
278                 MathFuncInset * func = (*it)->asFuncInset();
279                 string name;
280                 if (func) 
281                         name = func->name();
282                 else {
283                         // is this a user defined function?
284                         // guess so, if this is a "string" and it is followed by
285                         // a DelimInset
286                         //name = extractString((*it)->nucleus());
287                         //if (name.size() && it + 1
288                         //if ((*it
289                         // FIXME
290                         continue;
291                 }       
292
293                 // do we have an exponent?
294                 // simply skippping the postion does the right thing:
295                 // 'sin' '^2' 'x' -> 'sin(x)' '^2'
296                 MathArray::iterator jt = it + 1;
297                 if (MathScriptInset * script = (*jt)->asScriptInset()) {
298                         // allow superscripts only
299                         if (script->hasDown())
300                                 continue;
301                         ++jt;
302                         if (jt == ar.end())
303                                 continue;
304                 }
305         
306                 // create a proper inset as replacement
307                 MathExFuncInset * p = new MathExFuncInset(name);
308
309                 // jt points to the "argument". Get hold of this.
310                 MathArray::iterator st = extractArgument(p->cell(0), jt, ar.end());
311
312                 // replace the function name by a real function inset
313                 (*it).reset(p);
314                 
315                 // remove the source of the argument from the array
316                 ar.erase(jt, st);
317                 lyxerr << "\nFunctions to: " << ar << "\n";
318         }
319
320
321
322 //
323 // search integrals
324 //
325
326 bool symbolTest(MathInset * p, string const & name)
327 {
328         return p->asSymbolInset() && p->asSymbolInset()->name() == name;
329 }
330
331
332 bool intSymbolTest(MathInset * p)
333 {
334         return symbolTest(p, "int");
335 }
336
337
338 bool intDiffTest(MathInset * p)
339 {
340         return stringTest(p, "d");
341 }
342
343
344 // replace '\int' ['_^'] x 'd''x'(...)' sequences by a real MathExIntInset
345 // assume 'extractDelims' ran before
346 void extractIntegrals(MathArray & ar)
347 {
348         // we need at least three items...
349         if (ar.size() <= 2)
350                 return;
351
352         lyxerr << "\nIntegrals from: " << ar << "\n";
353         for (MathArray::size_type i = 0; i + 1< ar.size(); ++i) {
354                 MathArray::iterator it = ar.begin() + i;
355
356                 // is this a integral name?
357                 if (!intSymbolTest(it->nucleus()))
358                         continue;
359
360                 // search 'd'
361                 MathArray::iterator jt =
362                         endNestSearch(it, ar.end(), intSymbolTest, intDiffTest);
363
364                 // something sensible found?
365                 if (jt == ar.end())
366                         continue;
367
368                 // create a proper inset as replacement
369                 MathExIntInset * p = new MathExIntInset("int");
370
371                 // collect scripts
372                 MathArray::iterator st = it + 1;
373                 if ((*st)->asScriptInset()) {
374                         p->scripts(*st);
375                         p->cell(0) = MathArray(st + 1, jt);
376                 } else {
377                         p->cell(0) = MathArray(st, jt);
378                 }
379
380                 // use the atom behind the 'd' as differential
381                 MathArray::iterator tt = extractArgument(p->cell(1), jt + 1, ar.end());
382                 
383                 // remove used parts
384                 ar.erase(it + 1, tt);
385                 (*it).reset(p);
386         }
387         lyxerr << "\nIntegrals to: " << ar << "\n";
388 }
389
390
391 //
392 // search sums
393 //
394
395 bool sumSymbolTest(MathInset * p)
396 {
397         return p->asSymbolInset() && p->asSymbolInset()->name() == "sum";
398 }
399
400
401 bool equalSign(MathInset * p)
402 {
403         return stringTest(p, "=");
404 }
405
406
407 bool equalSign1(MathAtom const & at)
408 {
409         return equalSign(at.nucleus());
410 }
411
412
413
414 // replace '\sum' ['_^'] f(x) sequences by a real MathExIntInset
415 // assume 'extractDelims' ran before
416 void extractSums(MathArray & ar)
417 {
418         // we need at least two items...
419         if (ar.size() <= 1)
420                 return;
421
422         lyxerr << "\nSums from: " << ar << "\n";
423         for (MathArray::size_type i = 0; i + 1< ar.size(); ++i) {
424                 MathArray::iterator it = ar.begin() + i;
425
426                 // is this a sum name?
427                 if (!sumSymbolTest(it->nucleus()))
428                         continue;
429
430                 // create a proper inset as replacement
431                 MathExIntInset * p = new MathExIntInset("sum");
432
433                 // collect scripts
434                 MathArray::iterator st = it + 1;
435                 if (st != ar.end() && (*st)->asScriptInset()) {
436                         p->scripts(*st);
437                         ++st;
438
439                         // try to figure out the summation index from the subscript
440                         MathScriptInset * script = p->scripts()->asScriptInset();
441                         if (script->hasDown()) {
442                                 MathArray & ar = script->down().data_;
443                                 MathArray::iterator it =
444                                         std::find_if(ar.begin(), ar.end(), &equalSign1);
445                                 if (it != ar.end()) {
446                                         // we found a '=', use everything in front of that as index,
447                                         // and everything behind as start value
448                                         p->cell(1) = MathArray(ar.begin(), it);
449                                         ar.erase(ar.begin(), it + 1);
450                                 } else {
451                                         // use everything as summation index, don't use scripts.
452                                         p->cell(1) = ar;
453                                 }
454                         }
455                 }
456
457                 // use some  behind the script as core
458                 MathArray::iterator tt = extractArgument(p->cell(0), st, ar.end());
459
460                 // cleanup
461                 ar.erase(it + 1, tt);
462                 (*it).reset(p);
463         }
464         lyxerr << "\nSums to: " << ar << "\n";
465 }
466
467
468 //
469 // search differential stuff
470 //
471
472 // tests for 'd' or '\partial'
473 bool diffItemTest(MathInset * p)
474 {
475         return stringTest(p, "d");
476 }
477
478
479 bool diffItemTest(MathArray const & ar)
480 {
481         return ar.size() && diffItemTest(ar.front().nucleus());
482 }
483
484
485 bool diffFracTest(MathInset * p)
486 {
487         return
488                 p->asFracInset() &&
489                 diffItemTest(p->asFracInset()->cell(0)) &&
490                 diffItemTest(p->asFracInset()->cell(1));
491 }
492
493 void extractDiff(MathArray & ar)
494 {
495         lyxerr << "\nDiffs from: " << ar << "\n";
496         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
497                 MathArray::iterator it = ar.begin() + i;
498
499                 // is this a "differential fraction"?
500                 if (!diffFracTest(it->nucleus()))
501                         continue;
502                 
503                 MathFracInset * f = (*it)->asFracInset();
504                 if (!f) {
505                         lyxerr << "should not happen\n";
506                         continue;
507                 }
508
509                 // create a proper diff inset
510                 MathDiffInset * p = new MathDiffInset;
511
512                 // collect function, let jt point behind last used item
513                 MathArray::iterator jt = it + 1; 
514                 int n = 1; 
515                 MathArray & numer = f->cell(0);
516                 if (numer.size() > 1 && numer.at(1)->asScriptInset()) {
517                         // this is something like  d^n f(x) / d... or  d^n / d...
518                         n = 1; // FIXME
519                         if (numer.size() > 2) 
520                                 p->cell(0) = MathArray(numer.begin() + 2, numer.end());
521                         else
522                                 jt = extractArgument(p->cell(0), jt, ar.end());
523                 } else {
524                         // simply d f(x) / d... or  d/d...
525                         if (numer.size() > 1) 
526                                 p->cell(0) = MathArray(numer.begin() + 1, numer.end());
527                         else
528                                 jt = extractArgument(p->cell(0), jt, ar.end());
529                 }
530
531                 // collect denominator
532                 MathArray & denom = f->cell(1);
533                 for (MathArray::iterator dt = denom.begin(); dt + 1 != denom.end(); ) {
534                         if (!diffItemTest((*dt).nucleus())) {
535                                 lyxerr << "extractDiff: should not happen 2\n";
536                                 return;
537                         }
538                         MathArray diff;
539                         dt = extractArgument(diff, dt + 1, denom.end());
540                         p->addDer(diff);
541                         // safeguard
542                         if (dt == denom.end()) 
543                                 break;
544                 }
545
546                 // cleanup
547                 ar.erase(it + 1, jt);
548                 (*it).reset(p);
549         }
550         lyxerr << "\nDiffs to: " << ar << "\n";
551 }
552
553 //
554 // combine searches
555 //
556
557 void extractStructure(MathArray & ar)
558 {
559         extractMatrices(ar);
560         extractDelims(ar);
561         extractFunctions(ar);
562         extractIntegrals(ar);
563         extractSums(ar);
564         extractDiff(ar);
565         extractStrings(ar);
566 }
567
568
569 void write(MathArray const & dat, WriteStream & wi)
570 {
571         MathArray ar = dat;
572         extractStrings(ar);
573         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
574                 wi.firstitem = (it == ar.begin());
575                 MathInset const * p = it->nucleus();
576                 if (it + 1 != ar.end()) {
577                         if (MathScriptInset const * q = asScript(it)) {
578                                 q->write(p, wi);
579                                 ++it;
580                                 continue;
581                         } 
582                 }
583                 p->write(wi);
584         }
585 }
586
587
588 void normalize(MathArray const & ar, NormalStream & os)
589 {
590         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it)
591                 (*it)->normalize(os);
592 }
593
594
595 void octavize(MathArray const & dat, OctaveStream & os)
596 {
597         MathArray ar = dat;
598         extractStructure(ar);
599         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
600                 MathInset const * p = it->nucleus();
601                 if (it + 1 != ar.end()) {
602                         if (MathScriptInset const * q = asScript(it)) {
603                                 q->octavize(p, os);
604                                 ++it;   
605                                 continue;
606                         }
607                 }
608                 p->octavize(os);
609         }
610 }
611
612
613 void maplize(MathArray const & dat, MapleStream & os)
614 {
615         MathArray ar = dat;
616         extractStructure(ar);
617         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
618                 MathInset const * p = it->nucleus();
619                 if (it + 1 != ar.end()) {
620                         if (MathScriptInset const * q = asScript(it)) {
621                                 q->maplize(p, os);
622                                 ++it;   
623                                 continue;
624                         }
625                 }
626                 p->maplize(os);
627         }
628 }
629
630
631 void mathmlize(MathArray const & dat, MathMLStream & os)
632 {
633         MathArray ar = dat;
634         extractStructure(ar);
635         if (ar.size() == 0)
636                 os << "<mrow/>";
637         else if (ar.size() == 1)
638                 os << ar.begin()->nucleus();
639         else {
640                 os << MTag("mrow");
641                 for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
642                         MathInset const * p = it->nucleus();
643                         if (it + 1 != ar.end()) {
644                                 if (MathScriptInset const * q = asScript(it)) {
645                                         q->mathmlize(p, os);
646                                         ++it;   
647                                         continue;
648                                 }
649                         }
650                         p->mathmlize(os);
651                 }
652                 os << ETag("mrow");
653         }
654 }
655