1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 /*
21  Warning: The SvXMLElementExport helper class creates the beginning and
22  closing tags of xml elements in its constructor and destructor, so there's
23  hidden stuff going on, on occasion the ordering of these classes declarations
24  may be significant
25 */
26 
27 #include <com/sun/star/xml/sax/Writer.hpp>
28 #include <com/sun/star/beans/PropertyAttribute.hpp>
29 #include <com/sun/star/embed/ElementModes.hpp>
30 #include <com/sun/star/util/MeasureUnit.hpp>
31 #include <com/sun/star/task/XStatusIndicator.hpp>
32 #include <com/sun/star/uno/Any.h>
33 
34 #include <officecfg/Office/Common.hxx>
35 #include <rtl/math.hxx>
36 #include <sfx2/frame.hxx>
37 #include <sfx2/docfile.hxx>
38 #include <sfx2/sfxsids.hrc>
39 #include <osl/diagnose.h>
40 #include <unotools/saveopt.hxx>
41 #include <sot/storage.hxx>
42 #include <svl/itemset.hxx>
43 #include <svl/stritem.hxx>
44 #include <comphelper/fileformat.h>
45 #include <comphelper/processfactory.hxx>
46 #include <unotools/streamwrap.hxx>
47 #include <sax/tools/converter.hxx>
48 #include <xmloff/xmlnamespace.hxx>
49 #include <xmloff/xmltoken.hxx>
50 #include <xmloff/namespacemap.hxx>
51 #include <xmloff/attrlist.hxx>
52 #include <comphelper/genericpropertyset.hxx>
53 #include <comphelper/servicehelper.hxx>
54 #include <comphelper/propertysetinfo.hxx>
55 #include <tools/diagnose_ex.h>
56 #include <sal/log.hxx>
57 
58 #include <stack>
59 
60 #include <mathmlexport.hxx>
61 #include <xparsmlbase.hxx>
62 #include <strings.hrc>
63 #include <smmod.hxx>
64 #include <unomodel.hxx>
65 #include <document.hxx>
66 #include <utility.hxx>
67 #include <cfgitem.hxx>
68 #include <starmathdatabase.hxx>
69 
70 using namespace ::com::sun::star::beans;
71 using namespace ::com::sun::star::document;
72 using namespace ::com::sun::star::lang;
73 using namespace ::com::sun::star::uno;
74 using namespace ::com::sun::star;
75 using namespace ::xmloff::token;
76 
77 namespace
78 {
79 bool IsInPrivateUseArea(sal_Unicode cChar) { return 0xE000 <= cChar && cChar <= 0xF8FF; }
80 
81 sal_Unicode ConvertMathToMathML(sal_Unicode cChar)
82 {
83     sal_Unicode cRes = cChar;
84     if (IsInPrivateUseArea(cChar))
85     {
86         SAL_WARN("starmath", "Error: private use area characters should no longer be in use!");
87         cRes = u'@'; // just some character that should easily be notice as odd in the context
88     }
89     return cRes;
90 }
91 }
92 
93 bool SmXMLExportWrapper::Export(SfxMedium& rMedium)
94 {
95     bool bRet = true;
96     uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
97 
98     //Get model
99     uno::Reference<lang::XComponent> xModelComp = xModel;
100 
101     bool bEmbedded = false;
102     SmModel* pModel = comphelper::getUnoTunnelImplementation<SmModel>(xModel);
103 
104     SmDocShell* pDocShell = pModel ? static_cast<SmDocShell*>(pModel->GetObjectShell()) : nullptr;
105     if (pDocShell && SfxObjectCreateMode::EMBEDDED == pDocShell->GetCreateMode())
106         bEmbedded = true;
107 
108     uno::Reference<task::XStatusIndicator> xStatusIndicator;
109     if (!bEmbedded)
110     {
111         if (pDocShell /*&& pDocShell->GetMedium()*/)
112         {
113             OSL_ENSURE(pDocShell->GetMedium() == &rMedium, "different SfxMedium found");
114 
115             SfxItemSet* pSet = rMedium.GetItemSet();
116             if (pSet)
117             {
118                 const SfxUnoAnyItem* pItem = static_cast<const SfxUnoAnyItem*>(
119                     pSet->GetItem(SID_PROGRESS_STATUSBAR_CONTROL));
120                 if (pItem)
121                     pItem->GetValue() >>= xStatusIndicator;
122             }
123         }
124 
125         // set progress range and start status indicator
126         if (xStatusIndicator.is())
127         {
128             sal_Int32 nProgressRange = bFlat ? 1 : 3;
129             xStatusIndicator->start(SmResId(STR_STATSTR_WRITING), nProgressRange);
130         }
131     }
132 
133     // create XPropertySet with three properties for status indicator
134     comphelper::PropertyMapEntry aInfoMap[]
135         = { { OUString("UsePrettyPrinting"), 0, cppu::UnoType<bool>::get(),
136               beans::PropertyAttribute::MAYBEVOID, 0 },
137             { OUString("BaseURI"), 0, ::cppu::UnoType<OUString>::get(),
138               beans::PropertyAttribute::MAYBEVOID, 0 },
139             { OUString("StreamRelPath"), 0, ::cppu::UnoType<OUString>::get(),
140               beans::PropertyAttribute::MAYBEVOID, 0 },
141             { OUString("StreamName"), 0, ::cppu::UnoType<OUString>::get(),
142               beans::PropertyAttribute::MAYBEVOID, 0 },
143             { OUString(), 0, css::uno::Type(), 0, 0 } };
144     uno::Reference<beans::XPropertySet> xInfoSet(
145         comphelper::GenericPropertySet_CreateInstance(new comphelper::PropertySetInfo(aInfoMap)));
146 
147     bool bUsePrettyPrinting
148         = bFlat || officecfg::Office::Common::Save::Document::PrettyPrinting::get();
149     xInfoSet->setPropertyValue("UsePrettyPrinting", Any(bUsePrettyPrinting));
150 
151     // Set base URI
152     OUString sPropName("BaseURI");
153     xInfoSet->setPropertyValue(sPropName, makeAny(rMedium.GetBaseURL(true)));
154 
155     sal_Int32 nSteps = 0;
156     if (xStatusIndicator.is())
157         xStatusIndicator->setValue(nSteps++);
158     if (!bFlat) //Storage (Package) of Stream
159     {
160         uno::Reference<embed::XStorage> xStg = rMedium.GetOutputStorage();
161         bool bOASIS = (SotStorage::GetVersion(xStg) > SOFFICE_FILEFORMAT_60);
162 
163         // TODO/LATER: handle the case of embedded links gracefully
164         if (bEmbedded) //&& !pStg->IsRoot() )
165         {
166             OUString aName;
167             if (rMedium.GetItemSet())
168             {
169                 const SfxStringItem* pDocHierarchItem = static_cast<const SfxStringItem*>(
170                     rMedium.GetItemSet()->GetItem(SID_DOC_HIERARCHICALNAME));
171                 if (pDocHierarchItem)
172                     aName = pDocHierarchItem->GetValue();
173             }
174 
175             if (!aName.isEmpty())
176             {
177                 sPropName = "StreamRelPath";
178                 xInfoSet->setPropertyValue(sPropName, makeAny(aName));
179             }
180         }
181 
182         if (!bEmbedded)
183         {
184             if (xStatusIndicator.is())
185                 xStatusIndicator->setValue(nSteps++);
186 
187             bRet = WriteThroughComponent(xStg, xModelComp, "meta.xml", xContext, xInfoSet,
188                                          (bOASIS ? "com.sun.star.comp.Math.XMLOasisMetaExporter"
189                                                  : "com.sun.star.comp.Math.XMLMetaExporter"));
190         }
191         if (bRet)
192         {
193             if (xStatusIndicator.is())
194                 xStatusIndicator->setValue(nSteps++);
195 
196             bRet = WriteThroughComponent(xStg, xModelComp, "content.xml", xContext, xInfoSet,
197                                          "com.sun.star.comp.Math.XMLContentExporter");
198         }
199 
200         if (bRet)
201         {
202             if (xStatusIndicator.is())
203                 xStatusIndicator->setValue(nSteps++);
204 
205             bRet = WriteThroughComponent(xStg, xModelComp, "settings.xml", xContext, xInfoSet,
206                                          (bOASIS ? "com.sun.star.comp.Math.XMLOasisSettingsExporter"
207                                                  : "com.sun.star.comp.Math.XMLSettingsExporter"));
208         }
209     }
210     else
211     {
212         SvStream* pStream = rMedium.GetOutStream();
213         uno::Reference<io::XOutputStream> xOut(new utl::OOutputStreamWrapper(*pStream));
214 
215         if (xStatusIndicator.is())
216             xStatusIndicator->setValue(nSteps++);
217 
218         bRet = WriteThroughComponent(xOut, xModelComp, xContext, xInfoSet,
219                                      "com.sun.star.comp.Math.XMLContentExporter");
220     }
221 
222     if (xStatusIndicator.is())
223         xStatusIndicator->end();
224 
225     return bRet;
226 }
227 
228 /// export through an XML exporter component (output stream version)
229 bool SmXMLExportWrapper::WriteThroughComponent(const Reference<io::XOutputStream>& xOutputStream,
230                                                const Reference<XComponent>& xComponent,
231                                                Reference<uno::XComponentContext> const& rxContext,
232                                                Reference<beans::XPropertySet> const& rPropSet,
233                                                const char* pComponentName)
234 {
235     OSL_ENSURE(xOutputStream.is(), "I really need an output stream!");
236     OSL_ENSURE(xComponent.is(), "Need component!");
237     OSL_ENSURE(nullptr != pComponentName, "Need component name!");
238 
239     // get component
240     Reference<xml::sax::XWriter> xSaxWriter = xml::sax::Writer::create(rxContext);
241 
242     // connect XML writer to output stream
243     xSaxWriter->setOutputStream(xOutputStream);
244     if (m_bUseHTMLMLEntities)
245         xSaxWriter->setCustomEntityNames(starmathdatabase::icustomMathmlHtmlEntitiesExport);
246 
247     // prepare arguments (prepend doc handler to given arguments)
248     Sequence<Any> aArgs(2);
249     aArgs[0] <<= xSaxWriter;
250     aArgs[1] <<= rPropSet;
251 
252     // get filter component
253     Reference<document::XExporter> xExporter(
254         rxContext->getServiceManager()->createInstanceWithArgumentsAndContext(
255             OUString::createFromAscii(pComponentName), aArgs, rxContext),
256         UNO_QUERY);
257     OSL_ENSURE(xExporter.is(), "can't instantiate export filter component");
258     if (!xExporter.is())
259         return false;
260 
261     // connect model and filter
262     xExporter->setSourceDocument(xComponent);
263 
264     // filter!
265     Reference<XFilter> xFilter(xExporter, UNO_QUERY);
266     uno::Sequence<PropertyValue> aProps(0);
267     xFilter->filter(aProps);
268 
269     auto pFilter = comphelper::getUnoTunnelImplementation<SmXMLExport>(xFilter);
270     return pFilter == nullptr || pFilter->GetSuccess();
271 }
272 
273 /// export through an XML exporter component (storage version)
274 bool SmXMLExportWrapper::WriteThroughComponent(const Reference<embed::XStorage>& xStorage,
275                                                const Reference<XComponent>& xComponent,
276                                                const char* pStreamName,
277                                                Reference<uno::XComponentContext> const& rxContext,
278                                                Reference<beans::XPropertySet> const& rPropSet,
279                                                const char* pComponentName)
280 {
281     OSL_ENSURE(xStorage.is(), "Need storage!");
282     OSL_ENSURE(nullptr != pStreamName, "Need stream name!");
283 
284     // open stream
285     Reference<io::XStream> xStream;
286     OUString sStreamName = OUString::createFromAscii(pStreamName);
287     try
288     {
289         xStream = xStorage->openStreamElement(sStreamName, embed::ElementModes::READWRITE
290                                                                | embed::ElementModes::TRUNCATE);
291     }
292     catch (const uno::Exception&)
293     {
294         DBG_UNHANDLED_EXCEPTION("starmath", "Can't create output stream in package");
295         return false;
296     }
297 
298     uno::Reference<beans::XPropertySet> xSet(xStream, uno::UNO_QUERY);
299     xSet->setPropertyValue("MediaType", Any(OUString("text/xml")));
300 
301     // all streams must be encrypted in encrypted document
302     xSet->setPropertyValue("UseCommonStoragePasswordEncryption", Any(true));
303 
304     // set Base URL
305     if (rPropSet.is())
306     {
307         rPropSet->setPropertyValue("StreamName", makeAny(sStreamName));
308     }
309 
310     // write the stuff
311     bool bRet = WriteThroughComponent(xStream->getOutputStream(), xComponent, rxContext, rPropSet,
312                                       pComponentName);
313 
314     return bRet;
315 }
316 
317 SmXMLExport::SmXMLExport(const css::uno::Reference<css::uno::XComponentContext>& rContext,
318                          OUString const& implementationName, SvXMLExportFlags nExportFlags)
319     : SvXMLExport(rContext, implementationName, util::MeasureUnit::INCH, XML_MATH, nExportFlags)
320     , pTree(nullptr)
321     , bSuccess(false)
322 {
323 }
324 
325 sal_Int64 SAL_CALL SmXMLExport::getSomething(const uno::Sequence<sal_Int8>& rId)
326 {
327     if (isUnoTunnelId<SmXMLExport>(rId))
328         return sal::static_int_cast<sal_Int64>(reinterpret_cast<sal_uIntPtr>(this));
329 
330     return SvXMLExport::getSomething(rId);
331 }
332 
333 const uno::Sequence<sal_Int8>& SmXMLExport::getUnoTunnelId() noexcept
334 {
335     static const UnoTunnelIdInit theSmXMLExportUnoTunnelId;
336     return theSmXMLExportUnoTunnelId.getSeq();
337 }
338 
339 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
340 Math_XMLExporter_get_implementation(css::uno::XComponentContext* context,
341                                     css::uno::Sequence<css::uno::Any> const&)
342 {
343     return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLExporter",
344                                          SvXMLExportFlags::OASIS | SvXMLExportFlags::ALL));
345 }
346 
347 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
348 Math_XMLMetaExporter_get_implementation(css::uno::XComponentContext* context,
349                                         css::uno::Sequence<css::uno::Any> const&)
350 {
351     return cppu::acquire(
352         new SmXMLExport(context, "com.sun.star.comp.Math.XMLMetaExporter", SvXMLExportFlags::META));
353 }
354 
355 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
356 Math_XMLOasisMetaExporter_get_implementation(css::uno::XComponentContext* context,
357                                              css::uno::Sequence<css::uno::Any> const&)
358 {
359     return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLOasisMetaExporter",
360                                          SvXMLExportFlags::OASIS | SvXMLExportFlags::META));
361 }
362 
363 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
364 Math_XMLSettingsExporter_get_implementation(css::uno::XComponentContext* context,
365                                             css::uno::Sequence<css::uno::Any> const&)
366 {
367     return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLSettingsExporter",
368                                          SvXMLExportFlags::SETTINGS));
369 }
370 
371 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
372 Math_XMLOasisSettingsExporter_get_implementation(css::uno::XComponentContext* context,
373                                                  css::uno::Sequence<css::uno::Any> const&)
374 {
375     return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLOasisSettingsExporter",
376                                          SvXMLExportFlags::OASIS | SvXMLExportFlags::SETTINGS));
377 }
378 
379 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
380 Math_XMLContentExporter_get_implementation(css::uno::XComponentContext* context,
381                                            css::uno::Sequence<css::uno::Any> const&)
382 {
383     return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLContentExporter",
384                                          SvXMLExportFlags::OASIS | SvXMLExportFlags::CONTENT));
385 }
386 
387 ErrCode SmXMLExport::exportDoc(enum XMLTokenEnum eClass)
388 {
389     if (!(getExportFlags() & SvXMLExportFlags::CONTENT))
390     {
391         SvXMLExport::exportDoc(eClass);
392     }
393     else
394     {
395         uno::Reference<frame::XModel> xModel = GetModel();
396         SmModel* pModel = comphelper::getUnoTunnelImplementation<SmModel>(xModel);
397 
398         if (pModel)
399         {
400             SmDocShell* pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell());
401             pTree = pDocShell->GetFormulaTree();
402             aText = pDocShell->GetText();
403         }
404 
405         GetDocHandler()->startDocument();
406 
407         addChaffWhenEncryptedStorage();
408 
409         /*Add xmlns line*/
410         SvXMLAttributeList& rList = GetAttrList();
411 
412         // make use of a default namespace
413         ResetNamespaceMap(); // Math doesn't need namespaces from xmloff, since it now uses default namespaces (because that is common with current MathML usage in the web)
414         GetNamespaceMap_().Add(OUString(), GetXMLToken(XML_N_MATH), XML_NAMESPACE_MATH);
415 
416         rList.AddAttribute(GetNamespaceMap().GetAttrNameByKey(XML_NAMESPACE_MATH),
417                            GetNamespaceMap().GetNameByKey(XML_NAMESPACE_MATH));
418 
419         //I think we need something like ImplExportEntities();
420         ExportContent_();
421         GetDocHandler()->endDocument();
422     }
423 
424     bSuccess = true;
425     return ERRCODE_NONE;
426 }
427 
428 void SmXMLExport::ExportContent_()
429 {
430     uno::Reference<frame::XModel> xModel = GetModel();
431     SmModel* pModel = comphelper::getUnoTunnelImplementation<SmModel>(xModel);
432     SmDocShell* pDocShell = pModel ? static_cast<SmDocShell*>(pModel->GetObjectShell()) : nullptr;
433     OSL_ENSURE(pDocShell, "doc shell missing");
434 
435     if (pDocShell && !pDocShell->GetFormat().IsTextmode())
436     {
437         // If the Math equation is not in text mode, we attach a display="block"
438         // attribute on the <math> root. We don't do anything if it is in
439         // text mode, the default display="inline" value will be used.
440         AddAttribute(XML_NAMESPACE_MATH, XML_DISPLAY, XML_BLOCK);
441     }
442     SvXMLElementExport aEquation(*this, XML_NAMESPACE_MATH, XML_MATH, true, true);
443     std::unique_ptr<SvXMLElementExport> pSemantics;
444 
445     if (!aText.isEmpty())
446     {
447         pSemantics.reset(
448             new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_SEMANTICS, true, true));
449     }
450 
451     ExportNodes(pTree, 0);
452 
453     if (aText.isEmpty())
454         return;
455 
456     SmModule* pMod = SM_MOD();
457     sal_uInt16 nSmSyntaxVersion = pMod->GetConfig()->GetDefaultSmSyntaxVersion();
458 
459     // Convert symbol names
460     if (pDocShell)
461     {
462         nSmSyntaxVersion = pDocShell->GetSmSyntaxVersion();
463         AbstractSmParser* rParser = pDocShell->GetParser();
464         bool bVal = rParser->IsExportSymbolNames();
465         rParser->SetExportSymbolNames(true);
466         auto pTmpTree = rParser->Parse(aText);
467         aText = rParser->GetText();
468         pTmpTree.reset();
469         rParser->SetExportSymbolNames(bVal);
470     }
471 
472     OUStringBuffer sStrBuf(12);
473     sStrBuf.append(u"StarMath ");
474     if (nSmSyntaxVersion == 5)
475         sStrBuf.append(u"5.0");
476     else
477         sStrBuf.append(static_cast<sal_Int32>(nSmSyntaxVersion));
478 
479     AddAttribute(XML_NAMESPACE_MATH, XML_ENCODING, sStrBuf.makeStringAndClear());
480     SvXMLElementExport aAnnotation(*this, XML_NAMESPACE_MATH, XML_ANNOTATION, true, false);
481     GetDocHandler()->characters(aText);
482 }
483 
484 void SmXMLExport::GetViewSettings(Sequence<PropertyValue>& aProps)
485 {
486     uno::Reference<frame::XModel> xModel = GetModel();
487     if (!xModel.is())
488         return;
489 
490     SmModel* pModel = comphelper::getUnoTunnelImplementation<SmModel>(xModel);
491 
492     if (!pModel)
493         return;
494 
495     SmDocShell* pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell());
496     if (!pDocShell)
497         return;
498 
499     aProps.realloc(4);
500     PropertyValue* pValue = aProps.getArray();
501     sal_Int32 nIndex = 0;
502 
503     tools::Rectangle aRect(pDocShell->GetVisArea());
504 
505     pValue[nIndex].Name = "ViewAreaTop";
506     pValue[nIndex++].Value <<= aRect.Top();
507 
508     pValue[nIndex].Name = "ViewAreaLeft";
509     pValue[nIndex++].Value <<= aRect.Left();
510 
511     pValue[nIndex].Name = "ViewAreaWidth";
512     pValue[nIndex++].Value <<= aRect.GetWidth();
513 
514     pValue[nIndex].Name = "ViewAreaHeight";
515     pValue[nIndex++].Value <<= aRect.GetHeight();
516 }
517 
518 void SmXMLExport::GetConfigurationSettings(Sequence<PropertyValue>& rProps)
519 {
520     Reference<XPropertySet> xProps(GetModel(), UNO_QUERY);
521     if (!xProps.is())
522         return;
523 
524     Reference<XPropertySetInfo> xPropertySetInfo = xProps->getPropertySetInfo();
525     if (!xPropertySetInfo.is())
526         return;
527 
528     Sequence<Property> aProps = xPropertySetInfo->getProperties();
529     const sal_Int32 nCount = aProps.getLength();
530     if (!nCount)
531         return;
532 
533     rProps.realloc(nCount);
534     SmMathConfig* pConfig = SM_MOD()->GetConfig();
535     const bool bUsedSymbolsOnly = pConfig && pConfig->IsSaveOnlyUsedSymbols();
536 
537     std::transform(aProps.begin(), aProps.end(), rProps.begin(),
538                    [bUsedSymbolsOnly, &xProps](Property& prop) {
539                        PropertyValue aRet;
540                        if (prop.Name != "Formula" && prop.Name != "BasicLibraries"
541                            && prop.Name != "DialogLibraries" && prop.Name != "RuntimeUID")
542                        {
543                            aRet.Name = prop.Name;
544                            OUString aActualName(prop.Name);
545                            // handle 'save used symbols only'
546                            if (bUsedSymbolsOnly && prop.Name == "Symbols")
547                                aActualName = "UserDefinedSymbolsInUse";
548                            aRet.Value = xProps->getPropertyValue(aActualName);
549                        }
550                        return aRet;
551                    });
552 }
553 
554 void SmXMLExport::ExportLine(const SmNode* pNode, int nLevel) { ExportExpression(pNode, nLevel); }
555 
556 void SmXMLExport::ExportBinaryHorizontal(const SmNode* pNode, int nLevel)
557 {
558     TG nGroup = pNode->GetToken().nGroup;
559 
560     SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true);
561 
562     // Unfold the binary tree structure as long as the nodes are SmBinHorNode
563     // with the same nGroup. This will reduce the number of nested <mrow>
564     // elements e.g. we only need three <mrow> levels to export
565 
566     // "a*b*c*d+e*f*g*h+i*j*k*l = a*b*c*d+e*f*g*h+i*j*k*l =
567     //  a*b*c*d+e*f*g*h+i*j*k*l = a*b*c*d+e*f*g*h+i*j*k*l"
568 
569     // See https://www.libreoffice.org/bugzilla/show_bug.cgi?id=66081
570     ::std::stack<const SmNode*> s;
571     s.push(pNode);
572     while (!s.empty())
573     {
574         const SmNode* node = s.top();
575         s.pop();
576         if (node->GetType() != SmNodeType::BinHor || node->GetToken().nGroup != nGroup)
577         {
578             ExportNodes(node, nLevel + 1);
579             continue;
580         }
581         const SmBinHorNode* binNode = static_cast<const SmBinHorNode*>(node);
582         s.push(binNode->RightOperand());
583         s.push(binNode->Symbol());
584         s.push(binNode->LeftOperand());
585     }
586 }
587 
588 void SmXMLExport::ExportUnaryHorizontal(const SmNode* pNode, int nLevel)
589 {
590     ExportExpression(pNode, nLevel);
591 }
592 
593 void SmXMLExport::ExportExpression(const SmNode* pNode, int nLevel,
594                                    bool bNoMrowContainer /*=false*/)
595 {
596     std::unique_ptr<SvXMLElementExport> pRow;
597     size_t nSize = pNode->GetNumSubNodes();
598 
599     // #i115443: nodes of type expression always need to be grouped with mrow statement
600     if (!bNoMrowContainer && (nSize > 1 || pNode->GetType() == SmNodeType::Expression))
601         pRow.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MROW, true, true));
602 
603     for (size_t i = 0; i < nSize; ++i)
604     {
605         if (const SmNode* pTemp = pNode->GetSubNode(i))
606             ExportNodes(pTemp, nLevel + 1);
607     }
608 }
609 
610 void SmXMLExport::ExportBinaryVertical(const SmNode* pNode, int nLevel)
611 {
612     assert(pNode->GetNumSubNodes() == 3);
613     const SmNode* pNum = pNode->GetSubNode(0);
614     const SmNode* pDenom = pNode->GetSubNode(2);
615     if (pNum->GetType() == SmNodeType::Align && pNum->GetToken().eType != TALIGNC)
616     {
617         // A left or right alignment is specified on the numerator:
618         // attach the corresponding numalign attribute.
619         AddAttribute(XML_NAMESPACE_MATH, XML_NUMALIGN,
620                      pNum->GetToken().eType == TALIGNL ? XML_LEFT : XML_RIGHT);
621     }
622     if (pDenom->GetType() == SmNodeType::Align && pDenom->GetToken().eType != TALIGNC)
623     {
624         // A left or right alignment is specified on the denominator:
625         // attach the corresponding denomalign attribute.
626         AddAttribute(XML_NAMESPACE_MATH, XML_DENOMALIGN,
627                      pDenom->GetToken().eType == TALIGNL ? XML_LEFT : XML_RIGHT);
628     }
629     SvXMLElementExport aFraction(*this, XML_NAMESPACE_MATH, XML_MFRAC, true, true);
630     ExportNodes(pNum, nLevel);
631     ExportNodes(pDenom, nLevel);
632 }
633 
634 void SmXMLExport::ExportBinaryDiagonal(const SmNode* pNode, int nLevel)
635 {
636     assert(pNode->GetNumSubNodes() == 3);
637 
638     if (pNode->GetToken().eType == TWIDESLASH)
639     {
640         // wideslash
641         // export the node as <mfrac bevelled="true">
642         AddAttribute(XML_NAMESPACE_MATH, XML_BEVELLED, XML_TRUE);
643         SvXMLElementExport aFraction(*this, XML_NAMESPACE_MATH, XML_MFRAC, true, true);
644         ExportNodes(pNode->GetSubNode(0), nLevel);
645         ExportNodes(pNode->GetSubNode(1), nLevel);
646     }
647     else
648     {
649         // widebslash
650         // We can not use <mfrac> to a backslash, so just use <mo>\</mo>
651         SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true);
652 
653         ExportNodes(pNode->GetSubNode(0), nLevel);
654 
655         { // Scoping for <mo> creation
656             SvXMLElementExport aMo(*this, XML_NAMESPACE_MATH, XML_MO, true, true);
657             GetDocHandler()->characters(OUStringChar(MS_BACKSLASH));
658         }
659 
660         ExportNodes(pNode->GetSubNode(1), nLevel);
661     }
662 }
663 
664 void SmXMLExport::ExportTable(const SmNode* pNode, int nLevel)
665 {
666     std::unique_ptr<SvXMLElementExport> pTable;
667 
668     size_t nSize = pNode->GetNumSubNodes();
669 
670     //If the list ends in newline then the last entry has
671     //no subnodes, the newline is superfluous so we just drop
672     //the last node, inclusion would create a bad MathML
673     //table
674     if (nSize >= 1)
675     {
676         const SmNode* pLine = pNode->GetSubNode(nSize - 1);
677         if (pLine->GetType() == SmNodeType::Line && pLine->GetNumSubNodes() == 1
678             && pLine->GetSubNode(0) != nullptr
679             && pLine->GetSubNode(0)->GetToken().eType == TNEWLINE)
680             --nSize;
681     }
682 
683     // try to avoid creating a mtable element when the formula consists only
684     // of a single output line
685     if (nLevel || (nSize > 1))
686         pTable.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTABLE, true, true));
687 
688     for (size_t i = 0; i < nSize; ++i)
689     {
690         if (const SmNode* pTemp = pNode->GetSubNode(i))
691         {
692             std::unique_ptr<SvXMLElementExport> pRow;
693             std::unique_ptr<SvXMLElementExport> pCell;
694             if (pTable)
695             {
696                 pRow.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTR, true, true));
697                 SmTokenType eAlign = TALIGNC;
698                 if (pTemp->GetType() == SmNodeType::Align)
699                 {
700                     // For Binom() and Stack() constructions, the SmNodeType::Align nodes
701                     // are direct children.
702                     // binom{alignl ...}{alignr ...} and
703                     // stack{alignl ... ## alignr ... ## ...}
704                     eAlign = pTemp->GetToken().eType;
705                 }
706                 else if (pTemp->GetType() == SmNodeType::Line && pTemp->GetNumSubNodes() == 1
707                          && pTemp->GetSubNode(0)
708                          && pTemp->GetSubNode(0)->GetType() == SmNodeType::Align)
709                 {
710                     // For the Table() construction, the SmNodeType::Align node is a child
711                     // of an SmNodeType::Line node.
712                     // alignl ... newline alignr ... newline ...
713                     eAlign = pTemp->GetSubNode(0)->GetToken().eType;
714                 }
715                 if (eAlign != TALIGNC)
716                 {
717                     // If a left or right alignment is specified on this line,
718                     // attach the corresponding columnalign attribute.
719                     AddAttribute(XML_NAMESPACE_MATH, XML_COLUMNALIGN,
720                                  eAlign == TALIGNL ? XML_LEFT : XML_RIGHT);
721                 }
722                 pCell.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTD, true, true));
723             }
724             ExportNodes(pTemp, nLevel + 1);
725         }
726     }
727 }
728 
729 void SmXMLExport::ExportMath(const SmNode* pNode)
730 {
731     const SmTextNode* pTemp = static_cast<const SmTextNode*>(pNode);
732     std::unique_ptr<SvXMLElementExport> pMath;
733 
734     if (pNode->GetType() == SmNodeType::Math || pNode->GetType() == SmNodeType::GlyphSpecial)
735     {
736         // Export SmNodeType::Math and SmNodeType::GlyphSpecial symbols as <mo> elements
737         pMath.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MO, true, false));
738     }
739     else if (pNode->GetType() == SmNodeType::Special)
740     {
741         bool bIsItalic = IsItalic(pNode->GetFont());
742         if (!bIsItalic)
743             AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_NORMAL);
744         pMath.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MI, true, false));
745     }
746     else
747     {
748         // Export SmNodeType::MathIdent and SmNodeType::Place symbols as <mi> elements:
749         // - These math symbols should not be drawn slanted. Hence we should
750         // attach a mathvariant="normal" attribute to single-char <mi> elements
751         // that are not mathematical alphanumeric symbol. For simplicity and to
752         // work around browser limitations, we always attach such an attribute.
753         // - The MathML specification suggests to use empty <mi> elements as
754         // placeholders but they won't be visible in most MathML rendering
755         // engines so let's use an empty square for SmNodeType::Place instead.
756         AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_NORMAL);
757         pMath.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MI, true, false));
758     }
759     sal_Unicode nArse = pTemp->GetText()[0];
760     sal_Unicode cTmp = ConvertMathToMathML(nArse);
761     if (cTmp != 0)
762         nArse = cTmp;
763     OSL_ENSURE(nArse != 0xffff, "Non existent symbol");
764     GetDocHandler()->characters(OUString(nArse));
765 }
766 
767 void SmXMLExport::ExportText(const SmNode* pNode)
768 {
769     std::unique_ptr<SvXMLElementExport> pText;
770     const SmTextNode* pTemp = static_cast<const SmTextNode*>(pNode);
771     switch (pNode->GetToken().eType)
772     {
773         default:
774         case TIDENT:
775         {
776             //Note that we change the fontstyle to italic for strings that
777             //are italic and longer than a single character.
778             bool bIsItalic = IsItalic(pTemp->GetFont());
779             if ((pTemp->GetText().getLength() > 1) && bIsItalic)
780                 AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_ITALIC);
781             else if ((pTemp->GetText().getLength() == 1) && !bIsItalic)
782                 AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_NORMAL);
783             pText.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MI, true, false));
784             break;
785         }
786         case TNUMBER:
787             pText.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MN, true, false));
788             break;
789         case TTEXT:
790             pText.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTEXT, true, false));
791             break;
792     }
793     GetDocHandler()->characters(pTemp->GetText());
794 }
795 
796 void SmXMLExport::ExportBlank(const SmNode* pNode)
797 {
798     const SmBlankNode* pTemp = static_cast<const SmBlankNode*>(pNode);
799     //!! exports an <mspace> element. Note that for example "~_~" is allowed in
800     //!! Math (so it has no sense at all) but must not result in an empty
801     //!! <msub> tag in MathML !!
802 
803     if (pTemp->GetBlankNum() != 0)
804     {
805         // Attach a width attribute. We choose the (somewhat arbitrary) values
806         // ".5em" for a small gap '`' and "2em" for a large gap '~'.
807         // (see SmBlankNode::IncreaseBy for how pTemp->mnNum is set).
808         OUStringBuffer sStrBuf;
809         ::sax::Converter::convertDouble(sStrBuf, pTemp->GetBlankNum() * .5);
810         sStrBuf.append("em");
811         AddAttribute(XML_NAMESPACE_MATH, XML_WIDTH, sStrBuf.makeStringAndClear());
812     }
813 
814     SvXMLElementExport aTextExport(*this, XML_NAMESPACE_MATH, XML_MSPACE, true, false);
815 
816     GetDocHandler()->characters(OUString());
817 }
818 
819 void SmXMLExport::ExportSubSupScript(const SmNode* pNode, int nLevel)
820 {
821     const SmNode* pSub = nullptr;
822     const SmNode* pSup = nullptr;
823     const SmNode* pCSub = nullptr;
824     const SmNode* pCSup = nullptr;
825     const SmNode* pLSub = nullptr;
826     const SmNode* pLSup = nullptr;
827     std::unique_ptr<SvXMLElementExport> pThing2;
828 
829     //if we have prescripts at all then we must use the tensor notation
830 
831     //This is one of those excellent locations where scope is vital to
832     //arrange the construction and destruction of the element helper
833     //classes correctly
834     pLSub = pNode->GetSubNode(LSUB + 1);
835     pLSup = pNode->GetSubNode(LSUP + 1);
836     if (pLSub || pLSup)
837     {
838         SvXMLElementExport aMultiScripts(*this, XML_NAMESPACE_MATH, XML_MMULTISCRIPTS, true, true);
839 
840         if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1))
841             && nullptr != (pCSup = pNode->GetSubNode(CSUP + 1)))
842         {
843             pThing2.reset(
844                 new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDEROVER, true, true));
845         }
846         else if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1)))
847         {
848             pThing2.reset(
849                 new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDER, true, true));
850         }
851         else if (nullptr != (pCSup = pNode->GetSubNode(CSUP + 1)))
852         {
853             pThing2.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MOVER, true, true));
854         }
855 
856         ExportNodes(pNode->GetSubNode(0), nLevel + 1); //Main Term
857 
858         if (pCSub)
859             ExportNodes(pCSub, nLevel + 1);
860         if (pCSup)
861             ExportNodes(pCSup, nLevel + 1);
862         pThing2.reset();
863 
864         pSub = pNode->GetSubNode(RSUB + 1);
865         pSup = pNode->GetSubNode(RSUP + 1);
866         if (pSub || pSup)
867         {
868             if (pSub)
869                 ExportNodes(pSub, nLevel + 1);
870             else
871             {
872                 SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true);
873             }
874             if (pSup)
875                 ExportNodes(pSup, nLevel + 1);
876             else
877             {
878                 SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true);
879             }
880         }
881 
882         //Separator element between suffix and prefix sub/sup pairs
883         {
884             SvXMLElementExport aPrescripts(*this, XML_NAMESPACE_MATH, XML_MPRESCRIPTS, true, true);
885         }
886 
887         if (pLSub)
888             ExportNodes(pLSub, nLevel + 1);
889         else
890         {
891             SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true);
892         }
893         if (pLSup)
894             ExportNodes(pLSup, nLevel + 1);
895         else
896         {
897             SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true);
898         }
899     }
900     else
901     {
902         std::unique_ptr<SvXMLElementExport> pThing;
903         if (nullptr != (pSub = pNode->GetSubNode(RSUB + 1))
904             && nullptr != (pSup = pNode->GetSubNode(RSUP + 1)))
905         {
906             pThing.reset(
907                 new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MSUBSUP, true, true));
908         }
909         else if (nullptr != (pSub = pNode->GetSubNode(RSUB + 1)))
910         {
911             pThing.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MSUB, true, true));
912         }
913         else if (nullptr != (pSup = pNode->GetSubNode(RSUP + 1)))
914         {
915             pThing.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MSUP, true, true));
916         }
917 
918         if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1))
919             && nullptr != (pCSup = pNode->GetSubNode(CSUP + 1)))
920         {
921             pThing2.reset(
922                 new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDEROVER, true, true));
923         }
924         else if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1)))
925         {
926             pThing2.reset(
927                 new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDER, true, true));
928         }
929         else if (nullptr != (pCSup = pNode->GetSubNode(CSUP + 1)))
930         {
931             pThing2.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MOVER, true, true));
932         }
933         ExportNodes(pNode->GetSubNode(0), nLevel + 1); //Main Term
934 
935         if (pCSub)
936             ExportNodes(pCSub, nLevel + 1);
937         if (pCSup)
938             ExportNodes(pCSup, nLevel + 1);
939         pThing2.reset();
940 
941         if (pSub)
942             ExportNodes(pSub, nLevel + 1);
943         if (pSup)
944             ExportNodes(pSup, nLevel + 1);
945         pThing.reset();
946     }
947 }
948 
949 void SmXMLExport::ExportBrace(const SmNode* pNode, int nLevel)
950 {
951     const SmNode* pTemp;
952     const SmNode* pLeft = pNode->GetSubNode(0);
953     const SmNode* pRight = pNode->GetSubNode(2);
954 
955     // This used to generate <mfenced> or <mrow>+<mo> elements according to
956     // the stretchiness of fences. The MathML recommendation defines an
957     // <mrow>+<mo> construction that is equivalent to the <mfenced> element:
958     // http://www.w3.org/TR/MathML3/chapter3.html#presm.mfenced
959     // To simplify our code and avoid issues with mfenced implementations in
960     // MathML rendering engines, we now always generate <mrow>+<mo> elements.
961     // See #fdo 66282.
962 
963     // <mrow>
964     SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true);
965 
966     //   <mo fence="true"> opening-fence </mo>
967     if (pLeft && (pLeft->GetToken().eType != TNONE))
968     {
969         AddAttribute(XML_NAMESPACE_MATH, XML_FENCE, XML_TRUE);
970         AddAttribute(XML_NAMESPACE_MATH, XML_FORM, XML_PREFIX);
971         if (pNode->GetScaleMode() == SmScaleMode::Height)
972             AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE);
973         else
974             AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_FALSE);
975         ExportNodes(pLeft, nLevel + 1);
976     }
977 
978     if (nullptr != (pTemp = pNode->GetSubNode(1)))
979     {
980         // <mrow>
981         SvXMLElementExport aRowExport(*this, XML_NAMESPACE_MATH, XML_MROW, true, true);
982         ExportNodes(pTemp, nLevel + 1);
983         // </mrow>
984     }
985 
986     //   <mo fence="true"> closing-fence </mo>
987     if (pRight && (pRight->GetToken().eType != TNONE))
988     {
989         AddAttribute(XML_NAMESPACE_MATH, XML_FENCE, XML_TRUE);
990         AddAttribute(XML_NAMESPACE_MATH, XML_FORM, XML_POSTFIX);
991         if (pNode->GetScaleMode() == SmScaleMode::Height)
992             AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE);
993         else
994             AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_FALSE);
995         ExportNodes(pRight, nLevel + 1);
996     }
997 
998     // </mrow>
999 }
1000 
1001 void SmXMLExport::ExportRoot(const SmNode* pNode, int nLevel)
1002 {
1003     if (pNode->GetSubNode(0))
1004     {
1005         SvXMLElementExport aRoot(*this, XML_NAMESPACE_MATH, XML_MROOT, true, true);
1006         ExportNodes(pNode->GetSubNode(2), nLevel + 1);
1007         ExportNodes(pNode->GetSubNode(0), nLevel + 1);
1008     }
1009     else
1010     {
1011         SvXMLElementExport aSqrt(*this, XML_NAMESPACE_MATH, XML_MSQRT, true, true);
1012         ExportNodes(pNode->GetSubNode(2), nLevel + 1);
1013     }
1014 }
1015 
1016 void SmXMLExport::ExportOperator(const SmNode* pNode, int nLevel)
1017 {
1018     /*we need to either use content or font and size attributes
1019      *here*/
1020     SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true);
1021     ExportNodes(pNode->GetSubNode(0), nLevel + 1);
1022     ExportNodes(pNode->GetSubNode(1), nLevel + 1);
1023 }
1024 
1025 void SmXMLExport::ExportAttributes(const SmNode* pNode, int nLevel)
1026 {
1027     std::unique_ptr<SvXMLElementExport> pElement;
1028 
1029     if (pNode->GetToken().eType == TUNDERLINE)
1030     {
1031         AddAttribute(XML_NAMESPACE_MATH, XML_ACCENTUNDER, XML_TRUE);
1032         pElement.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDER, true, true));
1033     }
1034     else if (pNode->GetToken().eType == TOVERSTRIKE)
1035     {
1036         // export as <menclose notation="horizontalstrike">
1037         AddAttribute(XML_NAMESPACE_MATH, XML_NOTATION, XML_HORIZONTALSTRIKE);
1038         pElement.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MENCLOSE, true, true));
1039     }
1040     else
1041     {
1042         AddAttribute(XML_NAMESPACE_MATH, XML_ACCENT, XML_TRUE);
1043         pElement.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MOVER, true, true));
1044     }
1045 
1046     ExportNodes(pNode->GetSubNode(1), nLevel + 1);
1047     switch (pNode->GetToken().eType)
1048     {
1049         case TOVERLINE:
1050         {
1051             //proper entity support required
1052             SvXMLElementExport aMath(*this, XML_NAMESPACE_MATH, XML_MO, true, true);
1053             static constexpr OUStringLiteral nArse = u"\u00AF";
1054             GetDocHandler()->characters(nArse);
1055         }
1056         break;
1057         case TUNDERLINE:
1058         {
1059             //proper entity support required
1060             SvXMLElementExport aMath(*this, XML_NAMESPACE_MATH, XML_MO, true, true);
1061             static constexpr OUStringLiteral nArse = u"\u0332";
1062             GetDocHandler()->characters(nArse);
1063         }
1064         break;
1065         case TOVERSTRIKE:
1066             break;
1067         case TWIDETILDE:
1068         case TWIDEHAT:
1069         case TWIDEVEC:
1070         case TWIDEHARPOON:
1071         {
1072             // make these wide accents stretchy
1073             AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE);
1074             ExportNodes(pNode->GetSubNode(0), nLevel + 1);
1075         }
1076         break;
1077         default:
1078             ExportNodes(pNode->GetSubNode(0), nLevel + 1);
1079             break;
1080     }
1081 }
1082 
1083 static bool lcl_HasEffectOnMathvariant(const SmTokenType eType)
1084 {
1085     return eType == TBOLD || eType == TNBOLD || eType == TITALIC || eType == TNITALIC
1086            || eType == TSANS || eType == TSERIF || eType == TFIXED;
1087 }
1088 
1089 void SmXMLExport::ExportFont(const SmNode* pNode, int nLevel)
1090 {
1091     // gather the mathvariant attribute relevant data from all
1092     // successively following SmFontNodes...
1093 
1094     int nBold = -1; // for the following variables: -1 = yet undefined; 0 = false; 1 = true;
1095     int nItalic = -1; // for the following variables: -1 = yet undefined; 0 = false; 1 = true;
1096     int nSansSerifFixed = -1;
1097     SmTokenType eNodeType = TUNKNOWN;
1098 
1099     for (;;)
1100     {
1101         eNodeType = pNode->GetToken().eType;
1102         if (!lcl_HasEffectOnMathvariant(eNodeType))
1103             break;
1104         switch (eNodeType)
1105         {
1106             case TBOLD:
1107                 nBold = 1;
1108                 break;
1109             case TNBOLD:
1110                 nBold = 0;
1111                 break;
1112             case TITALIC:
1113                 nItalic = 1;
1114                 break;
1115             case TNITALIC:
1116                 nItalic = 0;
1117                 break;
1118             case TSANS:
1119                 nSansSerifFixed = 0;
1120                 break;
1121             case TSERIF:
1122                 nSansSerifFixed = 1;
1123                 break;
1124             case TFIXED:
1125                 nSansSerifFixed = 2;
1126                 break;
1127             default:
1128                 SAL_WARN("starmath", "unexpected case");
1129         }
1130         // According to the parser every node that is to be evaluated here
1131         // has a single non-zero subnode at index 1!! Thus we only need to check
1132         // that single node for follow-up nodes that have an effect on the attribute.
1133         if (pNode->GetNumSubNodes() > 1 && pNode->GetSubNode(1)
1134             && lcl_HasEffectOnMathvariant(pNode->GetSubNode(1)->GetToken().eType))
1135         {
1136             pNode = pNode->GetSubNode(1);
1137         }
1138         else
1139             break;
1140     }
1141 
1142     sal_uInt32 nc;
1143     switch (pNode->GetToken().eType)
1144     {
1145         case TPHANTOM:
1146             // No attribute needed. An <mphantom> element will be used below.
1147             break;
1148         case TMATHMLCOL:
1149         {
1150             nc = pNode->GetToken().cMathChar.toUInt32(16);
1151             OUString sssStr
1152                 = OUString::createFromAscii(starmathdatabase::Identify_Color_MATHML(nc).pIdent);
1153             AddAttribute(XML_NAMESPACE_MATH, XML_MATHCOLOR, sssStr);
1154         }
1155         break;
1156         case TRGB:
1157         case TRGBA:
1158         case THEX:
1159         case THTMLCOL:
1160         case TDVIPSNAMESCOL:
1161         case TICONICCOL:
1162         {
1163             OUStringBuffer sStrBuf(7);
1164             sStrBuf.append('#');
1165             nc = pNode->GetToken().cMathChar.toUInt32(16);
1166             sStrBuf.append(Color(ColorTransparency, nc).AsRGBHEXString());
1167             OUString ssStr(sStrBuf.makeStringAndClear());
1168             AddAttribute(XML_NAMESPACE_MATH, XML_MATHCOLOR, ssStr);
1169         }
1170         break;
1171         case TSIZE:
1172         {
1173             const SmFontNode* pFontNode = static_cast<const SmFontNode*>(pNode);
1174             const Fraction& aFrac = pFontNode->GetSizeParameter();
1175 
1176             OUStringBuffer sStrBuf;
1177             switch (pFontNode->GetSizeType())
1178             {
1179                 case FontSizeType::MULTIPLY:
1180                     ::sax::Converter::convertDouble(sStrBuf,
1181                                                     static_cast<double>(aFrac * Fraction(100.00)));
1182                     sStrBuf.append('%');
1183                     break;
1184                 case FontSizeType::DIVIDE:
1185                     ::sax::Converter::convertDouble(sStrBuf,
1186                                                     static_cast<double>(Fraction(100.00) / aFrac));
1187                     sStrBuf.append('%');
1188                     break;
1189                 case FontSizeType::ABSOLUT:
1190                     ::sax::Converter::convertDouble(sStrBuf, static_cast<double>(aFrac));
1191                     sStrBuf.append(GetXMLToken(XML_UNIT_PT));
1192                     break;
1193                 default:
1194                 {
1195                     //The problem here is that the wheels fall off because
1196                     //font size is stored in 100th's of a mm not pts, and
1197                     //rounding errors take their toll on the original
1198                     //value specified in points.
1199 
1200                     //Must fix StarMath to retain the original pt values
1201                     Fraction aTemp = Sm100th_mmToPts(pFontNode->GetFont().GetFontSize().Height());
1202 
1203                     if (pFontNode->GetSizeType() == FontSizeType::MINUS)
1204                         aTemp -= aFrac;
1205                     else
1206                         aTemp += aFrac;
1207 
1208                     double mytest = static_cast<double>(aTemp);
1209 
1210                     mytest = ::rtl::math::round(mytest, 1);
1211                     ::sax::Converter::convertDouble(sStrBuf, mytest);
1212                     sStrBuf.append(GetXMLToken(XML_UNIT_PT));
1213                 }
1214                 break;
1215             }
1216 
1217             OUString sStr(sStrBuf.makeStringAndClear());
1218             AddAttribute(XML_NAMESPACE_MATH, XML_MATHSIZE, sStr);
1219         }
1220         break;
1221         case TBOLD:
1222         case TITALIC:
1223         case TNBOLD:
1224         case TNITALIC:
1225         case TFIXED:
1226         case TSANS:
1227         case TSERIF:
1228         {
1229             // nBold:   -1 = yet undefined; 0 = false; 1 = true;
1230             // nItalic: -1 = yet undefined; 0 = false; 1 = true;
1231             // nSansSerifFixed: -1 = undefined; 0 = sans; 1 = serif; 2 = fixed;
1232             const char* pText = "normal";
1233             if (nSansSerifFixed == -1 || nSansSerifFixed == 1)
1234             {
1235                 pText = "normal";
1236                 if (nBold == 1 && nItalic != 1)
1237                     pText = "bold";
1238                 else if (nBold != 1 && nItalic == 1)
1239                     pText = "italic";
1240                 else if (nBold == 1 && nItalic == 1)
1241                     pText = "bold-italic";
1242             }
1243             else if (nSansSerifFixed == 0)
1244             {
1245                 pText = "sans-serif";
1246                 if (nBold == 1 && nItalic != 1)
1247                     pText = "bold-sans-serif";
1248                 else if (nBold != 1 && nItalic == 1)
1249                     pText = "sans-serif-italic";
1250                 else if (nBold == 1 && nItalic == 1)
1251                     pText = "sans-serif-bold-italic";
1252             }
1253             else if (nSansSerifFixed == 2)
1254                 pText = "monospace"; // no modifiers allowed for monospace ...
1255             else
1256             {
1257                 SAL_WARN("starmath", "unexpected case");
1258             }
1259             AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, OUString::createFromAscii(pText));
1260         }
1261         break;
1262         default:
1263             break;
1264     }
1265     {
1266         // Wrap everything in an <mphantom> or <mstyle> element. These elements
1267         // are mrow-like, so ExportExpression doesn't need to add an explicit
1268         // <mrow> element. See #fdo 66283.
1269         SvXMLElementExport aElement(*this, XML_NAMESPACE_MATH,
1270                                     pNode->GetToken().eType == TPHANTOM ? XML_MPHANTOM : XML_MSTYLE,
1271                                     true, true);
1272         ExportExpression(pNode, nLevel, true);
1273     }
1274 }
1275 
1276 void SmXMLExport::ExportVerticalBrace(const SmVerticalBraceNode* pNode, int nLevel)
1277 {
1278     // "[body] overbrace [script]"
1279 
1280     // Position body, overbrace and script vertically. First place the overbrace
1281     // OVER the body and then the script OVER this expression.
1282 
1283     //      [script]
1284     //   --[overbrace]--
1285     // XXXXXX[body]XXXXXXX
1286 
1287     // Similarly for the underbrace construction.
1288 
1289     XMLTokenEnum which;
1290 
1291     switch (pNode->GetToken().eType)
1292     {
1293         case TOVERBRACE:
1294         default:
1295             which = XML_MOVER;
1296             break;
1297         case TUNDERBRACE:
1298             which = XML_MUNDER;
1299             break;
1300     }
1301 
1302     SvXMLElementExport aOver1(*this, XML_NAMESPACE_MATH, which, true, true);
1303     { //Scoping
1304         // using accents will draw the over-/underbraces too close to the base
1305         // see http://www.w3.org/TR/MathML2/chapter3.html#id.3.4.5.2
1306         // also XML_ACCENT is illegal with XML_MUNDER. Thus no XML_ACCENT attribute here!
1307         SvXMLElementExport aOver2(*this, XML_NAMESPACE_MATH, which, true, true);
1308         ExportNodes(pNode->Body(), nLevel);
1309         AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE);
1310         ExportNodes(pNode->Brace(), nLevel);
1311     }
1312     ExportNodes(pNode->Script(), nLevel);
1313 }
1314 
1315 void SmXMLExport::ExportMatrix(const SmNode* pNode, int nLevel)
1316 {
1317     SvXMLElementExport aTable(*this, XML_NAMESPACE_MATH, XML_MTABLE, true, true);
1318     const SmMatrixNode* pMatrix = static_cast<const SmMatrixNode*>(pNode);
1319     size_t i = 0;
1320     for (sal_uInt16 y = 0; y < pMatrix->GetNumRows(); y++)
1321     {
1322         SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MTR, true, true);
1323         for (sal_uInt16 x = 0; x < pMatrix->GetNumCols(); x++)
1324         {
1325             if (const SmNode* pTemp = pNode->GetSubNode(i++))
1326             {
1327                 if (pTemp->GetType() == SmNodeType::Align && pTemp->GetToken().eType != TALIGNC)
1328                 {
1329                     // A left or right alignment is specified on this cell,
1330                     // attach the corresponding columnalign attribute.
1331                     AddAttribute(XML_NAMESPACE_MATH, XML_COLUMNALIGN,
1332                                  pTemp->GetToken().eType == TALIGNL ? XML_LEFT : XML_RIGHT);
1333                 }
1334                 SvXMLElementExport aCell(*this, XML_NAMESPACE_MATH, XML_MTD, true, true);
1335                 ExportNodes(pTemp, nLevel + 1);
1336             }
1337         }
1338     }
1339 }
1340 
1341 void SmXMLExport::ExportNodes(const SmNode* pNode, int nLevel)
1342 {
1343     if (!pNode)
1344         return;
1345     switch (pNode->GetType())
1346     {
1347         case SmNodeType::Table:
1348             ExportTable(pNode, nLevel);
1349             break;
1350         case SmNodeType::Align:
1351         case SmNodeType::Bracebody:
1352         case SmNodeType::Expression:
1353             ExportExpression(pNode, nLevel);
1354             break;
1355         case SmNodeType::Line:
1356             ExportLine(pNode, nLevel);
1357             break;
1358         case SmNodeType::Text:
1359             ExportText(pNode);
1360             break;
1361         case SmNodeType::GlyphSpecial:
1362         case SmNodeType::Math:
1363         {
1364             sal_Unicode cTmp = 0;
1365             const SmTextNode* pTemp = static_cast<const SmTextNode*>(pNode);
1366             if (!pTemp->GetText().isEmpty())
1367                 cTmp = ConvertMathToMathML(pTemp->GetText()[0]);
1368             if (cTmp == 0)
1369             {
1370                 // no conversion to MathML implemented -> export it as text
1371                 // thus at least it will not vanish into nothing
1372                 ExportText(pNode);
1373             }
1374             else
1375             {
1376                 switch (pNode->GetToken().eType)
1377                 {
1378                     case TINTD:
1379                         AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE);
1380                         break;
1381                     default:
1382                         break;
1383                 }
1384                 //To fully handle generic MathML we need to implement the full
1385                 //operator dictionary, we will generate MathML with explicit
1386                 //stretchiness for now.
1387                 sal_Int16 nLength = GetAttrList().getLength();
1388                 bool bAddStretch = true;
1389                 for (sal_Int16 i = 0; i < nLength; i++)
1390                 {
1391                     OUString sLocalName;
1392                     sal_uInt16 nPrefix = GetNamespaceMap().GetKeyByAttrName(
1393                         GetAttrList().getNameByIndex(i), &sLocalName);
1394 
1395                     if ((XML_NAMESPACE_MATH == nPrefix) && IsXMLToken(sLocalName, XML_STRETCHY))
1396                     {
1397                         bAddStretch = false;
1398                         break;
1399                     }
1400                 }
1401                 if (bAddStretch)
1402                 {
1403                     AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_FALSE);
1404                 }
1405                 ExportMath(pNode);
1406             }
1407         }
1408         break;
1409         case SmNodeType::
1410             Special: //SmNodeType::Special requires some sort of Entity preservation in the XML engine.
1411         case SmNodeType::MathIdent:
1412         case SmNodeType::Place:
1413             ExportMath(pNode);
1414             break;
1415         case SmNodeType::BinHor:
1416             ExportBinaryHorizontal(pNode, nLevel);
1417             break;
1418         case SmNodeType::UnHor:
1419             ExportUnaryHorizontal(pNode, nLevel);
1420             break;
1421         case SmNodeType::Brace:
1422             ExportBrace(pNode, nLevel);
1423             break;
1424         case SmNodeType::BinVer:
1425             ExportBinaryVertical(pNode, nLevel);
1426             break;
1427         case SmNodeType::BinDiagonal:
1428             ExportBinaryDiagonal(pNode, nLevel);
1429             break;
1430         case SmNodeType::SubSup:
1431             ExportSubSupScript(pNode, nLevel);
1432             break;
1433         case SmNodeType::Root:
1434             ExportRoot(pNode, nLevel);
1435             break;
1436         case SmNodeType::Oper:
1437             ExportOperator(pNode, nLevel);
1438             break;
1439         case SmNodeType::Attribute:
1440             ExportAttributes(pNode, nLevel);
1441             break;
1442         case SmNodeType::Font:
1443             ExportFont(pNode, nLevel);
1444             break;
1445         case SmNodeType::VerticalBrace:
1446             ExportVerticalBrace(static_cast<const SmVerticalBraceNode*>(pNode), nLevel);
1447             break;
1448         case SmNodeType::Matrix:
1449             ExportMatrix(pNode, nLevel);
1450             break;
1451         case SmNodeType::Blank:
1452             ExportBlank(pNode);
1453             break;
1454         default:
1455             SAL_WARN("starmath", "Warning: failed to export a node?");
1456             break;
1457     }
1458 }
1459 
1460 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1461