* Bugs 16071+16072+16075 fixed: prettyprint() debugged & a bit improved
[scilab.git] / scilab / modules / string / macros / prettyprint.sci
1 // Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
2 // Copyright (C) 2009-2010 - Calixte Denizet
3 // Copyright (C) 2012 - 2016 - Scilab Enterprises
4 // Copyright (C) 2019 - Samuel GOUGEON
5 //
6 // This file is hereby licensed under the terms of the GNU GPL v2.0,
7 // pursuant to article 5.3.4 of the CeCILL v.2.1.
8 // This file was originally licensed under the terms of the CeCILL v2.1,
9 // and continues to be available under such terms.
10 // For more information, see the COPYING file which you should have received
11 // along with this program.
12
13 function str = prettyprint(a, exportFormat, delimiter, processByElement, isWrapped)
14     // From any Scilab datatype and provide a representation to the TeX, LaTeX or MathML formats
15     //
16     // Syntax
17     // str = prettyprint(a) // Show the variable a with the default format (LaTeX)
18     // str = prettyprint(a,exportFormat) // Show the variable a with the specified format
19     // str = prettyprint(a,exportFormat, delim) // As above but change the delimiter
20     // str = prettyprint(a,exportFormat, delim, processByElement) // As above but process each element independently
21     // str = prettyprint(a,exportFormat, delim, processByElement, isWrapped) // As above Add the special keyword of Tex or LaTeX export
22     //
23     // Parameters
24     // a: is a Scilab variable
25     //exportFormat: is the format, if omitted 'latex' is used by default, it can be 'latex', 'tex' or 'mathml'.
26     // delimiter: is a string indicating the delimiter to use for the resulting matrix, it's only used if isWrapped is true. The delimiter can be '(', '{', '[', '|', '||' or ')'
27     //processByElement: is a boolean to indicate if the resulting matrix must be converted into a single string.
28     //isWrapped: is a boolean to indicate if the result must be wrapped inside delimiters ('$' for latex and tex or nothing for mathml) to be used with xstring or xtitle
29     //str: the representation of the variable a
30     //If the type of a is not handled, then an error is returned. If a user wants to handle the type foo when exporting with latex, he must define the function foo2latex.
31     //
32     // Description
33     // Taking a variable, the prettyprint function will provide a formatted representation of it.
34     // Formats can be TeX, LaTeX or MathML.
35     // They can be used in third party applications but also within Scilab with the most of the
36     // <link linkend="math_rendering_features_in_graphic">Scilab graphic features</link>.
37     // The following types are handled by this function:
38     //  <itemizedlist>
39     //    <listitem><para>Real / Complex matrices</para></listitem>
40     //    <listitem><para>Polynomial types</para></listitem>
41     //    <listitem><para>Boolean</para></listitem>
42     //    <listitem><para>Integer</para></listitem>
43     //    <listitem><para>String</para></listitem>
44     //    <listitem><para>Tlist</para></listitem>
45     //    <listitem><para>Rationnal</para></listitem>
46     //    <listitem><para>Cell</para></listitem>
47     //  </itemizedlist>
48     //
49     // Examples
50     // str = prettyprint(rand(3,3)) // Return the LaTeX representation of a 3,3 matrix
51     // xstring(0.2,0.2,str) // Show the representation in a graphic Windows
52     //
53     // prettyprint(rand(3,4),"mathml") // Return the MathML representation of a 3,4 matrix
54     // prettyprint(rand(3,4),"mathml","[") // Return the MathML representation of a 3,4 matrix with '[' as delimiter
55     //
56     // s=poly(0,'s'); G=[1,s;1+s^2,3*s^3];
57     // xstring(0.2,0.2,prettyprint(G*s-1)); // Show a polynomial through a LaTeX representation
58     //
59     // See also
60     // math_rendering_features_in_graphic
61     // xtitle
62     // axes_properties
63     // label_properties
64     // legend_properties
65     // text_properties
66     // xstringb
67     // xstringl
68     // xstring
69     //
70     // Authors
71     // Calixte Denizet
72     // Samuel Gougeon
73
74     if argn(2)<1 | argn(2)>5
75         error(msprintf(gettext("%s: Wrong number of input argument(s): %d to %d expected."),"prettyprint",1,4));
76     end
77
78     if ~isdef("exportFormat","l")    , exportFormat = "latex", end
79     if ~isdef("delimiter","l")       , delimiter = "("       , end
80     if ~isdef("processByElement","l"), processByElement = %F,  end
81     if ~isdef("isWrapped","l")       , isWrapped = %T,         end
82
83     if type(exportFormat) <> 10 then
84         error(msprintf(gettext("%s: Wrong type for input argument #%d: String expected.\n"),"prettyprint",2));
85     end
86     if type(delimiter) <> 10 | and(delimiter <> ["(" "[" "|" "||" "{" ""]) then
87         error(msprintf(gettext("%s: Wrong value for input argument #%d: ''%s'', ''%s'', ''%s'', ''%s'', ''%s'' or ''%s'' expected.\n"),"prettyprint",3,"(","[","|","||","{",""));
88     end
89     if type(processByElement) <> 4 then
90         error(msprintf(gettext("%s: Wrong type for argument #%d: Boolean expected.\n"),"prettyprint",4));
91     end
92     if type(isWrapped) <> 4 then
93         error(msprintf(gettext("%s: Wrong type for argument #%d: Boolean expected.\n"),"prettyprint",5));
94     end
95
96     // ---------------------
97     try
98         execstr("[plus,minus,img,op,cp,ow,cw,d2s]=" + exportFormat + "conf()");
99     catch
100         error(msprintf(gettext("%s: Wrong export format: %s"),"prettyprint",exportFormat));
101     end
102
103     typ = type(a);
104
105
106     select typ
107     case 1 then
108         // number : real or complex
109         if or(isinf(a) | isnan(a)) | norm(imag(a)) > %eps * norm(real(a)) then
110             str = comp2str(a,plus,minus,img,d2s);
111         else
112             str = comp2str(real(a),plus,minus,img,d2s);
113         end
114
115     case 2 then
116         //Polynomial type
117         x = varn(a);
118         C = coeff(a);
119         [m,n] = size(a);
120         maxd = max(0,max(degree(a)));
121         str = emptystr(a);
122         for i = 0:maxd do
123             execstr("expo=" + exportFormat + "exp(x,i)");
124             A = C(1:m,(1 + i * n):((i + 1) * n));
125             if or(isinf(a) | isnan(a)) | norm(imag(A)) > %eps * norm(real(A)) then
126                 str = str + comp2coef(A,expo,plus,minus,img,op,cp,d2s);
127             else
128                 str = str + comp2coef(real(A),expo,plus,minus,img,op,cp,d2s);
129             end
130         end
131         L = length(plus);
132         for i = 1:m do
133             for j = 1:n do
134                 if part(str(i,j),1:L) == plus then
135                     str(i,j) = part(str(i,j),L + 1:length(str(i,j)));
136                 end
137             end
138         end
139         //The null polynomial is represented by '' so we must replace it by '0z'
140         if exportFormat=="mathml"
141             str(str == "") = d2s(0) + "<mi>" + x + "</mi>";
142         else
143             str(str == "") = d2s(0) + x;
144         end
145
146     case 4 then
147         //Boolean type
148         str = "F" + emptystr(a);
149         str(a) = "T";
150         execstr("str=" + exportFormat + "exp(str,1)");
151
152     case 8 then
153         //Int type
154         str = d2s(a);
155
156     case 10 then
157         //String type
158         if or(exportFormat == ["tex" "latex"])
159             a = strsubst(a, "\", "\backslash\!")
160             a = strsubst(a, "$", "\$")
161             a = strsubst(a, "%", "\%")
162             a = strsubst(a, "&", "\&")
163             a = strsubst(a, "{", "\{")
164             a = strsubst(a, "}", "\}")
165             a = strsubst(a, "~", "\sim\!")
166             a = strsubst(a, "^", "\^\;\,")
167             a = strsubst(a, "<", "\!<\!")
168             a = strsubst(a, ">", "\!>\!")
169             // a = strsubst(a, "_", "\_ ")  // not required
170             // a = strsubst(a, "#", "\#")   // not required
171             if ~processByElement | isWrapped
172                 a = "\mathsf{\text{" + a + "}}" // default protection of spaces
173                 // Otherwise: we let the user choose the type of text
174                 // encapsulation
175             end
176         elseif exportFormat == "mathml"
177             a = strsubst(a, "<", "&lt;")
178             // " ' and & do not need protection
179             execstr("a = " + exportFormat + "exp(a,1)");
180         end
181         str = a;
182
183     case 16 then
184         //Tlist type
185         t = a(1);
186         select t(1)
187             //Rationnal type
188         case "r" then
189             num = prettyprint(a("num"), exportFormat, "(", %T, %F);
190             den = prettyprint(a("den"), exportFormat, "(", %T, %F);
191             execstr("str=rational2" + exportFormat + "(num,den)");
192             //Linear state space type
193         case "lss" then
194             execstr("str=lss2" + exportFormat + "(a)");
195         else
196             str = unknown_type(t(1),a,exportFormat);
197             return;
198         end
199
200     case 17  then
201         tof = typeof(a);
202         select tof
203         case "ce" then
204             //Cell type
205             dim = double(size(a));
206             L = length(dim);
207             if L >= 3 then
208                 str = unknown_type("ce",a,exportFormat);
209                 return;
210             end
211             str = emptystr(dim(1),dim(2));
212             for i = 1:dim(1) do
213                 for j = 1:dim(2) do
214                     str(i,j) = prettyprint(a{i,j},exportFormat,delimiter,%F,%F);
215                 end
216             end
217         else
218             str = unknown_type(tof,a,exportFormat);
219             return;
220         end
221     else
222         str = unknown_type(typeof(a),a,exportFormat);
223         return
224     end
225
226     [m,n] = size(a);
227     if m*n <> 1 & ~processByElement then
228         execstr("str=" + exportFormat + "matrix(str,''" + delimiter + "'')");
229     end
230
231     if isWrapped then
232         str = ow + str + cw;
233     end
234 endfunction
235
236 // ----------------------------------------------------------------------------
237
238 // MATHML
239 // ======
240 //This function gives different elements of configuration
241 //plus is '+', minus is '-', img is sqrt(-1), op and cp are opening ang closing
242 //parenthesis (useful to generate complex coeff in polynomials), ow and cw are
243 //opening and closing delimiters used to wrap the expression to be displayed
244 //with xstring or xtitle, d2s is the function used to convert double into mathml.
245 function [plus,minus,img,op,cp,ow,cw,d2s] = mathmlconf()
246     plus = "<mo>+</mo>";
247     minus = "<mo>-</mo>";
248     img = "<mi>i</mi>";
249     op = "<mfenced separator=""""><mrow>";
250     cp = "</mrow></mfenced>";
251     ow = "";
252     cw = "";
253     d2s = mathmldbl2str;
254 endfunction
255
256 //This function generates var^n (useful for polynomials)
257 function str = mathmlexp(var,n)
258     if n >= 2 then
259         str = "<msup><mi>" + var + "</mi><mn>" + string(n) + "</mn></msup> ";
260     elseif n == 1 then
261         str = "<mi>" + var + "</mi>";
262     else
263         str = "";
264     end
265 endfunction
266
267 //This function creates a fraction with the given numerator and denominator
268 function str = rational2mathml(num,den)
269     nl = ascii(10)
270     str = "<mfrac>" + nl + ..
271             "<mrow>" + nl + num + nl + "</mrow>" + nl + ..
272             "<mrow>" + nl + den + nl + "</mrow>" + nl + ..
273           "</mfrac>";
274 endfunction
275
276 //This function converts a double into a mathml string
277 //Negative and positive are handled and the scientific notation too
278 function str = mathmldbl2str(x)
279     if x == %inf then
280         str = "<mi>∞</mi>";
281     elseif x == -%inf then
282         str = "<mo>-</mo><mi>∞</mi>";
283     elseif isnan(x) then
284         str = "<mi>NaN</mi>";
285     else
286         str= strsubst(string(x),"-","<mo>-</mo><mn>");
287         if type(x) <> 8
288             if strindex(str,"D") <> [] then
289                 str = strsubst(str,"+","<mo>+</mo><mn>");
290                 str = strsubst(str,"D","</mn><mo>&#x00D7;</mo><msup><mn>10</mn><mrow>");
291                 if x > 0 then
292                     str = "<mn>" + str;
293                 end
294                 str = str + "</mn></mrow></msup>";
295             elseif x >= 0 then
296                 str = "<mn>" + str + "</mn>";
297             else
298                 str = str + "</mn>";
299             end
300         else
301             k = x>=0
302             if k~=[]
303                 str(k) =  "<mn>" + str(k)
304             end
305             str = str + "</mn>"
306         end
307     end
308 endfunction
309
310 //This function generates a matrix with the given delimiter
311 function str = mathmlmatrix(mat,delimiter)
312     if argn(2) == 1 then
313         delimiter = "(";
314     end
315     nl = ascii(10)
316     select delimiter
317     case "[" then
318         com = "<mfenced open=""["" close=""]"">";
319     case "(" then
320         com = "<mfenced open=""("" close="")"">";
321     case "|" then
322         com = "<mfenced open=""&#x2223;"" close=""&#x2223;"">";
323     case "" then
324         com = "<mfenced open="""" close="""">";
325     case "{" then
326         com = "<mfenced open=""{"" close=""}"">";
327     case "||" then
328         com = "<mfenced open=""&#x2225;"" close=""&#x2225;"">";
329     else
330         com = "<mfenced open=""("" close="")"">";
331     end
332     [m,n] = size(mat);
333     str = nl + com + nl + "<mtable>";
334     for i=1:m do
335         str = str + nl + "<mtr>" + nl + ..
336               "<mtd>" + strcat(mat(i,:),"</mtd>"+nl+"<mtd>") + "</mtd>" + nl + ..
337               "</mtr>";
338     end
339     str = str + nl + "</mtable>" + nl + "</mfenced>" + nl;
340 endfunction
341
342 //This function handles the syslin
343 function str = lss2mathml(sys)
344     nl = ascii(10);
345     ptp = "<mo>(</mo><mi>t</mi><mo>)</mo>";
346     if sys(7) == "c" then
347         der = "<mrow><mover><mi>X</mi><mo>.</mo></mover>" + ptp + "</mrow>";
348     else
349         der = "<mrow><mover><mi>X</mi><mo>+</mo></mover>" + ptp + "</mrow>"
350     end
351     str = "<mfenced close="""" open=""{"">" + nl + ..
352           "<mtable columnalign=""right center left"">" + nl + ..
353           "<mtr>" + ..
354           "<mtd>" + der + "</mtd><mtd><mo>=</mo></mtd>" + ..
355           "<mtd>" + prettyprint(sys(2),"mathml","(",%F,%F) + ..
356           "<mrow><mi>X</mi>" + ptp + "</mrow><mo>+</mo>" + ..
357           prettyprint(sys(3),"mathml","(",%F,%F) + ..
358           "<mrow><mi>U</mi>" + ptp + "</mrow></mtd></mtr>" + nl + ..
359           "<mtr><mtd><mrow><mi>Y</mi>" + ptp + "</mrow></mtd>" + ..
360           "<mtd><mo>=</mo></mtd>" + ..
361           "<mtd>" + prettyprint(sys(4),"mathml","(",%F,%F) + "<mrow><mi>X</mi>" + ptp + "</mrow>";
362     try
363         if norm(sys(5),1) == 0 then
364             str = str + "</mtd></mtr>"
365         else
366             str = str + "<mo>+</mo>" + prettyprint(sys(5),"mathml","(",%F,%F) + ..
367             "<mrow><mi>U</mi>" + ptp + "</mrow></mtd></mtr>" 
368         end
369     catch
370         str = str + "<mo>+</mo>" + prettyprint(sys(5),"mathml","(",%F,%F) + ..
371             "<mrow><mi>U</mi>" + ptp + "</mrow></mtd></mtr>" 
372     end
373     str = str + nl + "</mtable>" + nl + "</mfenced>" + nl;
374 endfunction
375
376 // ----------------------------------------------------------------------------
377
378 // LATEX
379 // =====
380 function [plus,minus,img,op,cp,ow,cw,d2s] = latexconf()
381     plus = "+";
382     minus = "-";
383     img = "i";
384     op = "(";
385     cp = ")";
386     ow = "$";
387     cw = "$";
388     d2s = latexdbl2str;
389 endfunction
390
391 function str = latexexp(var,n)
392     if n >= 2 then
393         str = var + "^{" + string(n) + "}";
394     elseif n == 1 then
395         str = var;
396     else
397         str = "";
398     end
399     if typ==2 then
400         str = str + " " // Makes easier wrapping long polynomials
401     end
402 endfunction
403
404 function str = rational2latex(num,den)
405     str = "{\frac{" + num + "}{" + den + "}}"
406 endfunction
407
408 function str = latexdbl2str(x)
409     if x == %inf then
410         str = "{\infty}";
411     elseif x == -%inf then
412         str = "{-\infty}";
413     elseif isnan(x) then
414         str = "{\mathrm{NaN}}";
415     else
416         str = string(x);
417         if type(x)~=8 & strindex(str,"D") <> [] then
418             str = strsubst(str,"D+","D");
419             str = strsubst(str,"D","\!\times\!10^{");
420             str = str + "}";
421         end
422     end
423 endfunction
424
425 function str = lss2latex(sys)
426     if sys(7) == "c" then
427         der = "\dot{X}(t)";
428     else
429         der = "\stackrel{+}{X}(t)"
430     end
431     str = "{\left\{\begin{array}{rcl}" + der + "&=&" + ..
432             prettyprint(sys(2),"latex","(",%F,%F) + " X(t)+" + ..
433             prettyprint(sys(3),"latex","(",%F,%F) + "U(t)\cr " + ..
434             "Y(t) &=& " + prettyprint(sys(4),"latex","(",%F,%F) + " X(t) ";
435     try
436         if norm(sys(5),1) == 0 then
437             str = str + "\end{array}\right.}";
438         else
439             str = str + " + " + prettyprint(sys(5),"latex","(",%F,%F) + " U(t)\end{array}\right.}";
440         end
441     catch
442         str = str + " + " + prettyprint(sys(5),"latex","(",%F,%F) + " U(t)\end{array}\right.}";
443     end
444 endfunction
445
446 function str = latexmatrix(mat,delimiter)
447     if argn(2) == 1 then
448         delimiter = "(";
449     end
450     select delimiter
451     case "[" then
452         com = "bmatrix";
453     case "(" then
454         com = "pmatrix";
455     case "|" then
456         com = "vmatrix";
457     case "" then
458         com = "matrix";
459     case "{" then
460         com = "Bmatrix";
461     case "||" then
462         com = "Vmatrix";
463     else
464         com = "pmatrix";
465     end
466     [m,n] = size(mat);
467     str = "{\begin{" + com + "}";
468     if m*n == 0 then
469
470         str = str + "\ ";
471     else
472         for i=1:m do
473             str = str + strcat(mat(i,:),"&") + "\cr ";
474         end
475     end
476
477     str = str + "\end{" + com + "}}";
478 endfunction
479
480 // ----------------------------------------------------------------------------
481 // TEX
482 // ===
483 function [plus,minus,img,op,cp,ow,cw,d2s] = texconf()
484     [plus,minus,img,op,cp,ow,cw,d2s] = latexconf()
485 endfunction
486
487 function str = texexp(var,n)
488     str = latexexp(var,n);
489 endfunction
490
491 function str = rational2tex(num,den)
492     str = "{{" + num + "}\over{" + den + "}}"
493 endfunction
494
495 function str = texdbl2str(x)
496     if x == %inf then
497         str = "{\infty}";
498     elseif x == -%inf then
499         str = "{-\infty}";
500     elseif isnan(x) then
501         str = "{\rm{NaN}}";
502     else
503         str = string(x);
504         if strindex(str,"D") <> [] then
505             str = strsubst(str,"D+","D");
506             str = strsubst(str,"D","\!\times\!10^{");
507             str = str + "}";
508         end
509     end
510 endfunction
511
512 function str = lss2tex(sys)
513     if sys(7) == "c" then
514         der = "\dot{X}(t)";
515     else
516         der = "{\buildrel + \over X(t)}"
517     end
518     str = "{\left\{\eqalign{" + der + " &= " + ..
519           prettyprint(sys(2),"tex","(",%F,%F) + " X(t)+" + ..
520           prettyprint(sys(3),"tex","(",%F,%F) + "U(t)\cr " + ..
521           "Y(t) &= " + prettyprint(sys(4),"tex","(",%F,%F) + " X(t) ";
522     try
523         if norm(sys(5),1) == 0 then
524             str = str + "}\right.}";
525         else
526             str = str + " + " + prettyprint(sys(5),"tex","(",%F,%F) + " U(t)}\right.}";
527         end
528     catch
529         str = str + " + " + prettyprint(sys(5),"tex","(",%F,%F) + " U(t)}\right.}";
530     end
531 endfunction
532
533 function str = texmatrix(mat,delimiter)
534     if argn(2) == 1 then
535         delimiter = "(";
536     end
537     select delimiter
538     case "[" then
539         com = "bmatrix";
540     case "(" then
541         com = "pmatrix";
542     case "|" then
543         com = "vmatrix";
544     case "" then
545         com = "matrix";
546     case "{" then
547         com = "Bmatrix";
548     case "||" then
549         com = "Vmatrix";
550     else
551         com = "pmatrix";
552     end
553     [m,n] = size(mat);
554     str = "{\" + com + "{";
555     if m*n == 0 then
556         str = str + "\ ";
557     else
558         for i=1:m do
559             str = str + strcat(mat(i,:),"&") + "\cr ";
560         end
561     end
562     str = str + "}}";
563 endfunction
564
565 // ==========================================================================
566
567 //This function converts a complex (or a double) into a string in using the
568 //function d2s (double2string) fixed by mathmlconf or latexconf.
569 function str = comp2str(z,plus,minus,img,d2s)
570     [m,n] = size(z);
571     if m*n <> 1 then
572         str = emptystr(z);
573         for i=1:m do
574             for j=1:n do
575                 str(i,j) = comp2str(z(i,j),plus,minus,img,d2s);
576             end
577         end
578         return;
579     end
580     re = real(z);
581     im = imag(z);
582     if re == 0 & im == 0 then
583         str = d2s(0);
584         return;
585     end;
586     if re == 0 then
587         if im == 1 then
588             str = img;
589         elseif im == -1 then
590             str = minus + img;
591         else
592             str = d2s(im) + img;
593         end
594     elseif im == 0 then
595         if re > 0 then
596             str = d2s(re);
597         else
598             str = d2s(re);
599         end
600     elseif im > 0 | im<>im then
601         if im == 1 then
602             str = d2s(re) + plus + img;
603         else
604             str = d2s(re) + plus + d2s(im) + img;
605         end
606     else
607         if im == -1 then
608             str = d2s(re) + minus + img;
609         else
610             str = d2s(re) + d2s(im) + img;
611         end
612     end
613 endfunction
614
615 //This function convert a complex into a coefficient (parenthesis are eventually
616 // added around the coeff, if var=='' (i.e. var^0), no parenthesis are added.
617 function str = comp2coef(z,var,plus,minus,img,op,cp,d2s)
618     [m,n] = size(z);
619     if m*n <> 1 then
620         str = emptystr(z);
621         for i=1:m do
622             for j=1:n do
623                 str(i,j) = comp2coef(z(i,j),var,plus,minus,img,op,cp,d2s);
624             end
625         end
626         return;
627     end
628     re = real(z);
629     im = imag(z);
630     if var <> "" & var <> " " then
631         if re <> 0 & im <> 0 then
632             if re < 0 then
633                 str = minus + op + comp2str(-z,plus,minus,img,d2s) + cp + var;
634             else
635                 str = plus + op + comp2str(z,plus,minus,img,d2s) + cp + var;
636             end
637         elseif re == 0 & im == 0 then
638             str = "";
639         elseif re == 1 & im == 0 then
640             str = plus + var;
641         elseif re == -1 & im == 0 then
642             str = minus + var;
643         elseif re > 0 | im > 0 then
644             str = plus + comp2str(z,plus,minus,img,d2s) + var;
645         else
646             str = comp2str(z,plus,minus,img,d2s) + var;
647         end
648     else
649         if re == 0 & im == 0 then
650             str = "";
651         else
652             str = comp2str(z,plus,minus,img,d2s);
653         end
654     end
655 endfunction
656
657 function str = unknown_type(typ,a,exportFormat)
658     try
659         execstr("str=" + typ + "2" + exportFormat + "(a)");
660     catch
661         error(msprintf(gettext("%s: Type %s is not handled : Define the function %s2%s."),"prettyprint",typ,typ,exportFormat))
662     end
663 endfunction