summaryrefslogtreecommitdiffstats
path: root/starmath/source/mathml/mathmlexport.cxx
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--starmath/source/mathml/mathmlexport.cxx1457
1 files changed, 1457 insertions, 0 deletions
diff --git a/starmath/source/mathml/mathmlexport.cxx b/starmath/source/mathml/mathmlexport.cxx
new file mode 100644
index 000000000..187dd1953
--- /dev/null
+++ b/starmath/source/mathml/mathmlexport.cxx
@@ -0,0 +1,1457 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/*
+ Warning: The SvXMLElementExport helper class creates the beginning and
+ closing tags of xml elements in its constructor and destructor, so there's
+ hidden stuff going on, on occasion the ordering of these classes declarations
+ may be significant
+*/
+
+#include <com/sun/star/xml/sax/Writer.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/embed/ElementModes.hpp>
+#include <com/sun/star/util/MeasureUnit.hpp>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+#include <com/sun/star/uno/Any.h>
+
+#include <officecfg/Office/Common.hxx>
+#include <rtl/math.hxx>
+#include <sfx2/frame.hxx>
+#include <sfx2/docfile.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <osl/diagnose.h>
+#include <sot/storage.hxx>
+#include <svl/itemset.hxx>
+#include <svl/stritem.hxx>
+#include <comphelper/fileformat.h>
+#include <comphelper/processfactory.hxx>
+#include <unotools/streamwrap.hxx>
+#include <sax/tools/converter.hxx>
+#include <xmloff/xmlnamespace.hxx>
+#include <xmloff/xmltoken.hxx>
+#include <xmloff/namespacemap.hxx>
+#include <xmloff/attrlist.hxx>
+#include <comphelper/genericpropertyset.hxx>
+#include <comphelper/servicehelper.hxx>
+#include <comphelper/propertysetinfo.hxx>
+#include <tools/diagnose_ex.h>
+#include <sal/log.hxx>
+
+#include <stack>
+
+#include <mathmlexport.hxx>
+#include <xparsmlbase.hxx>
+#include <strings.hrc>
+#include <smmod.hxx>
+#include <unomodel.hxx>
+#include <document.hxx>
+#include <utility.hxx>
+#include <cfgitem.hxx>
+#include <starmathdatabase.hxx>
+
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::document;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star;
+using namespace ::xmloff::token;
+
+namespace
+{
+bool IsInPrivateUseArea(sal_Unicode cChar) { return 0xE000 <= cChar && cChar <= 0xF8FF; }
+
+sal_Unicode ConvertMathToMathML(sal_Unicode cChar)
+{
+ sal_Unicode cRes = cChar;
+ if (IsInPrivateUseArea(cChar))
+ {
+ SAL_WARN("starmath", "Error: private use area characters should no longer be in use!");
+ cRes = u'@'; // just some character that should easily be notice as odd in the context
+ }
+ return cRes;
+}
+}
+
+bool SmXMLExportWrapper::Export(SfxMedium& rMedium)
+{
+ bool bRet = true;
+ uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
+
+ //Get model
+ uno::Reference<lang::XComponent> xModelComp = xModel;
+
+ bool bEmbedded = false;
+ SmModel* pModel = comphelper::getFromUnoTunnel<SmModel>(xModel);
+
+ SmDocShell* pDocShell = pModel ? static_cast<SmDocShell*>(pModel->GetObjectShell()) : nullptr;
+ if (pDocShell && SfxObjectCreateMode::EMBEDDED == pDocShell->GetCreateMode())
+ bEmbedded = true;
+
+ uno::Reference<task::XStatusIndicator> xStatusIndicator;
+ if (!bEmbedded)
+ {
+ if (pDocShell /*&& pDocShell->GetMedium()*/)
+ {
+ OSL_ENSURE(pDocShell->GetMedium() == &rMedium, "different SfxMedium found");
+
+ SfxItemSet* pSet = rMedium.GetItemSet();
+ if (pSet)
+ {
+ const SfxUnoAnyItem* pItem = pSet->GetItem(SID_PROGRESS_STATUSBAR_CONTROL);
+ if (pItem)
+ pItem->GetValue() >>= xStatusIndicator;
+ }
+ }
+
+ // set progress range and start status indicator
+ if (xStatusIndicator.is())
+ {
+ sal_Int32 nProgressRange = bFlat ? 1 : 3;
+ xStatusIndicator->start(SmResId(STR_STATSTR_WRITING), nProgressRange);
+ }
+ }
+
+ static constexpr OUStringLiteral sUsePrettyPrinting(u"UsePrettyPrinting");
+ static constexpr OUStringLiteral sBaseURI(u"BaseURI");
+ static constexpr OUStringLiteral sStreamRelPath(u"StreamRelPath");
+ static constexpr OUStringLiteral sStreamName(u"StreamName");
+
+ // create XPropertySet with three properties for status indicator
+ static const comphelper::PropertyMapEntry aInfoMap[] = {
+ { sUsePrettyPrinting, 0, cppu::UnoType<bool>::get(), beans::PropertyAttribute::MAYBEVOID,
+ 0 },
+ { sBaseURI, 0, ::cppu::UnoType<OUString>::get(), beans::PropertyAttribute::MAYBEVOID, 0 },
+ { sStreamRelPath, 0, ::cppu::UnoType<OUString>::get(), beans::PropertyAttribute::MAYBEVOID,
+ 0 },
+ { sStreamName, 0, ::cppu::UnoType<OUString>::get(), beans::PropertyAttribute::MAYBEVOID, 0 }
+ };
+ uno::Reference<beans::XPropertySet> xInfoSet(
+ comphelper::GenericPropertySet_CreateInstance(new comphelper::PropertySetInfo(aInfoMap)));
+
+ bool bUsePrettyPrinting
+ = bFlat || officecfg::Office::Common::Save::Document::PrettyPrinting::get();
+ xInfoSet->setPropertyValue(sUsePrettyPrinting, Any(bUsePrettyPrinting));
+
+ // Set base URI
+ xInfoSet->setPropertyValue(sBaseURI, Any(rMedium.GetBaseURL(true)));
+
+ sal_Int32 nSteps = 0;
+ if (xStatusIndicator.is())
+ xStatusIndicator->setValue(nSteps++);
+ if (!bFlat) //Storage (Package) of Stream
+ {
+ uno::Reference<embed::XStorage> xStg = rMedium.GetOutputStorage();
+ bool bOASIS = (SotStorage::GetVersion(xStg) > SOFFICE_FILEFORMAT_60);
+
+ // TODO/LATER: handle the case of embedded links gracefully
+ if (bEmbedded) //&& !pStg->IsRoot() )
+ {
+ OUString aName;
+ if (rMedium.GetItemSet())
+ {
+ const SfxStringItem* pDocHierarchItem
+ = rMedium.GetItemSet()->GetItem(SID_DOC_HIERARCHICALNAME);
+ if (pDocHierarchItem)
+ aName = pDocHierarchItem->GetValue();
+ }
+
+ if (!aName.isEmpty())
+ {
+ xInfoSet->setPropertyValue(sStreamRelPath, Any(aName));
+ }
+ }
+
+ if (!bEmbedded)
+ {
+ if (xStatusIndicator.is())
+ xStatusIndicator->setValue(nSteps++);
+
+ bRet = WriteThroughComponent(xStg, xModelComp, "meta.xml", xContext, xInfoSet,
+ (bOASIS ? "com.sun.star.comp.Math.XMLOasisMetaExporter"
+ : "com.sun.star.comp.Math.XMLMetaExporter"));
+ }
+ if (bRet)
+ {
+ if (xStatusIndicator.is())
+ xStatusIndicator->setValue(nSteps++);
+
+ bRet = WriteThroughComponent(xStg, xModelComp, "content.xml", xContext, xInfoSet,
+ "com.sun.star.comp.Math.XMLContentExporter");
+ }
+
+ if (bRet)
+ {
+ if (xStatusIndicator.is())
+ xStatusIndicator->setValue(nSteps++);
+
+ bRet = WriteThroughComponent(xStg, xModelComp, "settings.xml", xContext, xInfoSet,
+ (bOASIS ? "com.sun.star.comp.Math.XMLOasisSettingsExporter"
+ : "com.sun.star.comp.Math.XMLSettingsExporter"));
+ }
+ }
+ else
+ {
+ SvStream* pStream = rMedium.GetOutStream();
+ uno::Reference<io::XOutputStream> xOut(new utl::OOutputStreamWrapper(*pStream));
+
+ if (xStatusIndicator.is())
+ xStatusIndicator->setValue(nSteps++);
+
+ bRet = WriteThroughComponent(xOut, xModelComp, xContext, xInfoSet,
+ "com.sun.star.comp.Math.XMLContentExporter");
+ }
+
+ if (xStatusIndicator.is())
+ xStatusIndicator->end();
+
+ return bRet;
+}
+
+/// export through an XML exporter component (output stream version)
+bool SmXMLExportWrapper::WriteThroughComponent(const Reference<io::XOutputStream>& xOutputStream,
+ const Reference<XComponent>& xComponent,
+ Reference<uno::XComponentContext> const& rxContext,
+ Reference<beans::XPropertySet> const& rPropSet,
+ const char* pComponentName)
+{
+ OSL_ENSURE(xOutputStream.is(), "I really need an output stream!");
+ OSL_ENSURE(xComponent.is(), "Need component!");
+ OSL_ENSURE(nullptr != pComponentName, "Need component name!");
+
+ // get component
+ Reference<xml::sax::XWriter> xSaxWriter = xml::sax::Writer::create(rxContext);
+
+ // connect XML writer to output stream
+ xSaxWriter->setOutputStream(xOutputStream);
+ if (m_bUseHTMLMLEntities)
+ xSaxWriter->setCustomEntityNames(starmathdatabase::icustomMathmlHtmlEntitiesExport);
+
+ // prepare arguments (prepend doc handler to given arguments)
+ Sequence<Any> aArgs{ Any(xSaxWriter), Any(rPropSet) };
+
+ // get filter component
+ Reference<document::XExporter> xExporter(
+ rxContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ OUString::createFromAscii(pComponentName), aArgs, rxContext),
+ UNO_QUERY);
+ OSL_ENSURE(xExporter.is(), "can't instantiate export filter component");
+ if (!xExporter.is())
+ return false;
+
+ // connect model and filter
+ xExporter->setSourceDocument(xComponent);
+
+ // filter!
+ Reference<XFilter> xFilter(xExporter, UNO_QUERY);
+ uno::Sequence<PropertyValue> aProps(0);
+ xFilter->filter(aProps);
+
+ auto pFilter = comphelper::getFromUnoTunnel<SmXMLExport>(xFilter);
+ return pFilter == nullptr || pFilter->GetSuccess();
+}
+
+/// export through an XML exporter component (storage version)
+bool SmXMLExportWrapper::WriteThroughComponent(const Reference<embed::XStorage>& xStorage,
+ const Reference<XComponent>& xComponent,
+ const char* pStreamName,
+ Reference<uno::XComponentContext> const& rxContext,
+ Reference<beans::XPropertySet> const& rPropSet,
+ const char* pComponentName)
+{
+ OSL_ENSURE(xStorage.is(), "Need storage!");
+ OSL_ENSURE(nullptr != pStreamName, "Need stream name!");
+
+ // open stream
+ Reference<io::XStream> xStream;
+ OUString sStreamName = OUString::createFromAscii(pStreamName);
+ try
+ {
+ xStream = xStorage->openStreamElement(sStreamName, embed::ElementModes::READWRITE
+ | embed::ElementModes::TRUNCATE);
+ }
+ catch (const uno::Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("starmath", "Can't create output stream in package");
+ return false;
+ }
+
+ uno::Reference<beans::XPropertySet> xSet(xStream, uno::UNO_QUERY);
+ static const OUStringLiteral sMediaType = u"MediaType";
+ static const OUStringLiteral sTextXml = u"text/xml";
+ xSet->setPropertyValue(sMediaType, Any(OUString(sTextXml)));
+
+ // all streams must be encrypted in encrypted document
+ static const OUStringLiteral sUseCommonStoragePasswordEncryption
+ = u"UseCommonStoragePasswordEncryption";
+ xSet->setPropertyValue(sUseCommonStoragePasswordEncryption, Any(true));
+
+ // set Base URL
+ if (rPropSet.is())
+ {
+ rPropSet->setPropertyValue("StreamName", Any(sStreamName));
+ }
+
+ // write the stuff
+ bool bRet = WriteThroughComponent(xStream->getOutputStream(), xComponent, rxContext, rPropSet,
+ pComponentName);
+
+ return bRet;
+}
+
+SmXMLExport::SmXMLExport(const css::uno::Reference<css::uno::XComponentContext>& rContext,
+ OUString const& implementationName, SvXMLExportFlags nExportFlags)
+ : SvXMLExport(rContext, implementationName, util::MeasureUnit::INCH, XML_MATH, nExportFlags)
+ , pTree(nullptr)
+ , bSuccess(false)
+{
+}
+
+sal_Int64 SAL_CALL SmXMLExport::getSomething(const uno::Sequence<sal_Int8>& rId)
+{
+ return comphelper::getSomethingImpl(rId, this,
+ comphelper::FallbackToGetSomethingOf<SvXMLExport>{});
+}
+
+const uno::Sequence<sal_Int8>& SmXMLExport::getUnoTunnelId() noexcept
+{
+ static const comphelper::UnoIdInit theSmXMLExportUnoTunnelId;
+ return theSmXMLExportUnoTunnelId.getSeq();
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+Math_XMLExporter_get_implementation(css::uno::XComponentContext* context,
+ css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLExporter",
+ SvXMLExportFlags::OASIS | SvXMLExportFlags::ALL));
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+Math_XMLMetaExporter_get_implementation(css::uno::XComponentContext* context,
+ css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(
+ new SmXMLExport(context, "com.sun.star.comp.Math.XMLMetaExporter", SvXMLExportFlags::META));
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+Math_XMLOasisMetaExporter_get_implementation(css::uno::XComponentContext* context,
+ css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLOasisMetaExporter",
+ SvXMLExportFlags::OASIS | SvXMLExportFlags::META));
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+Math_XMLSettingsExporter_get_implementation(css::uno::XComponentContext* context,
+ css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLSettingsExporter",
+ SvXMLExportFlags::SETTINGS));
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+Math_XMLOasisSettingsExporter_get_implementation(css::uno::XComponentContext* context,
+ css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLOasisSettingsExporter",
+ SvXMLExportFlags::OASIS | SvXMLExportFlags::SETTINGS));
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+Math_XMLContentExporter_get_implementation(css::uno::XComponentContext* context,
+ css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLContentExporter",
+ SvXMLExportFlags::OASIS | SvXMLExportFlags::CONTENT));
+}
+
+ErrCode SmXMLExport::exportDoc(enum XMLTokenEnum eClass)
+{
+ if (!(getExportFlags() & SvXMLExportFlags::CONTENT))
+ {
+ SvXMLExport::exportDoc(eClass);
+ }
+ else
+ {
+ uno::Reference<frame::XModel> xModel = GetModel();
+ SmModel* pModel = comphelper::getFromUnoTunnel<SmModel>(xModel);
+
+ if (pModel)
+ {
+ SmDocShell* pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell());
+ pTree = pDocShell->GetFormulaTree();
+ aText = pDocShell->GetText();
+ }
+
+ GetDocHandler()->startDocument();
+
+ addChaffWhenEncryptedStorage();
+
+ /*Add xmlns line*/
+ SvXMLAttributeList& rList = GetAttrList();
+
+ // make use of a default namespace
+ 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)
+ GetNamespaceMap_().Add(OUString(), GetXMLToken(XML_N_MATH), XML_NAMESPACE_MATH);
+
+ rList.AddAttribute(GetNamespaceMap().GetAttrNameByKey(XML_NAMESPACE_MATH),
+ GetNamespaceMap().GetNameByKey(XML_NAMESPACE_MATH));
+
+ //I think we need something like ImplExportEntities();
+ ExportContent_();
+ GetDocHandler()->endDocument();
+ }
+
+ bSuccess = true;
+ return ERRCODE_NONE;
+}
+
+void SmXMLExport::ExportContent_()
+{
+ uno::Reference<frame::XModel> xModel = GetModel();
+ SmModel* pModel = comphelper::getFromUnoTunnel<SmModel>(xModel);
+ SmDocShell* pDocShell = pModel ? static_cast<SmDocShell*>(pModel->GetObjectShell()) : nullptr;
+ OSL_ENSURE(pDocShell, "doc shell missing");
+
+ if (pDocShell && !pDocShell->GetFormat().IsTextmode())
+ {
+ // If the Math equation is not in text mode, we attach a display="block"
+ // attribute on the <math> root. We don't do anything if it is in
+ // text mode, the default display="inline" value will be used.
+ AddAttribute(XML_NAMESPACE_MATH, XML_DISPLAY, XML_BLOCK);
+ }
+ SvXMLElementExport aEquation(*this, XML_NAMESPACE_MATH, XML_MATH, true, true);
+ std::unique_ptr<SvXMLElementExport> pSemantics;
+
+ if (!aText.isEmpty())
+ {
+ pSemantics.reset(
+ new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_SEMANTICS, true, true));
+ }
+
+ ExportNodes(pTree, 0);
+
+ if (aText.isEmpty())
+ return;
+
+ SmModule* pMod = SM_MOD();
+ sal_uInt16 nSmSyntaxVersion = pMod->GetConfig()->GetDefaultSmSyntaxVersion();
+
+ // Convert symbol names
+ if (pDocShell)
+ {
+ nSmSyntaxVersion = pDocShell->GetSmSyntaxVersion();
+ AbstractSmParser* rParser = pDocShell->GetParser();
+ bool bVal = rParser->IsExportSymbolNames();
+ rParser->SetExportSymbolNames(true);
+ auto pTmpTree = rParser->Parse(aText);
+ aText = rParser->GetText();
+ pTmpTree.reset();
+ rParser->SetExportSymbolNames(bVal);
+ }
+
+ OUStringBuffer sStrBuf(12);
+ sStrBuf.append(u"StarMath ");
+ if (nSmSyntaxVersion == 5)
+ sStrBuf.append(u"5.0");
+ else
+ sStrBuf.append(static_cast<sal_Int32>(nSmSyntaxVersion));
+
+ AddAttribute(XML_NAMESPACE_MATH, XML_ENCODING, sStrBuf.makeStringAndClear());
+ SvXMLElementExport aAnnotation(*this, XML_NAMESPACE_MATH, XML_ANNOTATION, true, false);
+ GetDocHandler()->characters(aText);
+}
+
+void SmXMLExport::GetViewSettings(Sequence<PropertyValue>& aProps)
+{
+ uno::Reference<frame::XModel> xModel = GetModel();
+ if (!xModel.is())
+ return;
+
+ SmModel* pModel = comphelper::getFromUnoTunnel<SmModel>(xModel);
+
+ if (!pModel)
+ return;
+
+ SmDocShell* pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell());
+ if (!pDocShell)
+ return;
+
+ aProps.realloc(4);
+ PropertyValue* pValue = aProps.getArray();
+ sal_Int32 nIndex = 0;
+
+ tools::Rectangle aRect(pDocShell->GetVisArea());
+
+ pValue[nIndex].Name = "ViewAreaTop";
+ pValue[nIndex++].Value <<= aRect.Top();
+
+ pValue[nIndex].Name = "ViewAreaLeft";
+ pValue[nIndex++].Value <<= aRect.Left();
+
+ pValue[nIndex].Name = "ViewAreaWidth";
+ pValue[nIndex++].Value <<= aRect.GetWidth();
+
+ pValue[nIndex].Name = "ViewAreaHeight";
+ pValue[nIndex++].Value <<= aRect.GetHeight();
+}
+
+void SmXMLExport::GetConfigurationSettings(Sequence<PropertyValue>& rProps)
+{
+ Reference<XPropertySet> xProps(GetModel(), UNO_QUERY);
+ if (!xProps.is())
+ return;
+
+ Reference<XPropertySetInfo> xPropertySetInfo = xProps->getPropertySetInfo();
+ if (!xPropertySetInfo.is())
+ return;
+
+ const Sequence<Property> aProps = xPropertySetInfo->getProperties();
+ const sal_Int32 nCount = aProps.getLength();
+ if (!nCount)
+ return;
+
+ rProps.realloc(nCount);
+ SmMathConfig* pConfig = SM_MOD()->GetConfig();
+ const bool bUsedSymbolsOnly = pConfig && pConfig->IsSaveOnlyUsedSymbols();
+
+ std::transform(aProps.begin(), aProps.end(), rProps.getArray(),
+ [bUsedSymbolsOnly, &xProps](const Property& prop) {
+ PropertyValue aRet;
+ if (prop.Name != "Formula" && prop.Name != "BasicLibraries"
+ && prop.Name != "DialogLibraries" && prop.Name != "RuntimeUID")
+ {
+ aRet.Name = prop.Name;
+ OUString aActualName(prop.Name);
+ // handle 'save used symbols only'
+ static constexpr OUStringLiteral sUserDefinedSymbolsInUse
+ = u"UserDefinedSymbolsInUse";
+ if (bUsedSymbolsOnly && prop.Name == "Symbols")
+ aActualName = sUserDefinedSymbolsInUse;
+ aRet.Value = xProps->getPropertyValue(aActualName);
+ }
+ return aRet;
+ });
+}
+
+void SmXMLExport::ExportLine(const SmNode* pNode, int nLevel) { ExportExpression(pNode, nLevel); }
+
+void SmXMLExport::ExportBinaryHorizontal(const SmNode* pNode, int nLevel)
+{
+ TG nGroup = pNode->GetToken().nGroup;
+
+ SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true);
+
+ // Unfold the binary tree structure as long as the nodes are SmBinHorNode
+ // with the same nGroup. This will reduce the number of nested <mrow>
+ // elements e.g. we only need three <mrow> levels to export
+
+ // "a*b*c*d+e*f*g*h+i*j*k*l = a*b*c*d+e*f*g*h+i*j*k*l =
+ // a*b*c*d+e*f*g*h+i*j*k*l = a*b*c*d+e*f*g*h+i*j*k*l"
+
+ // See https://www.libreoffice.org/bugzilla/show_bug.cgi?id=66081
+ ::std::stack<const SmNode*> s;
+ s.push(pNode);
+ while (!s.empty())
+ {
+ const SmNode* node = s.top();
+ s.pop();
+ if (node->GetType() != SmNodeType::BinHor || node->GetToken().nGroup != nGroup)
+ {
+ ExportNodes(node, nLevel + 1);
+ continue;
+ }
+ const SmBinHorNode* binNode = static_cast<const SmBinHorNode*>(node);
+ s.push(binNode->RightOperand());
+ s.push(binNode->Symbol());
+ s.push(binNode->LeftOperand());
+ }
+}
+
+void SmXMLExport::ExportUnaryHorizontal(const SmNode* pNode, int nLevel)
+{
+ ExportExpression(pNode, nLevel);
+}
+
+void SmXMLExport::ExportExpression(const SmNode* pNode, int nLevel,
+ bool bNoMrowContainer /*=false*/)
+{
+ std::unique_ptr<SvXMLElementExport> pRow;
+ size_t nSize = pNode->GetNumSubNodes();
+
+ // #i115443: nodes of type expression always need to be grouped with mrow statement
+ if (!bNoMrowContainer && (nSize > 1 || pNode->GetType() == SmNodeType::Expression))
+ pRow.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MROW, true, true));
+
+ for (size_t i = 0; i < nSize; ++i)
+ {
+ if (const SmNode* pTemp = pNode->GetSubNode(i))
+ ExportNodes(pTemp, nLevel + 1);
+ }
+}
+
+void SmXMLExport::ExportBinaryVertical(const SmNode* pNode, int nLevel)
+{
+ assert(pNode->GetNumSubNodes() == 3);
+ const SmNode* pNum = pNode->GetSubNode(0);
+ const SmNode* pDenom = pNode->GetSubNode(2);
+ if (pNum->GetType() == SmNodeType::Align && pNum->GetToken().eType != TALIGNC)
+ {
+ // A left or right alignment is specified on the numerator:
+ // attach the corresponding numalign attribute.
+ AddAttribute(XML_NAMESPACE_MATH, XML_NUMALIGN,
+ pNum->GetToken().eType == TALIGNL ? XML_LEFT : XML_RIGHT);
+ }
+ if (pDenom->GetType() == SmNodeType::Align && pDenom->GetToken().eType != TALIGNC)
+ {
+ // A left or right alignment is specified on the denominator:
+ // attach the corresponding denomalign attribute.
+ AddAttribute(XML_NAMESPACE_MATH, XML_DENOMALIGN,
+ pDenom->GetToken().eType == TALIGNL ? XML_LEFT : XML_RIGHT);
+ }
+ SvXMLElementExport aFraction(*this, XML_NAMESPACE_MATH, XML_MFRAC, true, true);
+ ExportNodes(pNum, nLevel);
+ ExportNodes(pDenom, nLevel);
+}
+
+void SmXMLExport::ExportBinaryDiagonal(const SmNode* pNode, int nLevel)
+{
+ assert(pNode->GetNumSubNodes() == 3);
+
+ if (pNode->GetToken().eType == TWIDESLASH)
+ {
+ // wideslash
+ // export the node as <mfrac bevelled="true">
+ AddAttribute(XML_NAMESPACE_MATH, XML_BEVELLED, XML_TRUE);
+ SvXMLElementExport aFraction(*this, XML_NAMESPACE_MATH, XML_MFRAC, true, true);
+ ExportNodes(pNode->GetSubNode(0), nLevel);
+ ExportNodes(pNode->GetSubNode(1), nLevel);
+ }
+ else
+ {
+ // widebslash
+ // We can not use <mfrac> to a backslash, so just use <mo>\</mo>
+ SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true);
+
+ ExportNodes(pNode->GetSubNode(0), nLevel);
+
+ { // Scoping for <mo> creation
+ SvXMLElementExport aMo(*this, XML_NAMESPACE_MATH, XML_MO, true, true);
+ GetDocHandler()->characters(OUStringChar(MS_BACKSLASH));
+ }
+
+ ExportNodes(pNode->GetSubNode(1), nLevel);
+ }
+}
+
+void SmXMLExport::ExportTable(const SmNode* pNode, int nLevel)
+{
+ std::unique_ptr<SvXMLElementExport> pTable;
+
+ size_t nSize = pNode->GetNumSubNodes();
+
+ //If the list ends in newline then the last entry has
+ //no subnodes, the newline is superfluous so we just drop
+ //the last node, inclusion would create a bad MathML
+ //table
+ if (nSize >= 1)
+ {
+ const SmNode* pLine = pNode->GetSubNode(nSize - 1);
+ if (pLine->GetType() == SmNodeType::Line && pLine->GetNumSubNodes() == 1
+ && pLine->GetSubNode(0) != nullptr
+ && pLine->GetSubNode(0)->GetToken().eType == TNEWLINE)
+ --nSize;
+ }
+
+ // try to avoid creating a mtable element when the formula consists only
+ // of a single output line
+ if (nLevel || (nSize > 1))
+ pTable.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTABLE, true, true));
+
+ for (size_t i = 0; i < nSize; ++i)
+ {
+ if (const SmNode* pTemp = pNode->GetSubNode(i))
+ {
+ std::unique_ptr<SvXMLElementExport> pRow;
+ std::unique_ptr<SvXMLElementExport> pCell;
+ if (pTable)
+ {
+ pRow.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTR, true, true));
+ SmTokenType eAlign = TALIGNC;
+ if (pTemp->GetType() == SmNodeType::Align)
+ {
+ // For Binom() and Stack() constructions, the SmNodeType::Align nodes
+ // are direct children.
+ // binom{alignl ...}{alignr ...} and
+ // stack{alignl ... ## alignr ... ## ...}
+ eAlign = pTemp->GetToken().eType;
+ }
+ else if (pTemp->GetType() == SmNodeType::Line && pTemp->GetNumSubNodes() == 1
+ && pTemp->GetSubNode(0)
+ && pTemp->GetSubNode(0)->GetType() == SmNodeType::Align)
+ {
+ // For the Table() construction, the SmNodeType::Align node is a child
+ // of an SmNodeType::Line node.
+ // alignl ... newline alignr ... newline ...
+ eAlign = pTemp->GetSubNode(0)->GetToken().eType;
+ }
+ if (eAlign != TALIGNC)
+ {
+ // If a left or right alignment is specified on this line,
+ // attach the corresponding columnalign attribute.
+ AddAttribute(XML_NAMESPACE_MATH, XML_COLUMNALIGN,
+ eAlign == TALIGNL ? XML_LEFT : XML_RIGHT);
+ }
+ pCell.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTD, true, true));
+ }
+ ExportNodes(pTemp, nLevel + 1);
+ }
+ }
+}
+
+void SmXMLExport::ExportMath(const SmNode* pNode)
+{
+ const SmTextNode* pTemp = static_cast<const SmTextNode*>(pNode);
+ std::unique_ptr<SvXMLElementExport> pMath;
+
+ if (pNode->GetType() == SmNodeType::Math || pNode->GetType() == SmNodeType::GlyphSpecial)
+ {
+ // Export SmNodeType::Math and SmNodeType::GlyphSpecial symbols as <mo> elements
+ pMath.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MO, true, false));
+ }
+ else if (pNode->GetType() == SmNodeType::Special)
+ {
+ bool bIsItalic = IsItalic(pNode->GetFont());
+ if (!bIsItalic)
+ AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_NORMAL);
+ pMath.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MI, true, false));
+ }
+ else
+ {
+ // Export SmNodeType::MathIdent and SmNodeType::Place symbols as <mi> elements:
+ // - These math symbols should not be drawn slanted. Hence we should
+ // attach a mathvariant="normal" attribute to single-char <mi> elements
+ // that are not mathematical alphanumeric symbol. For simplicity and to
+ // work around browser limitations, we always attach such an attribute.
+ // - The MathML specification suggests to use empty <mi> elements as
+ // placeholders but they won't be visible in most MathML rendering
+ // engines so let's use an empty square for SmNodeType::Place instead.
+ AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_NORMAL);
+ pMath.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MI, true, false));
+ }
+ sal_Unicode nArse = pTemp->GetText()[0];
+ sal_Unicode cTmp = ConvertMathToMathML(nArse);
+ if (cTmp != 0)
+ nArse = cTmp;
+ OSL_ENSURE(nArse != 0xffff, "Non existent symbol");
+ GetDocHandler()->characters(OUString(nArse));
+}
+
+void SmXMLExport::ExportText(const SmNode* pNode)
+{
+ std::unique_ptr<SvXMLElementExport> pText;
+ const SmTextNode* pTemp = static_cast<const SmTextNode*>(pNode);
+ switch (pNode->GetToken().eType)
+ {
+ default:
+ case TIDENT:
+ {
+ //Note that we change the fontstyle to italic for strings that
+ //are italic and longer than a single character.
+ bool bIsItalic = IsItalic(pTemp->GetFont());
+ if ((pTemp->GetText().getLength() > 1) && bIsItalic)
+ AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_ITALIC);
+ else if ((pTemp->GetText().getLength() == 1) && !bIsItalic)
+ AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_NORMAL);
+ pText.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MI, true, false));
+ break;
+ }
+ case TNUMBER:
+ pText.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MN, true, false));
+ break;
+ case TTEXT:
+ pText.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTEXT, true, false));
+ break;
+ }
+ GetDocHandler()->characters(pTemp->GetText());
+}
+
+void SmXMLExport::ExportBlank(const SmNode* pNode)
+{
+ const SmBlankNode* pTemp = static_cast<const SmBlankNode*>(pNode);
+ //!! exports an <mspace> element. Note that for example "~_~" is allowed in
+ //!! Math (so it has no sense at all) but must not result in an empty
+ //!! <msub> tag in MathML !!
+
+ if (pTemp->GetBlankNum() != 0)
+ {
+ // Attach a width attribute. We choose the (somewhat arbitrary) values
+ // ".5em" for a small gap '`' and "2em" for a large gap '~'.
+ // (see SmBlankNode::IncreaseBy for how pTemp->mnNum is set).
+ OUStringBuffer sStrBuf;
+ ::sax::Converter::convertDouble(sStrBuf, pTemp->GetBlankNum() * .5);
+ sStrBuf.append("em");
+ AddAttribute(XML_NAMESPACE_MATH, XML_WIDTH, sStrBuf.makeStringAndClear());
+ }
+
+ SvXMLElementExport aTextExport(*this, XML_NAMESPACE_MATH, XML_MSPACE, true, false);
+
+ GetDocHandler()->characters(OUString());
+}
+
+void SmXMLExport::ExportSubSupScript(const SmNode* pNode, int nLevel)
+{
+ const SmNode* pSub = nullptr;
+ const SmNode* pSup = nullptr;
+ const SmNode* pCSub = nullptr;
+ const SmNode* pCSup = nullptr;
+ const SmNode* pLSub = nullptr;
+ const SmNode* pLSup = nullptr;
+ std::unique_ptr<SvXMLElementExport> pThing2;
+
+ //if we have prescripts at all then we must use the tensor notation
+
+ //This is one of those excellent locations where scope is vital to
+ //arrange the construction and destruction of the element helper
+ //classes correctly
+ pLSub = pNode->GetSubNode(LSUB + 1);
+ pLSup = pNode->GetSubNode(LSUP + 1);
+ if (pLSub || pLSup)
+ {
+ SvXMLElementExport aMultiScripts(*this, XML_NAMESPACE_MATH, XML_MMULTISCRIPTS, true, true);
+
+ if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1))
+ && nullptr != (pCSup = pNode->GetSubNode(CSUP + 1)))
+ {
+ pThing2.reset(
+ new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDEROVER, true, true));
+ }
+ else if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1)))
+ {
+ pThing2.reset(
+ new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDER, true, true));
+ }
+ else if (nullptr != (pCSup = pNode->GetSubNode(CSUP + 1)))
+ {
+ pThing2.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MOVER, true, true));
+ }
+
+ ExportNodes(pNode->GetSubNode(0), nLevel + 1); //Main Term
+
+ if (pCSub)
+ ExportNodes(pCSub, nLevel + 1);
+ if (pCSup)
+ ExportNodes(pCSup, nLevel + 1);
+ pThing2.reset();
+
+ pSub = pNode->GetSubNode(RSUB + 1);
+ pSup = pNode->GetSubNode(RSUP + 1);
+ if (pSub || pSup)
+ {
+ if (pSub)
+ ExportNodes(pSub, nLevel + 1);
+ else
+ {
+ SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true);
+ }
+ if (pSup)
+ ExportNodes(pSup, nLevel + 1);
+ else
+ {
+ SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true);
+ }
+ }
+
+ //Separator element between suffix and prefix sub/sup pairs
+ {
+ SvXMLElementExport aPrescripts(*this, XML_NAMESPACE_MATH, XML_MPRESCRIPTS, true, true);
+ }
+
+ if (pLSub)
+ ExportNodes(pLSub, nLevel + 1);
+ else
+ {
+ SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true);
+ }
+ if (pLSup)
+ ExportNodes(pLSup, nLevel + 1);
+ else
+ {
+ SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true);
+ }
+ }
+ else
+ {
+ std::unique_ptr<SvXMLElementExport> pThing;
+ if (nullptr != (pSub = pNode->GetSubNode(RSUB + 1))
+ && nullptr != (pSup = pNode->GetSubNode(RSUP + 1)))
+ {
+ pThing.reset(
+ new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MSUBSUP, true, true));
+ }
+ else if (nullptr != (pSub = pNode->GetSubNode(RSUB + 1)))
+ {
+ pThing.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MSUB, true, true));
+ }
+ else if (nullptr != (pSup = pNode->GetSubNode(RSUP + 1)))
+ {
+ pThing.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MSUP, true, true));
+ }
+
+ if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1))
+ && nullptr != (pCSup = pNode->GetSubNode(CSUP + 1)))
+ {
+ pThing2.reset(
+ new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDEROVER, true, true));
+ }
+ else if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1)))
+ {
+ pThing2.reset(
+ new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDER, true, true));
+ }
+ else if (nullptr != (pCSup = pNode->GetSubNode(CSUP + 1)))
+ {
+ pThing2.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MOVER, true, true));
+ }
+ ExportNodes(pNode->GetSubNode(0), nLevel + 1); //Main Term
+
+ if (pCSub)
+ ExportNodes(pCSub, nLevel + 1);
+ if (pCSup)
+ ExportNodes(pCSup, nLevel + 1);
+ pThing2.reset();
+
+ if (pSub)
+ ExportNodes(pSub, nLevel + 1);
+ if (pSup)
+ ExportNodes(pSup, nLevel + 1);
+ pThing.reset();
+ }
+}
+
+void SmXMLExport::ExportBrace(const SmNode* pNode, int nLevel)
+{
+ const SmNode* pTemp;
+ const SmNode* pLeft = pNode->GetSubNode(0);
+ const SmNode* pRight = pNode->GetSubNode(2);
+
+ // This used to generate <mfenced> or <mrow>+<mo> elements according to
+ // the stretchiness of fences. The MathML recommendation defines an
+ // <mrow>+<mo> construction that is equivalent to the <mfenced> element:
+ // http://www.w3.org/TR/MathML3/chapter3.html#presm.mfenced
+ // To simplify our code and avoid issues with mfenced implementations in
+ // MathML rendering engines, we now always generate <mrow>+<mo> elements.
+ // See #fdo 66282.
+
+ // <mrow>
+ SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true);
+
+ // <mo fence="true"> opening-fence </mo>
+ if (pLeft && (pLeft->GetToken().eType != TNONE))
+ {
+ AddAttribute(XML_NAMESPACE_MATH, XML_FENCE, XML_TRUE);
+ AddAttribute(XML_NAMESPACE_MATH, XML_FORM, XML_PREFIX);
+ if (pNode->GetScaleMode() == SmScaleMode::Height)
+ AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE);
+ else
+ AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_FALSE);
+ ExportNodes(pLeft, nLevel + 1);
+ }
+
+ if (nullptr != (pTemp = pNode->GetSubNode(1)))
+ {
+ // <mrow>
+ SvXMLElementExport aRowExport(*this, XML_NAMESPACE_MATH, XML_MROW, true, true);
+ ExportNodes(pTemp, nLevel + 1);
+ // </mrow>
+ }
+
+ // <mo fence="true"> closing-fence </mo>
+ if (pRight && (pRight->GetToken().eType != TNONE))
+ {
+ AddAttribute(XML_NAMESPACE_MATH, XML_FENCE, XML_TRUE);
+ AddAttribute(XML_NAMESPACE_MATH, XML_FORM, XML_POSTFIX);
+ if (pNode->GetScaleMode() == SmScaleMode::Height)
+ AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE);
+ else
+ AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_FALSE);
+ ExportNodes(pRight, nLevel + 1);
+ }
+
+ // </mrow>
+}
+
+void SmXMLExport::ExportRoot(const SmNode* pNode, int nLevel)
+{
+ if (pNode->GetSubNode(0))
+ {
+ SvXMLElementExport aRoot(*this, XML_NAMESPACE_MATH, XML_MROOT, true, true);
+ ExportNodes(pNode->GetSubNode(2), nLevel + 1);
+ ExportNodes(pNode->GetSubNode(0), nLevel + 1);
+ }
+ else
+ {
+ SvXMLElementExport aSqrt(*this, XML_NAMESPACE_MATH, XML_MSQRT, true, true);
+ ExportNodes(pNode->GetSubNode(2), nLevel + 1);
+ }
+}
+
+void SmXMLExport::ExportOperator(const SmNode* pNode, int nLevel)
+{
+ /*we need to either use content or font and size attributes
+ *here*/
+ SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true);
+ ExportNodes(pNode->GetSubNode(0), nLevel + 1);
+ ExportNodes(pNode->GetSubNode(1), nLevel + 1);
+}
+
+void SmXMLExport::ExportAttributes(const SmNode* pNode, int nLevel)
+{
+ std::unique_ptr<SvXMLElementExport> pElement;
+
+ if (pNode->GetToken().eType == TUNDERLINE)
+ {
+ AddAttribute(XML_NAMESPACE_MATH, XML_ACCENTUNDER, XML_TRUE);
+ pElement.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDER, true, true));
+ }
+ else if (pNode->GetToken().eType == TOVERSTRIKE)
+ {
+ // export as <menclose notation="horizontalstrike">
+ AddAttribute(XML_NAMESPACE_MATH, XML_NOTATION, XML_HORIZONTALSTRIKE);
+ pElement.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MENCLOSE, true, true));
+ }
+ else
+ {
+ AddAttribute(XML_NAMESPACE_MATH, XML_ACCENT, XML_TRUE);
+ pElement.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MOVER, true, true));
+ }
+
+ ExportNodes(pNode->GetSubNode(1), nLevel + 1);
+ switch (pNode->GetToken().eType)
+ {
+ case TOVERLINE:
+ {
+ //proper entity support required
+ SvXMLElementExport aMath(*this, XML_NAMESPACE_MATH, XML_MO, true, true);
+ static constexpr OUStringLiteral nArse = u"\u00AF";
+ GetDocHandler()->characters(nArse);
+ }
+ break;
+ case TUNDERLINE:
+ {
+ //proper entity support required
+ SvXMLElementExport aMath(*this, XML_NAMESPACE_MATH, XML_MO, true, true);
+ static constexpr OUStringLiteral nArse = u"\u0332";
+ GetDocHandler()->characters(nArse);
+ }
+ break;
+ case TOVERSTRIKE:
+ break;
+ case TWIDETILDE:
+ case TWIDEHAT:
+ case TWIDEVEC:
+ case TWIDEHARPOON:
+ {
+ // make these wide accents stretchy
+ AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE);
+ ExportNodes(pNode->GetSubNode(0), nLevel + 1);
+ }
+ break;
+ default:
+ ExportNodes(pNode->GetSubNode(0), nLevel + 1);
+ break;
+ }
+}
+
+static bool lcl_HasEffectOnMathvariant(const SmTokenType eType)
+{
+ return eType == TBOLD || eType == TNBOLD || eType == TITALIC || eType == TNITALIC
+ || eType == TSANS || eType == TSERIF || eType == TFIXED;
+}
+
+void SmXMLExport::ExportFont(const SmNode* pNode, int nLevel)
+{
+ // gather the mathvariant attribute relevant data from all
+ // successively following SmFontNodes...
+
+ int nBold = -1; // for the following variables: -1 = yet undefined; 0 = false; 1 = true;
+ int nItalic = -1; // for the following variables: -1 = yet undefined; 0 = false; 1 = true;
+ int nSansSerifFixed = -1;
+ SmTokenType eNodeType = TUNKNOWN;
+
+ for (;;)
+ {
+ eNodeType = pNode->GetToken().eType;
+ if (!lcl_HasEffectOnMathvariant(eNodeType))
+ break;
+ switch (eNodeType)
+ {
+ case TBOLD:
+ nBold = 1;
+ break;
+ case TNBOLD:
+ nBold = 0;
+ break;
+ case TITALIC:
+ nItalic = 1;
+ break;
+ case TNITALIC:
+ nItalic = 0;
+ break;
+ case TSANS:
+ nSansSerifFixed = 0;
+ break;
+ case TSERIF:
+ nSansSerifFixed = 1;
+ break;
+ case TFIXED:
+ nSansSerifFixed = 2;
+ break;
+ default:
+ SAL_WARN("starmath", "unexpected case");
+ }
+ // According to the parser every node that is to be evaluated here
+ // has a single non-zero subnode at index 1!! Thus we only need to check
+ // that single node for follow-up nodes that have an effect on the attribute.
+ if (pNode->GetNumSubNodes() > 1 && pNode->GetSubNode(1)
+ && lcl_HasEffectOnMathvariant(pNode->GetSubNode(1)->GetToken().eType))
+ {
+ pNode = pNode->GetSubNode(1);
+ }
+ else
+ break;
+ }
+
+ sal_uInt32 nc;
+ switch (pNode->GetToken().eType)
+ {
+ case TPHANTOM:
+ // No attribute needed. An <mphantom> element will be used below.
+ break;
+ case TMATHMLCOL:
+ {
+ nc = pNode->GetToken().cMathChar.toUInt32(16);
+ const OUString& sssStr = starmathdatabase::Identify_Color_MATHML(nc).aIdent;
+ AddAttribute(XML_NAMESPACE_MATH, XML_MATHCOLOR, sssStr);
+ }
+ break;
+ case TRGB:
+ case TRGBA:
+ case THEX:
+ case THTMLCOL:
+ case TDVIPSNAMESCOL:
+ case TICONICCOL:
+ {
+ nc = pNode->GetToken().cMathChar.toUInt32(16);
+ OUString ssStr("#" + Color(ColorTransparency, nc).AsRGBHEXString());
+ AddAttribute(XML_NAMESPACE_MATH, XML_MATHCOLOR, ssStr);
+ }
+ break;
+ case TSIZE:
+ {
+ const SmFontNode* pFontNode = static_cast<const SmFontNode*>(pNode);
+ const Fraction& aFrac = pFontNode->GetSizeParameter();
+
+ OUStringBuffer sStrBuf;
+ switch (pFontNode->GetSizeType())
+ {
+ case FontSizeType::MULTIPLY:
+ ::sax::Converter::convertDouble(sStrBuf,
+ static_cast<double>(aFrac * Fraction(100.00)));
+ sStrBuf.append('%');
+ break;
+ case FontSizeType::DIVIDE:
+ ::sax::Converter::convertDouble(sStrBuf,
+ static_cast<double>(Fraction(100.00) / aFrac));
+ sStrBuf.append('%');
+ break;
+ case FontSizeType::ABSOLUT:
+ ::sax::Converter::convertDouble(sStrBuf, static_cast<double>(aFrac));
+ sStrBuf.append(GetXMLToken(XML_UNIT_PT));
+ break;
+ default:
+ {
+ //The problem here is that the wheels fall off because
+ //font size is stored in 100th's of a mm not pts, and
+ //rounding errors take their toll on the original
+ //value specified in points.
+
+ //Must fix StarMath to retain the original pt values
+ Fraction aTemp = Sm100th_mmToPts(pFontNode->GetFont().GetFontSize().Height());
+
+ if (pFontNode->GetSizeType() == FontSizeType::MINUS)
+ aTemp -= aFrac;
+ else
+ aTemp += aFrac;
+
+ double mytest = static_cast<double>(aTemp);
+
+ mytest = ::rtl::math::round(mytest, 1);
+ ::sax::Converter::convertDouble(sStrBuf, mytest);
+ sStrBuf.append(GetXMLToken(XML_UNIT_PT));
+ }
+ break;
+ }
+
+ OUString sStr(sStrBuf.makeStringAndClear());
+ AddAttribute(XML_NAMESPACE_MATH, XML_MATHSIZE, sStr);
+ }
+ break;
+ case TBOLD:
+ case TITALIC:
+ case TNBOLD:
+ case TNITALIC:
+ case TFIXED:
+ case TSANS:
+ case TSERIF:
+ {
+ // nBold: -1 = yet undefined; 0 = false; 1 = true;
+ // nItalic: -1 = yet undefined; 0 = false; 1 = true;
+ // nSansSerifFixed: -1 = undefined; 0 = sans; 1 = serif; 2 = fixed;
+ const char* pText = "normal";
+ if (nSansSerifFixed == -1 || nSansSerifFixed == 1)
+ {
+ pText = "normal";
+ if (nBold == 1 && nItalic != 1)
+ pText = "bold";
+ else if (nBold != 1 && nItalic == 1)
+ pText = "italic";
+ else if (nBold == 1 && nItalic == 1)
+ pText = "bold-italic";
+ }
+ else if (nSansSerifFixed == 0)
+ {
+ pText = "sans-serif";
+ if (nBold == 1 && nItalic != 1)
+ pText = "bold-sans-serif";
+ else if (nBold != 1 && nItalic == 1)
+ pText = "sans-serif-italic";
+ else if (nBold == 1 && nItalic == 1)
+ pText = "sans-serif-bold-italic";
+ }
+ else if (nSansSerifFixed == 2)
+ pText = "monospace"; // no modifiers allowed for monospace ...
+ else
+ {
+ SAL_WARN("starmath", "unexpected case");
+ }
+ AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, OUString::createFromAscii(pText));
+ }
+ break;
+ default:
+ break;
+ }
+ {
+ // Wrap everything in an <mphantom> or <mstyle> element. These elements
+ // are mrow-like, so ExportExpression doesn't need to add an explicit
+ // <mrow> element. See #fdo 66283.
+ SvXMLElementExport aElement(*this, XML_NAMESPACE_MATH,
+ pNode->GetToken().eType == TPHANTOM ? XML_MPHANTOM : XML_MSTYLE,
+ true, true);
+ ExportExpression(pNode, nLevel, true);
+ }
+}
+
+void SmXMLExport::ExportVerticalBrace(const SmVerticalBraceNode* pNode, int nLevel)
+{
+ // "[body] overbrace [script]"
+
+ // Position body, overbrace and script vertically. First place the overbrace
+ // OVER the body and then the script OVER this expression.
+
+ // [script]
+ // --[overbrace]--
+ // XXXXXX[body]XXXXXXX
+
+ // Similarly for the underbrace construction.
+
+ XMLTokenEnum which;
+
+ switch (pNode->GetToken().eType)
+ {
+ case TOVERBRACE:
+ default:
+ which = XML_MOVER;
+ break;
+ case TUNDERBRACE:
+ which = XML_MUNDER;
+ break;
+ }
+
+ SvXMLElementExport aOver1(*this, XML_NAMESPACE_MATH, which, true, true);
+ { //Scoping
+ // using accents will draw the over-/underbraces too close to the base
+ // see http://www.w3.org/TR/MathML2/chapter3.html#id.3.4.5.2
+ // also XML_ACCENT is illegal with XML_MUNDER. Thus no XML_ACCENT attribute here!
+ SvXMLElementExport aOver2(*this, XML_NAMESPACE_MATH, which, true, true);
+ ExportNodes(pNode->Body(), nLevel);
+ AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE);
+ ExportNodes(pNode->Brace(), nLevel);
+ }
+ ExportNodes(pNode->Script(), nLevel);
+}
+
+void SmXMLExport::ExportMatrix(const SmNode* pNode, int nLevel)
+{
+ SvXMLElementExport aTable(*this, XML_NAMESPACE_MATH, XML_MTABLE, true, true);
+ const SmMatrixNode* pMatrix = static_cast<const SmMatrixNode*>(pNode);
+ size_t i = 0;
+ for (sal_uInt16 y = 0; y < pMatrix->GetNumRows(); y++)
+ {
+ SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MTR, true, true);
+ for (sal_uInt16 x = 0; x < pMatrix->GetNumCols(); x++)
+ {
+ if (const SmNode* pTemp = pNode->GetSubNode(i++))
+ {
+ if (pTemp->GetType() == SmNodeType::Align && pTemp->GetToken().eType != TALIGNC)
+ {
+ // A left or right alignment is specified on this cell,
+ // attach the corresponding columnalign attribute.
+ AddAttribute(XML_NAMESPACE_MATH, XML_COLUMNALIGN,
+ pTemp->GetToken().eType == TALIGNL ? XML_LEFT : XML_RIGHT);
+ }
+ SvXMLElementExport aCell(*this, XML_NAMESPACE_MATH, XML_MTD, true, true);
+ ExportNodes(pTemp, nLevel + 1);
+ }
+ }
+ }
+}
+
+void SmXMLExport::ExportNodes(const SmNode* pNode, int nLevel)
+{
+ if (!pNode)
+ return;
+ switch (pNode->GetType())
+ {
+ case SmNodeType::Table:
+ ExportTable(pNode, nLevel);
+ break;
+ case SmNodeType::Align:
+ case SmNodeType::Bracebody:
+ case SmNodeType::Expression:
+ ExportExpression(pNode, nLevel);
+ break;
+ case SmNodeType::Line:
+ ExportLine(pNode, nLevel);
+ break;
+ case SmNodeType::Text:
+ ExportText(pNode);
+ break;
+ case SmNodeType::GlyphSpecial:
+ case SmNodeType::Math:
+ {
+ sal_Unicode cTmp = 0;
+ const SmTextNode* pTemp = static_cast<const SmTextNode*>(pNode);
+ if (!pTemp->GetText().isEmpty())
+ cTmp = ConvertMathToMathML(pTemp->GetText()[0]);
+ if (cTmp == 0)
+ {
+ // no conversion to MathML implemented -> export it as text
+ // thus at least it will not vanish into nothing
+ ExportText(pNode);
+ }
+ else
+ {
+ switch (pNode->GetToken().eType)
+ {
+ case TINTD:
+ AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE);
+ break;
+ default:
+ break;
+ }
+ //To fully handle generic MathML we need to implement the full
+ //operator dictionary, we will generate MathML with explicit
+ //stretchiness for now.
+ sal_Int16 nLength = GetAttrList().getLength();
+ bool bAddStretch = true;
+ for (sal_Int16 i = 0; i < nLength; i++)
+ {
+ OUString sLocalName;
+ sal_uInt16 nPrefix = GetNamespaceMap().GetKeyByAttrName(
+ GetAttrList().getNameByIndex(i), &sLocalName);
+
+ if ((XML_NAMESPACE_MATH == nPrefix) && IsXMLToken(sLocalName, XML_STRETCHY))
+ {
+ bAddStretch = false;
+ break;
+ }
+ }
+ if (bAddStretch)
+ {
+ AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_FALSE);
+ }
+ ExportMath(pNode);
+ }
+ }
+ break;
+ case SmNodeType::
+ Special: //SmNodeType::Special requires some sort of Entity preservation in the XML engine.
+ case SmNodeType::MathIdent:
+ case SmNodeType::Place:
+ ExportMath(pNode);
+ break;
+ case SmNodeType::BinHor:
+ ExportBinaryHorizontal(pNode, nLevel);
+ break;
+ case SmNodeType::UnHor:
+ ExportUnaryHorizontal(pNode, nLevel);
+ break;
+ case SmNodeType::Brace:
+ ExportBrace(pNode, nLevel);
+ break;
+ case SmNodeType::BinVer:
+ ExportBinaryVertical(pNode, nLevel);
+ break;
+ case SmNodeType::BinDiagonal:
+ ExportBinaryDiagonal(pNode, nLevel);
+ break;
+ case SmNodeType::SubSup:
+ ExportSubSupScript(pNode, nLevel);
+ break;
+ case SmNodeType::Root:
+ ExportRoot(pNode, nLevel);
+ break;
+ case SmNodeType::Oper:
+ ExportOperator(pNode, nLevel);
+ break;
+ case SmNodeType::Attribute:
+ ExportAttributes(pNode, nLevel);
+ break;
+ case SmNodeType::Font:
+ ExportFont(pNode, nLevel);
+ break;
+ case SmNodeType::VerticalBrace:
+ ExportVerticalBrace(static_cast<const SmVerticalBraceNode*>(pNode), nLevel);
+ break;
+ case SmNodeType::Matrix:
+ ExportMatrix(pNode, nLevel);
+ break;
+ case SmNodeType::Blank:
+ ExportBlank(pNode);
+ break;
+ default:
+ SAL_WARN("starmath", "Warning: failed to export a node?");
+ break;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */