diff options
Diffstat (limited to '')
-rw-r--r-- | starmath/source/mathml/mathmlexport.cxx | 1457 |
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: */ |