Coverage Report

Created: 2025-11-16 09:57

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