/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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_uInt32 cChar) { return 0xE000 <= cChar && cChar <= 0xF8FF; } sal_uInt32 ConvertMathToMathML(std::u16string_view rText, sal_Int32 nIndex = 0) { auto cRes = o3tl::iterateCodePoints(rText, &nIndex); if (IsInPrivateUseArea(cRes)) { 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 xContext(comphelper::getProcessComponentContext()); //Get model uno::Reference xModelComp = xModel; bool bEmbedded = false; SmModel* pModel = comphelper::getFromUnoTunnel(xModel); SmDocShell* pDocShell = pModel ? static_cast(pModel->GetObjectShell()) : nullptr; if (pDocShell && SfxObjectCreateMode::EMBEDDED == pDocShell->GetCreateMode()) bEmbedded = true; uno::Reference xStatusIndicator; if (!bEmbedded) { if (pDocShell /*&& pDocShell->GetMedium()*/) { OSL_ENSURE(pDocShell->GetMedium() == &rMedium, "different SfxMedium found"); const SfxUnoAnyItem* pItem = rMedium.GetItemSet().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 OUString sUsePrettyPrinting(u"UsePrettyPrinting"_ustr); static constexpr OUString sBaseURI(u"BaseURI"_ustr); static constexpr OUString sStreamRelPath(u"StreamRelPath"_ustr); static constexpr OUString sStreamName(u"StreamName"_ustr); // create XPropertySet with three properties for status indicator static const comphelper::PropertyMapEntry aInfoMap[] = { { sUsePrettyPrinting, 0, cppu::UnoType::get(), beans::PropertyAttribute::MAYBEVOID, 0 }, { sBaseURI, 0, ::cppu::UnoType::get(), beans::PropertyAttribute::MAYBEVOID, 0 }, { sStreamRelPath, 0, ::cppu::UnoType::get(), beans::PropertyAttribute::MAYBEVOID, 0 }, { sStreamName, 0, ::cppu::UnoType::get(), beans::PropertyAttribute::MAYBEVOID, 0 } }; uno::Reference 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 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; 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 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& xOutputStream, const Reference& xComponent, Reference const& rxContext, Reference 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 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 aArgs{ Any(xSaxWriter), Any(rPropSet) }; // get filter component Reference 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(xExporter, UNO_QUERY); uno::Sequence aProps(0); xFilter->filter(aProps); auto pFilter = dynamic_cast(xFilter.get()); return pFilter == nullptr || pFilter->GetSuccess(); } /// export through an XML exporter component (storage version) bool SmXMLExportWrapper::WriteThroughComponent(const Reference& xStorage, const Reference& xComponent, const char* pStreamName, Reference const& rxContext, Reference const& rPropSet, const char* pComponentName) { OSL_ENSURE(xStorage.is(), "Need storage!"); OSL_ENSURE(nullptr != pStreamName, "Need stream name!"); // open stream Reference 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 xSet(xStream, uno::UNO_QUERY); static constexpr OUStringLiteral sMediaType = u"MediaType"; static constexpr OUStringLiteral sTextXml = u"text/xml"; xSet->setPropertyValue(sMediaType, Any(OUString(sTextXml))); // all streams must be encrypted in encrypted document static constexpr OUStringLiteral sUseCommonStoragePasswordEncryption = u"UseCommonStoragePasswordEncryption"; xSet->setPropertyValue(sUseCommonStoragePasswordEncryption, Any(true)); // set Base URL if (rPropSet.is()) { rPropSet->setPropertyValue(u"StreamName"_ustr, Any(sStreamName)); } // write the stuff bool bRet = WriteThroughComponent(xStream->getOutputStream(), xComponent, rxContext, rPropSet, pComponentName); return bRet; } SmXMLExport::SmXMLExport(const css::uno::Reference& rContext, OUString const& implementationName, SvXMLExportFlags nExportFlags) : SvXMLExport(rContext, implementationName, util::MeasureUnit::INCH, XML_MATH, nExportFlags) , pTree(nullptr) , bSuccess(false) { } extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* Math_XMLExporter_get_implementation(css::uno::XComponentContext* context, css::uno::Sequence const&) { return cppu::acquire(new SmXMLExport(context, u"com.sun.star.comp.Math.XMLExporter"_ustr, SvXMLExportFlags::OASIS | SvXMLExportFlags::ALL)); } extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* Math_XMLMetaExporter_get_implementation(css::uno::XComponentContext* context, css::uno::Sequence const&) { return cppu::acquire(new SmXMLExport(context, u"com.sun.star.comp.Math.XMLMetaExporter"_ustr, SvXMLExportFlags::META)); } extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* Math_XMLOasisMetaExporter_get_implementation(css::uno::XComponentContext* context, css::uno::Sequence const&) { return cppu::acquire(new SmXMLExport(context, u"com.sun.star.comp.Math.XMLOasisMetaExporter"_ustr, SvXMLExportFlags::OASIS | SvXMLExportFlags::META)); } extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* Math_XMLSettingsExporter_get_implementation(css::uno::XComponentContext* context, css::uno::Sequence const&) { return cppu::acquire(new SmXMLExport( context, u"com.sun.star.comp.Math.XMLSettingsExporter"_ustr, SvXMLExportFlags::SETTINGS)); } extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* Math_XMLOasisSettingsExporter_get_implementation(css::uno::XComponentContext* context, css::uno::Sequence const&) { return cppu::acquire(new SmXMLExport(context, u"com.sun.star.comp.Math.XMLOasisSettingsExporter"_ustr, SvXMLExportFlags::OASIS | SvXMLExportFlags::SETTINGS)); } extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* Math_XMLContentExporter_get_implementation(css::uno::XComponentContext* context, css::uno::Sequence const&) { return cppu::acquire(new SmXMLExport(context, u"com.sun.star.comp.Math.XMLContentExporter"_ustr, SvXMLExportFlags::OASIS | SvXMLExportFlags::CONTENT)); } ErrCode SmXMLExport::exportDoc(enum XMLTokenEnum eClass) { if (!(getExportFlags() & SvXMLExportFlags::CONTENT)) { SvXMLExport::exportDoc(eClass); } else { uno::Reference xModel = GetModel(); SmModel* pModel = comphelper::getFromUnoTunnel(xModel); if (pModel) { SmDocShell* pDocShell = static_cast(pModel->GetObjectShell()); pTree = pDocShell->GetFormulaTree(); aText = pDocShell->GetText(); } GetDocHandler()->startDocument(); addChaffWhenEncryptedStorage(); /*Add xmlns line*/ comphelper::AttributeList& 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 xModel = GetModel(); SmModel* pModel = comphelper::getFromUnoTunnel(xModel); SmDocShell* pDocShell = pModel ? static_cast(pModel->GetObjectShell()) : nullptr; OSL_ENSURE(pDocShell, "doc shell missing"); if (pDocShell) { if (!pDocShell->GetFormat().IsTextmode()) { // If the Math equation is not in text mode, we attach a display="block" // attribute on the 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); } if (pDocShell->GetFormat().IsRightToLeft()) { // If the Math equation is set right-to-left, we attach a dir="rtl" // attribute on the root. We don't do anything if it is set // left-to-right, the default dir="ltr" value will be used. AddAttribute(XML_NAMESPACE_MATH, XML_DIR, XML_RTL); } } SvXMLElementExport aEquation(*this, XML_NAMESPACE_MATH, XML_MATH, true, true); std::unique_ptr 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_Int16 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(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& aProps) { uno::Reference xModel = GetModel(); if (!xModel.is()) return; SmModel* pModel = comphelper::getFromUnoTunnel(xModel); if (!pModel) return; SmDocShell* pDocShell = static_cast(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& rProps) { Reference xProps(GetModel(), UNO_QUERY); if (!xProps.is()) return; Reference xPropertySetInfo = xProps->getPropertySetInfo(); if (!xPropertySetInfo.is()) return; const Sequence 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 // elements e.g. we only need three 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 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(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 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 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 to a backslash, so just use \ SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true); ExportNodes(pNode->GetSubNode(0), nLevel); { // Scoping for 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 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 pRow; std::unique_ptr 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(pNode); std::unique_ptr pMath; if (pNode->GetType() == SmNodeType::Math || pNode->GetType() == SmNodeType::GlyphSpecial) { // Export SmNodeType::Math and SmNodeType::GlyphSpecial symbols as 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 elements: // - These math symbols should not be drawn slanted. Hence we should // attach a mathvariant="normal" attribute to single-char 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 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)); } auto nArse = ConvertMathToMathML(pTemp->GetText()); OSL_ENSURE(nArse != 0xffff, "Non existent symbol"); GetDocHandler()->characters(OUString(&nArse, 1)); } void SmXMLExport::ExportText(const SmNode* pNode) { std::unique_ptr pText; const SmTextNode* pTemp = static_cast(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(pNode); //!! exports an element. Note that for example "~_~" is allowed in //!! Math (so it has no sense at all) but must not result in an empty //!! 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 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 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 or + elements according to // the stretchiness of fences. The MathML recommendation defines an // + construction that is equivalent to the 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 + elements. // See #fdo 66282. // SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true); // opening-fence 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))) { // SvXMLElementExport aRowExport(*this, XML_NAMESPACE_MATH, XML_MROW, true, true); ExportNodes(pTemp, nLevel + 1); // } // closing-fence 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); } // } 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 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 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 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(pNode); const Fraction& aFrac = pFontNode->GetSizeParameter(); OUStringBuffer sStrBuf; switch (pFontNode->GetSizeType()) { case FontSizeType::MULTIPLY: ::sax::Converter::convertDouble(sStrBuf, static_cast(aFrac * Fraction(100, 1))); sStrBuf.append('%'); break; case FontSizeType::DIVIDE: ::sax::Converter::convertDouble(sStrBuf, static_cast(Fraction(100, 1) / aFrac)); sStrBuf.append('%'); break; case FontSizeType::ABSOLUT: ::sax::Converter::convertDouble(sStrBuf, static_cast(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 double mytest = o3tl::convert(pFontNode->GetFont().GetFontSize().Height(), SmO3tlLengthUnit(), o3tl::Length::pt); if (pFontNode->GetSizeType() == FontSizeType::MINUS) mytest -= static_cast(aFrac); else mytest += static_cast(aFrac); 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 or element. These elements // are mrow-like, so ExportExpression doesn't need to add an explicit // 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(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: { const SmTextNode* pTemp = static_cast(pNode); if (pTemp->GetText().isEmpty()) { // 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(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: */