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