1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <sal/config.h>
12 #include <string_view>
14 #include "ooxmlimport.hxx"
17 #include <oox/mathml/importutils.hxx>
18 #include <oox/token/namespaces.hxx>
19 #include <rtl/ustring.hxx>
20 #include <rtl/ustrbuf.hxx>
21 #include <sal/log.hxx>
22 #include <o3tl/string_view.hxx>
24 using namespace oox::formulaimport
;
27 The primary internal data structure for the formula is the text representation
28 (the SmNode tree is built from it), so read data must be converted into this format.
31 #define OPENING( token ) XML_STREAM_OPENING( token )
32 #define CLOSING( token ) XML_STREAM_CLOSING( token )
34 // TODO create IS_OPENING(), IS_CLOSING() instead of doing 'next == OPENING( next )' ?
36 SmOoxmlImport::SmOoxmlImport( oox::formulaimport::XmlStream
& s
)
41 OUString
SmOoxmlImport::ConvertToStarMath()
43 return handleStream();
46 // "toplevel" of reading, there will be oMath (if there was oMathPara, that was
47 // up to the parent component to handle)
50 OUString
SmOoxmlImport::handleStream()
52 m_rStream
.ensureOpeningTag( M_TOKEN( oMath
));
54 while( !m_rStream
.atEnd() && m_rStream
.currentToken() != CLOSING( M_TOKEN( oMath
)))
56 // strictly speaking, it is not OMathArg here, but currently supported
57 // functionality is the same like OMathArg, in the future this may need improving
58 OUString item
= readOMathArg( M_TOKEN( oMath
));
65 m_rStream
.ensureClosingTag( M_TOKEN( oMath
));
66 // Placeholders are written out as nothing (i.e. nothing inside e.g. the <e> element),
67 // which will result in "{}" in the formula text. Fix this up.
68 OUString ret2
= ret
.makeStringAndClear().replaceAll( "{}", "<?>" );
69 // And as a result, empty parts of the formula that are not placeholders are written out
70 // as a single space, so fix that up too.
71 ret2
= ret2
.replaceAll( "{ }", "{}" );
72 SAL_INFO( "starmath.ooxml", "Formula: " << ret2
);
76 OUString
SmOoxmlImport::readOMathArg( int stoptoken
)
79 while( !m_rStream
.atEnd() && m_rStream
.currentToken() != CLOSING( stoptoken
))
83 switch( m_rStream
.currentToken())
85 case OPENING( M_TOKEN( acc
)):
86 ret
.append(handleAcc());
88 case OPENING( M_TOKEN( bar
)):
89 ret
.append(handleBar());
91 case OPENING( M_TOKEN( box
)):
92 ret
.append(handleBox());
94 case OPENING( M_TOKEN( borderBox
)):
95 ret
.append(handleBorderBox());
97 case OPENING( M_TOKEN( d
)):
98 ret
.append(handleD());
100 case OPENING( M_TOKEN( eqArr
)):
101 ret
.append(handleEqArr());
103 case OPENING( M_TOKEN( f
)):
104 ret
.append(handleF());
106 case OPENING( M_TOKEN( func
)):
107 ret
.append(handleFunc());
109 case OPENING( M_TOKEN( limLow
)):
110 ret
.append(handleLimLowUpp( LimLow
));
112 case OPENING( M_TOKEN( limUpp
)):
113 ret
.append(handleLimLowUpp( LimUpp
));
115 case OPENING( M_TOKEN( groupChr
)):
116 ret
.append(handleGroupChr());
118 case OPENING( M_TOKEN( m
)):
119 ret
.append(handleM());
121 case OPENING( M_TOKEN( nary
)):
122 ret
.append(handleNary());
124 case OPENING( M_TOKEN( r
)):
125 ret
.append(handleR());
127 case OPENING( M_TOKEN( rad
)):
128 ret
.append(handleRad());
130 case OPENING( M_TOKEN( sPre
)):
131 ret
.append(handleSpre());
133 case OPENING( M_TOKEN( sSub
)):
134 ret
.append(handleSsub());
136 case OPENING( M_TOKEN( sSubSup
)):
137 ret
.append(handleSsubsup());
139 case OPENING( M_TOKEN( sSup
)):
140 ret
.append(handleSsup());
143 m_rStream
.handleUnexpectedTag();
147 return ret
.makeStringAndClear();
150 OUString
SmOoxmlImport::readOMathArgInElement( int token
)
152 m_rStream
.ensureOpeningTag( token
);
153 OUString ret
= readOMathArg( token
);
154 m_rStream
.ensureClosingTag( token
);
158 OUString
SmOoxmlImport::handleAcc()
160 m_rStream
.ensureOpeningTag( M_TOKEN( acc
));
161 sal_Unicode accChr
= 0x302;
162 if( XmlStream::Tag accPr
= m_rStream
.checkOpeningTag( M_TOKEN( accPr
)))
164 if( XmlStream::Tag chr
= m_rStream
.checkOpeningTag( M_TOKEN( chr
)))
166 accChr
= chr
.attribute( M_TOKEN( val
), accChr
);
167 m_rStream
.ensureClosingTag( M_TOKEN( chr
));
169 m_rStream
.ensureClosingTag( M_TOKEN( accPr
));
171 // see aTokenTable in parse.cxx
187 case MS_COMBOVERLINE
:
204 // prefer wide variants for these 3, .docx can't seem to differentiate
205 // between e.g. 'vec' and 'widevec', if whatever the accent is above is short, this
206 // shouldn't matter, but short above a longer expression doesn't look right
233 SAL_WARN( "starmath.ooxml", "Unknown m:chr in m:acc \'" << OUString(accChr
) << "\'" );
236 OUString e
= readOMathArgInElement( M_TOKEN( e
));
237 m_rStream
.ensureClosingTag( M_TOKEN( acc
));
238 return acc
+ " {" + e
+ "}";
241 OUString
SmOoxmlImport::handleBar()
243 m_rStream
.ensureOpeningTag( M_TOKEN( bar
));
244 enum pos_t
{ top
, bot
} topbot
= bot
;
245 if( m_rStream
.checkOpeningTag( M_TOKEN( barPr
)))
247 if( XmlStream::Tag pos
= m_rStream
.checkOpeningTag( M_TOKEN( pos
)))
249 if( pos
.attribute( M_TOKEN( val
)) == "top" )
251 else if( pos
.attribute( M_TOKEN( val
)) == "bot" )
253 m_rStream
.ensureClosingTag( M_TOKEN( pos
));
255 m_rStream
.ensureClosingTag( M_TOKEN( barPr
));
257 OUString e
= readOMathArgInElement( M_TOKEN( e
));
258 m_rStream
.ensureClosingTag( M_TOKEN( bar
));
260 return "overline {" + e
+ "}";
262 return "underline {" + e
+ "}";
265 OUString
SmOoxmlImport::handleBox()
267 // there does not seem to be functionality in LO to actually implement this
268 // (or is there), but at least read in the contents instead of ignoring them
269 m_rStream
.ensureOpeningTag( M_TOKEN( box
));
270 OUString e
= readOMathArgInElement( M_TOKEN( e
));
271 m_rStream
.ensureClosingTag( M_TOKEN( box
));
276 OUString
SmOoxmlImport::handleBorderBox()
278 m_rStream
.ensureOpeningTag( M_TOKEN( borderBox
));
279 bool isStrikeH
= false;
280 if( m_rStream
.checkOpeningTag( M_TOKEN( borderBoxPr
)))
282 if( XmlStream::Tag strikeH
= m_rStream
.checkOpeningTag( M_TOKEN( strikeH
)))
284 if( strikeH
.attribute( M_TOKEN( val
), false ))
286 m_rStream
.ensureClosingTag( M_TOKEN( strikeH
));
288 m_rStream
.ensureClosingTag( M_TOKEN( borderBoxPr
));
290 OUString e
= readOMathArgInElement( M_TOKEN( e
));
291 m_rStream
.ensureClosingTag( M_TOKEN( borderBox
));
293 return "overstrike {" + e
+ "}";
294 // LO does not seem to implement anything for handling the other cases
298 OUString
SmOoxmlImport::handleD()
300 m_rStream
.ensureOpeningTag( M_TOKEN( d
));
301 OUString opening
= "(";
302 OUString closing
= ")";
303 OUString separator
= "|";
304 if( XmlStream::Tag dPr
= m_rStream
.checkOpeningTag( M_TOKEN( dPr
)))
306 if( XmlStream::Tag begChr
= m_rStream
.checkOpeningTag( M_TOKEN( begChr
)))
308 opening
= begChr
.attribute( M_TOKEN( val
), opening
);
309 m_rStream
.ensureClosingTag( M_TOKEN( begChr
));
311 if( XmlStream::Tag sepChr
= m_rStream
.checkOpeningTag( M_TOKEN( sepChr
)))
313 separator
= sepChr
.attribute( M_TOKEN( val
), separator
);
314 m_rStream
.ensureClosingTag( M_TOKEN( sepChr
));
316 if( XmlStream::Tag endChr
= m_rStream
.checkOpeningTag( M_TOKEN( endChr
)))
318 closing
= endChr
.attribute( M_TOKEN( val
), closing
);
319 m_rStream
.ensureClosingTag( M_TOKEN( endChr
));
321 m_rStream
.ensureClosingTag( M_TOKEN( dPr
));
324 opening
= "left lbrace ";
326 closing
= " right rbrace";
327 if( opening
== u
"\u27e6" )
328 opening
= "left ldbracket ";
329 if( closing
== u
"\u27e7" )
330 closing
= " right rdbracket";
332 opening
= "left lline ";
334 closing
= " right rline";
335 if (opening
== OUStringChar(MS_DLINE
)
336 || opening
== OUStringChar(MS_DVERTLINE
))
337 opening
= "left ldline ";
338 if (closing
== OUStringChar(MS_DLINE
)
339 || closing
== OUStringChar(MS_DVERTLINE
))
340 closing
= " right rdline";
341 if (opening
== OUStringChar(MS_LANGLE
)
342 || opening
== OUStringChar(MS_LMATHANGLE
))
343 opening
= "left langle ";
344 if (closing
== OUStringChar(MS_RANGLE
)
345 || closing
== OUStringChar(MS_RMATHANGLE
))
346 closing
= " right rangle";
347 // use scalable brackets (the explicit "left" or "right")
348 if( opening
== "(" || opening
== "[" )
349 opening
= "left " + opening
;
350 if( closing
== ")" || closing
== "]" )
351 closing
= " right " + closing
;
352 if( separator
== "|" ) // plain "|" would be actually "V" (logical or)
353 separator
= " mline ";
354 if( opening
.isEmpty())
355 opening
= "left none ";
356 if( closing
.isEmpty())
357 closing
= " right none";
358 OUStringBuffer
ret( opening
);
360 while( m_rStream
.findTag( OPENING( M_TOKEN( e
))))
363 ret
.append( separator
);
365 ret
.append( readOMathArgInElement( M_TOKEN( e
)));
367 ret
.append( closing
);
368 m_rStream
.ensureClosingTag( M_TOKEN( d
));
369 return ret
.makeStringAndClear();
372 OUString
SmOoxmlImport::handleEqArr()
374 m_rStream
.ensureOpeningTag( M_TOKEN( eqArr
));
377 { // there must be at least one m:e
381 + readOMathArgInElement( M_TOKEN( e
))
383 } while( !m_rStream
.atEnd() && m_rStream
.findTag( OPENING( M_TOKEN( e
))));
384 m_rStream
.ensureClosingTag( M_TOKEN( eqArr
));
385 return "stack {" + ret
+ "}";
388 OUString
SmOoxmlImport::handleF()
390 m_rStream
.ensureOpeningTag( M_TOKEN( f
));
391 enum operation_t
{ bar
, lin
, noBar
} operation
= bar
;
392 if( m_rStream
.checkOpeningTag( M_TOKEN( fPr
)))
394 if( XmlStream::Tag type
= m_rStream
.checkOpeningTag( M_TOKEN( type
)))
396 if( type
.attribute( M_TOKEN( val
)) == "bar" )
398 else if( type
.attribute( M_TOKEN( val
)) == "lin" )
400 else if( type
.attribute( M_TOKEN( val
)) == "noBar" )
402 m_rStream
.ensureClosingTag( M_TOKEN( type
));
404 m_rStream
.ensureClosingTag( M_TOKEN( fPr
));
406 OUString num
= readOMathArgInElement( M_TOKEN( num
));
407 OUString den
= readOMathArgInElement( M_TOKEN( den
));
408 m_rStream
.ensureClosingTag( M_TOKEN( f
));
409 if( operation
== bar
)
410 return "{" + num
+ "} over {" + den
+ "}";
411 else if( operation
== lin
)
412 return "{" + num
+ "} / {" + den
+ "}";
415 return "binom {" + num
+ "} {" + den
+ "}";
419 OUString
SmOoxmlImport::handleFunc()
421 //lim from{x rightarrow 1} x
422 m_rStream
.ensureOpeningTag( M_TOKEN( func
));
423 OUString fname
= readOMathArgInElement( M_TOKEN( fName
));
424 // fix the various functions
425 if( fname
.startsWith( "lim csub {" ))
426 fname
= OUString::Concat("lim from {") + fname
.subView( 10 );
427 OUString ret
= fname
+ " {" + readOMathArgInElement( M_TOKEN( e
)) + "}";
428 m_rStream
.ensureClosingTag( M_TOKEN( func
));
432 OUString
SmOoxmlImport::handleLimLowUpp( LimLowUpp_t limlowupp
)
434 int token
= limlowupp
== LimLow
? M_TOKEN( limLow
) : M_TOKEN( limUpp
);
435 m_rStream
.ensureOpeningTag( token
);
436 OUString e
= readOMathArgInElement( M_TOKEN( e
));
437 OUString lim
= readOMathArgInElement( M_TOKEN( lim
));
438 m_rStream
.ensureClosingTag( token
);
439 // fix up overbrace/underbrace (use { }, as {} will be converted to a placeholder)
440 if( limlowupp
== LimUpp
&& e
.endsWith( " overbrace { }" ))
441 return e
.subView( 0, e
.getLength() - 2 ) + lim
+ "}";
442 if( limlowupp
== LimLow
&& e
.endsWith( " underbrace { }" ))
443 return e
.subView( 0, e
.getLength() - 2 ) + lim
+ "}";
445 + ( limlowupp
== LimLow
446 ? std::u16string_view( u
" csub {" ) : std::u16string_view( u
" csup {" ))
450 OUString
SmOoxmlImport::handleGroupChr()
452 m_rStream
.ensureOpeningTag( M_TOKEN( groupChr
));
453 sal_Unicode chr
= 0x23df;
454 enum pos_t
{ top
, bot
} pos
= bot
;
455 if( m_rStream
.checkOpeningTag( M_TOKEN( groupChrPr
)))
457 if( XmlStream::Tag chrTag
= m_rStream
.checkOpeningTag( M_TOKEN( chr
)))
459 chr
= chrTag
.attribute( M_TOKEN( val
), chr
);
460 m_rStream
.ensureClosingTag( M_TOKEN( chr
));
462 if( XmlStream::Tag posTag
= m_rStream
.checkOpeningTag( M_TOKEN( pos
)))
464 if( posTag
.attribute( M_TOKEN( val
), OUString( "bot" )) == "top" )
466 m_rStream
.ensureClosingTag( M_TOKEN( pos
));
468 m_rStream
.ensureClosingTag( M_TOKEN( groupChrPr
));
470 OUString e
= readOMathArgInElement( M_TOKEN( e
));
471 m_rStream
.ensureClosingTag( M_TOKEN( groupChr
));
472 if( pos
== top
&& chr
== u
'\x23de')
473 return "{" + e
+ "} overbrace { }";
474 if( pos
== bot
&& chr
== u
'\x23df')
475 return "{" + e
+ "} underbrace { }";
477 return "{" + e
+ "} csup {" + OUStringChar( chr
) + "}";
479 return "{" + e
+ "} csub {" + OUStringChar( chr
) + "}";
482 OUString
SmOoxmlImport::handleM()
484 m_rStream
.ensureOpeningTag( M_TOKEN( m
));
485 OUStringBuffer allrows
;
486 do // there must be at least one m:mr
488 m_rStream
.ensureOpeningTag( M_TOKEN( mr
));
490 do // there must be at least one m:e
494 row
.append(readOMathArgInElement( M_TOKEN( e
)));
495 } while( !m_rStream
.atEnd() && m_rStream
.findTag( OPENING( M_TOKEN( e
))));
496 if( !allrows
.isEmpty())
497 allrows
.append(" ## ");
499 m_rStream
.ensureClosingTag( M_TOKEN( mr
));
500 } while( !m_rStream
.atEnd() && m_rStream
.findTag( OPENING( M_TOKEN( mr
))));
501 m_rStream
.ensureClosingTag( M_TOKEN( m
));
502 return "matrix {" + allrows
+ "}";
505 OUString
SmOoxmlImport::handleNary()
507 m_rStream
.ensureOpeningTag( M_TOKEN( nary
));
508 sal_Unicode chr
= 0x222b;
509 bool subHide
= false;
510 bool supHide
= false;
511 if( m_rStream
.checkOpeningTag( M_TOKEN( naryPr
)))
513 if( XmlStream::Tag chrTag
= m_rStream
.checkOpeningTag( M_TOKEN( chr
)))
515 chr
= chrTag
.attribute( M_TOKEN( val
), chr
);
516 m_rStream
.ensureClosingTag( M_TOKEN( chr
));
518 if( XmlStream::Tag subHideTag
= m_rStream
.checkOpeningTag( M_TOKEN( subHide
)))
520 subHide
= subHideTag
.attribute( M_TOKEN( val
), subHide
);
521 m_rStream
.ensureClosingTag( M_TOKEN( subHide
));
523 if( XmlStream::Tag supHideTag
= m_rStream
.checkOpeningTag( M_TOKEN( supHide
)))
525 supHide
= supHideTag
.attribute( M_TOKEN( val
), supHide
);
526 m_rStream
.ensureClosingTag( M_TOKEN( supHide
));
528 m_rStream
.ensureClosingTag( M_TOKEN( naryPr
));
530 OUString sub
= readOMathArgInElement( M_TOKEN( sub
));
531 OUString sup
= readOMathArgInElement( M_TOKEN( sup
));
532 OUString e
= readOMathArgInElement( M_TOKEN( e
));
564 SAL_WARN( "starmath.ooxml", "Unknown m:nary chr \'" << OUString(chr
) << "\'" );
568 ret
+= " from {" + sub
+ "}";
570 ret
+= " to {" + sup
+ "}";
571 ret
+= " {" + e
+ "}";
572 m_rStream
.ensureClosingTag( M_TOKEN( nary
));
577 OUString
SmOoxmlImport::handleR()
579 m_rStream
.ensureOpeningTag( M_TOKEN( r
));
581 bool literal
= false;
582 if( XmlStream::Tag rPr
= m_rStream
.checkOpeningTag( M_TOKEN( rPr
)))
584 if( XmlStream::Tag litTag
= m_rStream
.checkOpeningTag( M_TOKEN( lit
)))
586 literal
= litTag
.attribute( M_TOKEN( val
), true );
587 m_rStream
.ensureClosingTag( M_TOKEN( lit
));
589 if( XmlStream::Tag norTag
= m_rStream
.checkOpeningTag( M_TOKEN( nor
)))
591 normal
= norTag
.attribute( M_TOKEN( val
), true );
592 m_rStream
.ensureClosingTag( M_TOKEN( nor
));
594 m_rStream
.ensureClosingTag( M_TOKEN( rPr
));
597 while( !m_rStream
.atEnd() && m_rStream
.currentToken() != CLOSING( m_rStream
.currentToken()))
599 switch( m_rStream
.currentToken())
601 case OPENING( M_TOKEN( t
)):
603 XmlStream::Tag rtag
= m_rStream
.ensureOpeningTag( M_TOKEN( t
));
604 if( rtag
.attribute( OOX_TOKEN( xml
, space
)) != "preserve" )
605 text
.append(o3tl::trim(rtag
.text
));
607 text
.append(rtag
.text
);
608 m_rStream
.ensureClosingTag( M_TOKEN( t
));
612 m_rStream
.handleUnexpectedTag();
616 m_rStream
.ensureClosingTag( M_TOKEN( r
));
617 if( normal
|| literal
)
619 text
.insert(0, "\"");
622 return text
.makeStringAndClear().replaceAll("{", "\\{").replaceAll("}", "\\}");
625 OUString
SmOoxmlImport::handleRad()
627 m_rStream
.ensureOpeningTag( M_TOKEN( rad
));
628 bool degHide
= false;
629 if( m_rStream
.checkOpeningTag( M_TOKEN( radPr
)))
631 if( XmlStream::Tag degHideTag
= m_rStream
.checkOpeningTag( M_TOKEN( degHide
)))
633 degHide
= degHideTag
.attribute( M_TOKEN( val
), degHide
);
634 m_rStream
.ensureClosingTag( M_TOKEN( degHide
));
636 m_rStream
.ensureClosingTag( M_TOKEN( radPr
));
638 OUString deg
= readOMathArgInElement( M_TOKEN( deg
));
639 OUString e
= readOMathArgInElement( M_TOKEN( e
));
640 m_rStream
.ensureClosingTag( M_TOKEN( rad
));
642 return "sqrt {" + e
+ "}";
644 return "nroot {" + deg
+ "} {" + e
+ "}";
647 OUString
SmOoxmlImport::handleSpre()
649 m_rStream
.ensureOpeningTag( M_TOKEN( sPre
));
650 OUString sub
= readOMathArgInElement( M_TOKEN( sub
));
651 OUString sup
= readOMathArgInElement( M_TOKEN( sup
));
652 OUString e
= readOMathArgInElement( M_TOKEN( e
));
653 m_rStream
.ensureClosingTag( M_TOKEN( sPre
));
654 return "{" + e
+ "} lsub {" + sub
+ "} lsup {" + sup
+ "}";
657 OUString
SmOoxmlImport::handleSsub()
659 m_rStream
.ensureOpeningTag( M_TOKEN( sSub
));
660 OUString e
= readOMathArgInElement( M_TOKEN( e
));
661 OUString sub
= readOMathArgInElement( M_TOKEN( sub
));
662 m_rStream
.ensureClosingTag( M_TOKEN( sSub
));
663 return "{" + e
+ "} rsub {" + sub
+ "}";
666 OUString
SmOoxmlImport::handleSsubsup()
668 m_rStream
.ensureOpeningTag( M_TOKEN( sSubSup
));
669 OUString e
= readOMathArgInElement( M_TOKEN( e
));
670 OUString sub
= readOMathArgInElement( M_TOKEN( sub
));
671 OUString sup
= readOMathArgInElement( M_TOKEN( sup
));
672 m_rStream
.ensureClosingTag( M_TOKEN( sSubSup
));
673 return "{" + e
+ "} rsub {" + sub
+ "} rsup {" + sup
+ "}";
676 OUString
SmOoxmlImport::handleSsup()
678 m_rStream
.ensureOpeningTag( M_TOKEN( sSup
));
679 OUString e
= readOMathArgInElement( M_TOKEN( e
));
680 OUString sup
= readOMathArgInElement( M_TOKEN( sup
));
681 m_rStream
.ensureClosingTag( M_TOKEN( sSup
));
682 return "{" + e
+ "} ^ {" + sup
+ "}";
685 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */