From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- starmath/source/mathml/import.cxx | 1430 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1430 insertions(+) create mode 100644 starmath/source/mathml/import.cxx (limited to 'starmath/source/mathml/import.cxx') diff --git a/starmath/source/mathml/import.cxx b/starmath/source/mathml/import.cxx new file mode 100644 index 000000000..52d47028e --- /dev/null +++ b/starmath/source/mathml/import.cxx @@ -0,0 +1,1430 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +// Our mathml +#include + +// LO tools to use +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Extra LO tools +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Our starmath tools +#include +#include +#include +#include +#include +#include + +// Old parser +#include + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace com::sun::star::xml::sax; +using namespace ::xmloff::token; + +// SmMLImportContext +/*************************************************************************************************/ + +SmMlElement* SmMLImportWrapper::getElementTree() +{ + return m_pMlImport == nullptr ? nullptr : m_pMlImport->getElementTree(); +} + +ErrCode SmMLImportWrapper::Import(SfxMedium& rMedium) +{ + // Fetch context + uno::Reference xContext(comphelper::getProcessComponentContext()); + if (!xContext.is()) + { + SAL_WARN("starmath", "Failed to fetch model while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Check model + if (!m_xModel.is()) + { + SAL_WARN("starmath", "Failed to fetch model while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Make a model component from our SmModel + uno::Reference xModelComp = m_xModel; + if (!xModelComp.is()) + { + SAL_WARN("starmath", "Failed to make model while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Try to get an XStatusIndicator from the Medium + uno::Reference xStatusIndicator; + + // Get model via uno + SmModel* pModel = comphelper::getFromUnoTunnel(m_xModel); + if (pModel == nullptr) + { + SAL_WARN("starmath", "Failed to fetch sm model while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Get doc shell + m_pDocShell = static_cast(pModel->GetObjectShell()); + if (m_pDocShell == nullptr) + { + SAL_WARN("starmath", "Failed to fetch smdoc shell while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Check if it is an embed object + bool bEmbedded = m_pDocShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED; + + if (!bEmbedded) + { + // Extra check to ensure everything is fine + if (m_pDocShell->GetMedium() != &rMedium) + { + SAL_WARN("starmath", "Given medium and doc shell medium differ while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Fetch the item set + SfxItemSet* pSet = rMedium.GetItemSet(); + if (pSet) + { + const SfxUnoAnyItem* pItem = pSet->GetItem(SID_PROGRESS_STATUSBAR_CONTROL); + if (pItem != nullptr) + pItem->GetValue() >>= xStatusIndicator; + } + } + + // Create property list + static const comphelper::PropertyMapEntry aInfoMap[] + = { { u"PrivateData", 0, cppu::UnoType::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { u"BaseURI", 0, ::cppu::UnoType::get(), beans::PropertyAttribute::MAYBEVOID, + 0 }, + { u"StreamRelPath", 0, ::cppu::UnoType::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { u"StreamName", 0, ::cppu::UnoType::get(), + beans::PropertyAttribute::MAYBEVOID, 0 } }; + uno::Reference xInfoSet( + comphelper::GenericPropertySet_CreateInstance(new comphelper::PropertySetInfo(aInfoMap))); + + // Set base URI + // needed for relative URLs; but it's OK to import e.g. MathML from the clipboard without one + SAL_INFO_IF(rMedium.GetBaseURL().isEmpty(), "starmath", "SmMLImportWrapper: no base URL"); + xInfoSet->setPropertyValue("BaseURI", Any(rMedium.GetBaseURL())); + + // Fetch progress range + sal_Int32 nProgressRange(rMedium.IsStorage() ? 3 : 1); + if (xStatusIndicator.is()) + { + xStatusIndicator->start(SvxResId(RID_SVXSTR_DOC_LOAD), nProgressRange); + xStatusIndicator->setValue(0); + } + + // Get storage + if (rMedium.IsStorage()) + { + // TODO/LATER: handle the case of embedded links gracefully + if (bEmbedded) // && !rMedium.GetStorage()->IsRoot() ) + { + OUString aName(u"dummyObjName"); + if (rMedium.GetItemSet()) + { + const SfxStringItem* pDocHierarchItem + = rMedium.GetItemSet()->GetItem(SID_DOC_HIERARCHICALNAME); + if (pDocHierarchItem != nullptr) + aName = pDocHierarchItem->GetValue(); + } + + if (!aName.isEmpty()) + xInfoSet->setPropertyValue("StreamRelPath", Any(aName)); + } + + // Check if use OASIS ( new document format ) + bool bOASIS = SotStorage::GetVersion(rMedium.GetStorage()) > SOFFICE_FILEFORMAT_60; + if (xStatusIndicator.is()) + xStatusIndicator->setValue(1); + + // Error code in case of needed + ErrCode nWarn = ERRCODE_NONE; + + // Read metadata + // read a component from storage + if (!bEmbedded) + { + if (bOASIS) + nWarn = ReadThroughComponentS(rMedium.GetStorage(), xModelComp, u"meta.xml", + xContext, xInfoSet, + u"com.sun.star.comp.Math.MLOasisMetaImporter", 6); + else + nWarn + = ReadThroughComponentS(rMedium.GetStorage(), xModelComp, u"meta.xml", xContext, + xInfoSet, u"com.sun.star.comp.Math.XMLMetaImporter", 5); + } + + // Check if successful + if (nWarn != ERRCODE_NONE) + { + if (xStatusIndicator.is()) + xStatusIndicator->end(); + SAL_WARN("starmath", "Failed to read file"); + return nWarn; + } + + // Increase success indicator + if (xStatusIndicator.is()) + xStatusIndicator->setValue(2); + + // Read settings + // read a component from storage + if (bOASIS) + nWarn = ReadThroughComponentS(rMedium.GetStorage(), xModelComp, u"settings.xml", + xContext, xInfoSet, + u"com.sun.star.comp.Math.MLOasisSettingsImporter", 6); + else + nWarn + = ReadThroughComponentS(rMedium.GetStorage(), xModelComp, u"settings.xml", xContext, + xInfoSet, u"com.sun.star.comp.Math.XMLSettingsImporter", 5); + + // Check if successful + if (nWarn == ERRCODE_IO_BROKENPACKAGE) + { + if (xStatusIndicator.is()) + xStatusIndicator->end(); + SAL_WARN("starmath", "Failed to read file"); + return nWarn; + } + + // Increase success indicator + if (xStatusIndicator.is()) + xStatusIndicator->setValue(3); + + // Read document + // read a component from storage + if (m_pDocShell->GetSmSyntaxVersion() == 5) + nWarn + = ReadThroughComponentS(rMedium.GetStorage(), xModelComp, u"content.xml", xContext, + xInfoSet, u"com.sun.star.comp.Math.XMLImporter", 5); + else + nWarn + = ReadThroughComponentS(rMedium.GetStorage(), xModelComp, u"content.xml", xContext, + xInfoSet, u"com.sun.star.comp.Math.MLImporter", 6); + // Check if successful + if (nWarn != ERRCODE_NONE) + { + if (xStatusIndicator.is()) + xStatusIndicator->end(); + SAL_WARN("starmath", "Failed to read file"); + return nWarn; + } + + // Finish + if (xStatusIndicator.is()) + xStatusIndicator->end(); + return ERRCODE_NONE; + } + else + { + // Create input stream + Reference xInputStream + = new utl::OInputStreamWrapper(rMedium.GetInStream()); + + // Increase success indicator + if (xStatusIndicator.is()) + xStatusIndicator->setValue(1); + + // Read data + // read a component from input stream + ErrCode nError = ERRCODE_NONE; + if (m_pDocShell->GetSmSyntaxVersion() == 5) + nError = ReadThroughComponentIS(xInputStream, xModelComp, xContext, xInfoSet, + u"com.sun.star.comp.Math.XMLImporter", false, 5); + else + nError = ReadThroughComponentIS(xInputStream, xModelComp, xContext, xInfoSet, + u"com.sun.star.comp.Math.MLImporter", false, 6); + + // Finish + if (xStatusIndicator.is()) + xStatusIndicator->end(); + + // Declare any error + if (nError != ERRCODE_NONE) + SAL_WARN("starmath", "Failed to read file"); + + return nError; + } +} + +ErrCode SmMLImportWrapper::Import(std::u16string_view aSource) +{ + // Fetch context + uno::Reference xContext(comphelper::getProcessComponentContext()); + if (!xContext.is()) + { + SAL_WARN("starmath", "Failed to fetch model while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Check model + if (!m_xModel.is()) + { + SAL_WARN("starmath", "Failed to fetch model while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Make a model component from our SmModel + uno::Reference xModelComp = m_xModel; + if (!xModelComp.is()) + { + SAL_WARN("starmath", "Failed to make model while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Get model via uno + SmModel* pModel = comphelper::getFromUnoTunnel(m_xModel); + if (pModel == nullptr) + { + SAL_WARN("starmath", "Failed to fetch sm model while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Get doc shell + m_pDocShell = static_cast(pModel->GetObjectShell()); + if (m_pDocShell == nullptr) + { + SAL_WARN("starmath", "Failed to fetch smdoc shell while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Create property list + static const comphelper::PropertyMapEntry aInfoMap[] + = { { u"PrivateData", 0, cppu::UnoType::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { u"BaseURI", 0, ::cppu::UnoType::get(), beans::PropertyAttribute::MAYBEVOID, + 0 }, + { u"StreamRelPath", 0, ::cppu::UnoType::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { u"StreamName", 0, ::cppu::UnoType::get(), + beans::PropertyAttribute::MAYBEVOID, 0 } }; + uno::Reference xInfoSet( + comphelper::GenericPropertySet_CreateInstance(new comphelper::PropertySetInfo(aInfoMap))); + + // Read data + // read a component from text + ErrCode nError = ReadThroughComponentMS(aSource, xModelComp, xContext, xInfoSet); + + // Declare any error + if (nError != ERRCODE_NONE) + { + SAL_WARN("starmath", "Failed to read file"); + return nError; + } + + return ERRCODE_NONE; +} + +// read a component from input stream +ErrCode SmMLImportWrapper::ReadThroughComponentIS( + const Reference& xInputStream, const Reference& xModelComponent, + Reference const& rxContext, + Reference const& rPropSet, const char16_t* pFilterName, bool bEncrypted, + int_fast16_t nSyntaxVersion) +{ + // Needs an input stream but checked by caller + // Needs a context but checked by caller + // Needs property set but checked by caller + // Needs a filter name but checked by caller + + // Prepare ParserInputSource + xml::sax::InputSource aParserInput; + aParserInput.aInputStream = xInputStream; + + // Prepare property list + Sequence aArgs{ Any(rPropSet) }; + + // Get filter + Reference xFilter + = rxContext->getServiceManager()->createInstanceWithArgumentsAndContext( + OUString(pFilterName), aArgs, rxContext); + if (!xFilter.is()) + { + SAL_WARN("starmath", "Can't instantiate filter component " << OUString(pFilterName)); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Connect model and filter + Reference xImporter(xFilter, UNO_QUERY); + xImporter->setTargetDocument(xModelComponent); + + // Finally, parser the stream + try + { + Reference xFastParser(xFilter, UNO_QUERY); + Reference xFastDocHandler(xFilter, UNO_QUERY); + if (xFastParser) + { + xFastParser->setCustomEntityNames(starmathdatabase::icustomMathmlHtmlEntities); + xFastParser->parseStream(aParserInput); + } + else if (xFastDocHandler) + { + Reference xParser + = css::xml::sax::FastParser::create(rxContext); + xParser->setCustomEntityNames(starmathdatabase::icustomMathmlHtmlEntities); + xParser->setFastDocumentHandler(xFastDocHandler); + xParser->parseStream(aParserInput); + } + else + { + Reference xDocHandler(xFilter, UNO_QUERY); + assert(xDocHandler); + Reference xParser = css::xml::sax::Parser::create(rxContext); + xParser->setDocumentHandler(xDocHandler); + xParser->parseStream(aParserInput); + } + + if (nSyntaxVersion == 5) + { + SmXMLImport* pXMlImport = comphelper::getFromUnoTunnel(xFilter); + if (pXMlImport != nullptr && pXMlImport->GetSuccess()) + return ERRCODE_NONE; + else + { + SAL_WARN("starmath", "Filter failed on file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + } + + m_pMlImport = comphelper::getFromUnoTunnel(xFilter); + if (m_pMlImport != nullptr && m_pMlImport->getSuccess()) + return ERRCODE_NONE; + else + { + SAL_WARN("starmath", "Filter failed on file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + } + catch (const xml::sax::SAXParseException& r) + { + // Sax parser sends wrapped exceptions, try to find the original one + xml::sax::SAXException aTmp; + xml::sax::SAXException aSaxEx = *static_cast(&r); + while (aSaxEx.WrappedException >>= aTmp) + aSaxEx = aTmp; + + packages::zip::ZipIOException aBrokenPackage; + if (aSaxEx.WrappedException >>= aBrokenPackage) + { + SAL_WARN("starmath", "Failed to read file SAXParseException"); + return ERRCODE_IO_BROKENPACKAGE; + } + + if (bEncrypted) + { + SAL_WARN("starmath", "Wrong file password SAXParseException"); + return ERRCODE_SFX_WRONGPASSWORD; + } + } + catch (const xml::sax::SAXException& r) + { + packages::zip::ZipIOException aBrokenPackage; + if (r.WrappedException >>= aBrokenPackage) + { + SAL_WARN("starmath", "Failed to read file SAXException"); + return ERRCODE_IO_BROKENPACKAGE; + } + + if (bEncrypted) + { + SAL_WARN("starmath", "Wrong file password SAXException"); + return ERRCODE_SFX_WRONGPASSWORD; + } + } + catch (const packages::zip::ZipIOException&) + { + SAL_WARN("starmath", "Failed to unzip file ZipIOException"); + return ERRCODE_IO_BROKENPACKAGE; + } + catch (const io::IOException&) + { + SAL_WARN("starmath", "Failed to read file ZipIOException"); + return ERRCODE_IO_UNKNOWN; + } + catch (const std::range_error&) + { + SAL_WARN("starmath", "Failed to read file"); + return ERRCODE_ABORT; + } + + return ERRCODE_ABORT; +} + +// read a component from storage +ErrCode SmMLImportWrapper::ReadThroughComponentS(const uno::Reference& xStorage, + const Reference& xModelComponent, + const char16_t* pStreamName, + Reference const& rxContext, + Reference const& rPropSet, + const char16_t* pFilterName, + int_fast16_t nSyntaxVersion) +{ + // Needs a storage but checked by caller + // Needs a model but checked by caller + // Needs a stream name but checked by caller + // Needs a context but checked by caller + // Needs a property set but checked by caller + // Needs a filter name but checked by caller + + // Get the input stream + try + { + // Create the stream for the event read + uno::Reference xEventsStream + = xStorage->openStreamElement(OUString(pStreamName), embed::ElementModes::READ); + + // Determine if stream is encrypted or not + uno::Reference xProps(xEventsStream, uno::UNO_QUERY); + Any aAny = xProps->getPropertyValue("Encrypted"); + bool bEncrypted = false; + aAny >>= bEncrypted; + + // Set base URL and open stream + rPropSet->setPropertyValue("StreamName", Any(OUString(pStreamName))); + Reference xStream = xEventsStream->getInputStream(); + + // Execute read + return ReadThroughComponentIS(xStream, xModelComponent, rxContext, rPropSet, pFilterName, + bEncrypted, nSyntaxVersion); + } + catch (packages::WrongPasswordException&) + { + SAL_WARN("starmath", "Wrong file password"); + return ERRCODE_SFX_WRONGPASSWORD; + } + catch (packages::zip::ZipIOException&) + { + SAL_WARN("starmath", "Failed to unzip file"); + return ERRCODE_IO_BROKENPACKAGE; + } + catch (uno::Exception&) + { + } + + return ERRCODE_SFX_DOLOADFAILED; +} + +// read a component from text +ErrCode SmMLImportWrapper::ReadThroughComponentMS( + std::u16string_view aText, const css::uno::Reference& xModelComponent, + css::uno::Reference const& rxContext, + css::uno::Reference const& rPropSet) +{ + // Needs a storage but checked by caller + // Needs a model but checked by caller + // Needs a stream name but checked by caller + // Needs a context but checked by caller + // Needs a property set but checked by caller + // Needs a filter name but checked by caller + + // Get the input stream + try + { + // Generate input memory stream + SvMemoryStream aMemoryStream; + aMemoryStream.WriteOString(OUStringToOString(aText, RTL_TEXTENCODING_UTF8)); + uno::Reference xStream(new utl::OInputStreamWrapper(aMemoryStream)); + + // Execute read + return ReadThroughComponentIS(xStream, xModelComponent, rxContext, rPropSet, + u"com.sun.star.comp.Math.MLImporter", false, 6); + } + catch (packages::WrongPasswordException&) + { + SAL_WARN("starmath", "Wrong file password"); + return ERRCODE_SFX_WRONGPASSWORD; + } + catch (packages::zip::ZipIOException&) + { + SAL_WARN("starmath", "Failed to unzip file"); + return ERRCODE_IO_BROKENPACKAGE; + } + catch (uno::Exception&) + { + } + + return ERRCODE_SFX_DOLOADFAILED; +} + +// SmMLImport technical +/*************************************************************************************************/ + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +Math_MLImporter_get_implementation(uno::XComponentContext* pCtx, + uno::Sequence const& /*rSeq*/) +{ + return cppu::acquire( + new SmMLImport(pCtx, "com.sun.star.comp.Math.XMLImporter", SvXMLImportFlags::ALL)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +Math_MLOasisMetaImporter_get_implementation(uno::XComponentContext* pCtx, + uno::Sequence const& /*rSeq*/) +{ + return cppu::acquire(new SmMLImport(pCtx, "com.sun.star.comp.Math.XMLOasisMetaImporter", + SvXMLImportFlags::META)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +Math_MLOasisSettingsImporter_get_implementation(uno::XComponentContext* pCtx, + uno::Sequence const& /*rSeq*/) +{ + return cppu::acquire(new SmMLImport(pCtx, "com.sun.star.comp.Math.XMLOasisSettingsImporter", + SvXMLImportFlags::SETTINGS)); +} + +// SmMLImportContext +/*************************************************************************************************/ + +namespace +{ +class SmMLImportContext : public SvXMLImportContext +{ +private: + SmMlElement** m_pParent; + SmMlElement* m_pElement; + SmMlElement* m_pStyle; + +public: + SmMLImportContext(SmMLImport& rImport, SmMlElement** pParent) + : SvXMLImportContext(rImport) + , m_pParent(pParent) + , m_pElement(nullptr) + , m_pStyle(nullptr) + { + } + +private: + void declareMlError(); + +public: + /** Handles characters (text) + */ + virtual void SAL_CALL characters(const OUString& aChars) override; + + /** Starts the mathml element + */ + virtual void SAL_CALL startFastElement( + sal_Int32 nElement, const Reference& aAttributeList) override; + + /** Ends the mathml element + */ + virtual void SAL_CALL endFastElement(sal_Int32 Element) override; + + /** Creates child element + */ + virtual uno::Reference + SAL_CALL createFastChildContext(sal_Int32 nElement, + const uno::Reference& Attribs) override; + + /** Inherits the style from it's parents + */ + void inheritStyle(); + + /** Inherits the style from it's parents on end + */ + void inheritStyleEnd(); + + /** Handle mathml attributes + */ + void handleAttributes(const Reference& aAttributeList); + + /** Handle mathml length attributes + */ + SmLengthValue handleLengthAttribute(const OUString& aAttribute); +}; + +uno::Reference SAL_CALL +SmMLImportContext::createFastChildContext(sal_Int32, const uno::Reference&) +{ + uno::Reference xContext; + xContext = new SmMLImportContext(static_cast(GetImport()), &m_pElement); + return xContext; +} + +void SmMLImportContext::declareMlError() +{ + SmMLImport& aSmMLImport = static_cast(GetImport()); + aSmMLImport.declareMlError(); +} + +void SmMLImportContext::inheritStyle() +{ + while ((m_pStyle = m_pStyle->getParentElement()) != nullptr) + { + if (m_pStyle->getParentElement()->getMlElementType() == SmMlElementType::MlMstyle + || m_pStyle->getParentElement()->getMlElementType() == SmMlElementType::MlMath) + break; + } + + // Parent inheritation + // Mathcolor, mathsize, dir and displaystyle are inherited from parent + SmMlElement* pParent = *m_pParent; + m_pElement->setAttribute(pParent->getAttribute(SmMlAttributeValueType::MlMathcolor)); + m_pElement->setAttribute(pParent->getAttribute(SmMlAttributeValueType::MlMathsize)); + m_pElement->setAttribute(pParent->getAttribute(SmMlAttributeValueType::MlDir)); + m_pElement->setAttribute(pParent->getAttribute(SmMlAttributeValueType::MlDisplaystyle)); + + // Inherit operator dictionary overwrites + if (m_pStyle != nullptr + && (m_pElement->getMlElementType() == SmMlElementType::MlMo + || m_pElement->getMlElementType() == SmMlElementType::MlMstyle + || m_pElement->getMlElementType() == SmMlElementType::MlMath)) + { + // TODO fetch operator dictionary first and then overwrite + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlAccent)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlAccent)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlFence)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlFence)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlLspace)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlLspace)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlMaxsize)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlMaxsize)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlMinsize)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlMinsize)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlMovablelimits)) + m_pElement->setAttribute( + m_pStyle->getAttribute(SmMlAttributeValueType::MlMovablelimits)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlRspace)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlRspace)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlSeparator)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlSeparator)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlStretchy)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlStretchy)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlSymmetric)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlSymmetric)); + + if (m_pElement->getMlElementType() == SmMlElementType::MlMo) + { + // Set form based in position + SmMlAttribute aAttribute(SmMlAttributeValueType::MlForm); + SmMlForm aForm; + if (m_pElement->getSubElementId() == 0) + aForm = { SmMlAttributeValueForm::MlPrefix }; + else + aForm = { SmMlAttributeValueForm::MlInfix }; + aAttribute.setMlForm(&aForm); + m_pElement->setAttribute(aAttribute); + } + } + + // Inherit mathvariant + if (m_pStyle && m_pStyle->isAttributeSet(SmMlAttributeValueType::MlMathvariant)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlMathvariant)); +} + +void SmMLImportContext::inheritStyleEnd() +{ + // Mo: check it is the end: postfix + if (m_pElement->getMlElementType() == SmMlElementType::MlMo) + { + if ((*m_pParent)->getSubElementsCount() == m_pElement->getSubElementId()) + { + // Set form based in position + SmMlAttribute aAttribute(SmMlAttributeValueType::MlForm); + SmMlForm aForm = { SmMlAttributeValueForm::MlPosfix }; + aAttribute.setMlForm(&aForm); + m_pElement->setAttribute(aAttribute); + } + } + + // Mi: 1 char -> italic + if (m_pElement->getMlElementType() != SmMlElementType::MlMi) + return; + + // Inherit mathvariant + if (!m_pStyle->isAttributeSet(SmMlAttributeValueType::MlMathvariant)) + { + sal_Int32 nIndexUtf16 = 0; + // Check if there is only one code point + m_pElement->getText().iterateCodePoints(&nIndexUtf16, 1); + // Mathml says that 1 code point -> italic + if (nIndexUtf16 == m_pElement->getText().getLength()) + { + SmMlAttribute aAttribute(SmMlAttributeValueType::MlMathvariant); + SmMlMathvariant aMathvariant = { SmMlAttributeValueMathvariant::italic }; + aAttribute.setMlMathvariant(&aMathvariant); + aAttribute.setSet(false); + m_pElement->setAttribute(aAttribute); + } + } +} + +SmLengthValue SmMLImportContext::handleLengthAttribute(const OUString& aAttribute) +{ + // Locate unit indication + int32_t nUnitPos; + for (nUnitPos = 0; + nUnitPos < aAttribute.getLength() + && (rtl::isAsciiHexDigit(aAttribute[nUnitPos]) || aAttribute[nUnitPos] == '.'); + ++nUnitPos) + ; + + // Find unit + SmLengthUnit nUnit = SmLengthUnit::MlM; + if (nUnitPos != aAttribute.getLength()) + { + OUString aUnit = aAttribute.copy(nUnitPos); + if (aUnit.compareToIgnoreAsciiCaseAscii("ex")) + nUnit = SmLengthUnit::MlEx; + if (aUnit.compareToIgnoreAsciiCaseAscii("px")) + nUnit = SmLengthUnit::MlPx; + if (aUnit.compareToIgnoreAsciiCaseAscii("in")) + nUnit = SmLengthUnit::MlIn; + if (aUnit.compareToIgnoreAsciiCaseAscii("cm")) + nUnit = SmLengthUnit::MlCm; + if (aUnit.compareToIgnoreAsciiCaseAscii("mm")) + nUnit = SmLengthUnit::MlMm; + if (aUnit.compareToIgnoreAsciiCaseAscii("pt")) + nUnit = SmLengthUnit::MlPt; + if (aUnit.compareToIgnoreAsciiCaseAscii("pc")) + nUnit = SmLengthUnit::MlPc; + if (aUnit.compareToIgnoreAsciiCaseAscii("%")) + nUnit = SmLengthUnit::MlP; + else + declareMlError(); + } + + // Get value + std::u16string_view aValue = aAttribute.subView(0, nUnitPos); + double nValue = o3tl::toDouble(aValue); + if (nValue == 0) + { + nUnit = SmLengthUnit::MlM; + nValue = 1.0; + declareMlError(); + } + + // Return + SmLengthValue aLengthValue = { nUnit, nValue, new OUString(aAttribute) }; + return aLengthValue; +} + +void SmMLImportContext::handleAttributes(const Reference& aAttributeList) +{ + for (auto& aIter : sax_fastparser::castToFastAttributeList(aAttributeList)) + { + SmMlAttribute aAttribute(SmMlAttributeValueType::NMlEmpty); + switch (aIter.getToken() & TOKEN_MASK) + { + case XML_ACCENT: + { + if (IsXMLToken(aIter, XML_TRUE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlAccent); + SmMlAccent aAccent = { SmMlAttributeValueAccent::MlTrue }; + aAttribute.setMlAccent(&aAccent); + } + else if (IsXMLToken(aIter, XML_FALSE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlAccent); + SmMlAccent aAccent = { SmMlAttributeValueAccent::MlFalse }; + aAttribute.setMlAccent(&aAccent); + } + else + { + declareMlError(); + } + break; + } + case XML_DIR: + { + if (IsXMLToken(aIter, XML_RTL)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlDir); + SmMlDir aDir = { SmMlAttributeValueDir::MlRtl }; + aAttribute.setMlDir(&aDir); + } + else if (IsXMLToken(aIter, XML_LTR)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlDir); + SmMlDir aDir = { SmMlAttributeValueDir::MlLtr }; + aAttribute.setMlDir(&aDir); + } + else + { + declareMlError(); + } + break; + } + case XML_DISPLAYSTYLE: + if (IsXMLToken(aIter, XML_TRUE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlDisplaystyle); + SmMlDisplaystyle aDisplaystyle = { SmMlAttributeValueDisplaystyle::MlTrue }; + aAttribute.setMlDisplaystyle(&aDisplaystyle); + } + else if (IsXMLToken(aIter, XML_FALSE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlDisplaystyle); + SmMlDisplaystyle aDisplaystyle = { SmMlAttributeValueDisplaystyle::MlFalse }; + aAttribute.setMlDisplaystyle(&aDisplaystyle); + } + else + { + declareMlError(); + } + break; + case XML_FENCE: + if (IsXMLToken(aIter, XML_TRUE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlFence); + SmMlFence aFence = { SmMlAttributeValueFence::MlTrue }; + aAttribute.setMlFence(&aFence); + } + else if (IsXMLToken(aIter, XML_FALSE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlFence); + SmMlFence aFence = { SmMlAttributeValueFence::MlFalse }; + aAttribute.setMlFence(&aFence); + } + else + { + declareMlError(); + } + break; + case XML_HREF: + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlHref); + OUString* aRef = new OUString(aIter.toString()); + SmMlHref aHref = { SmMlAttributeValueHref::NMlValid, aRef }; + aAttribute.setMlHref(&aHref); + break; + } + case XML_LSPACE: + { + SmMlLspace aLspace; + aLspace.m_aLengthValue = handleLengthAttribute(aIter.toString()); + aAttribute.setMlLspace(&aLspace); + break; + } + case XML_MATHBACKGROUND: + { + if (IsXMLToken(aIter, XML_TRANSPARENT)) + { + SmMlMathbackground aMathbackground + = { SmMlAttributeValueMathbackground::MlTransparent, COL_TRANSPARENT }; + aAttribute.setMlMathbackground(&aMathbackground); + } + else + { + Color aColor + = starmathdatabase::Identify_ColorName_HTML(aIter.toString()).cColor; + SmMlMathbackground aMathbackground + = { SmMlAttributeValueMathbackground::MlRgb, aColor }; + aAttribute.setMlMathbackground(&aMathbackground); + } + break; + } + case XML_MATHCOLOR: + { + if (IsXMLToken(aIter, XML_DEFAULT)) + { + SmMlMathcolor aMathcolor + = { SmMlAttributeValueMathcolor::MlDefault, COL_BLACK }; + aAttribute.setMlMathcolor(&aMathcolor); + } + else + { + Color aColor + = starmathdatabase::Identify_ColorName_HTML(aIter.toString()).cColor; + SmMlMathcolor aMathcolor = { SmMlAttributeValueMathcolor::MlRgb, aColor }; + aAttribute.setMlMathcolor(&aMathcolor); + } + break; + } + case XML_MATHSIZE: + { + SmMlMathsize aMathsize; + aMathsize.m_aLengthValue = handleLengthAttribute(aIter.toString()); + aAttribute.setMlMathsize(&aMathsize); + break; + } + case XML_MATHVARIANT: + { + OUString aVariant = aIter.toString(); + SmMlAttributeValueMathvariant nVariant = SmMlAttributeValueMathvariant::normal; + if (aVariant.compareTo(u"normal")) + nVariant = SmMlAttributeValueMathvariant::normal; + else if (aVariant.compareTo(u"bold")) + nVariant = SmMlAttributeValueMathvariant::bold; + else if (aVariant.compareTo(u"italic")) + nVariant = SmMlAttributeValueMathvariant::italic; + else if (aVariant.compareTo(u"double-struck")) + nVariant = SmMlAttributeValueMathvariant::double_struck; + else if (aVariant.compareTo(u"script")) + nVariant = SmMlAttributeValueMathvariant::script; + else if (aVariant.compareTo(u"fraktur")) + nVariant = SmMlAttributeValueMathvariant::fraktur; + else if (aVariant.compareTo(u"sans-serif")) + nVariant = SmMlAttributeValueMathvariant::sans_serif; + else if (aVariant.compareTo(u"monospace")) + nVariant = SmMlAttributeValueMathvariant::monospace; + else if (aVariant.compareTo(u"bold-italic")) + nVariant = SmMlAttributeValueMathvariant::bold_italic; + else if (aVariant.compareTo(u"bold-fracktur")) + nVariant = SmMlAttributeValueMathvariant::bold_fraktur; + else if (aVariant.compareTo(u"bold-script")) + nVariant = SmMlAttributeValueMathvariant::bold_script; + else if (aVariant.compareTo(u"bold-sans-serif")) + nVariant = SmMlAttributeValueMathvariant::bold_sans_serif; + else if (aVariant.compareTo(u"sans-serif-italic")) + nVariant = SmMlAttributeValueMathvariant::sans_serif_italic; + else if (aVariant.compareTo(u"sans-serif-bold-italic")) + nVariant = SmMlAttributeValueMathvariant::sans_serif_bold_italic; + else if (aVariant.compareTo(u"initial")) + nVariant = SmMlAttributeValueMathvariant::initial; + else if (aVariant.compareTo(u"tailed")) + nVariant = SmMlAttributeValueMathvariant::tailed; + else if (aVariant.compareTo(u"looped")) + nVariant = SmMlAttributeValueMathvariant::looped; + else if (aVariant.compareTo(u"stretched")) + nVariant = SmMlAttributeValueMathvariant::stretched; + else + declareMlError(); + SmMlMathvariant aMathvariant = { nVariant }; + aAttribute.setMlMathvariant(&aMathvariant); + break; + } + case XML_MAXSIZE: + { + SmMlMaxsize aMaxsize; + if (IsXMLToken(aIter, XML_INFINITY)) + { + aMaxsize.m_aMaxsize = SmMlAttributeValueMaxsize::MlInfinity; + aMaxsize.m_aLengthValue = { SmLengthUnit::MlP, 10000, new OUString(u"10000%") }; + } + else + { + aMaxsize.m_aMaxsize = SmMlAttributeValueMaxsize::MlFinite; + aMaxsize.m_aLengthValue = handleLengthAttribute(aIter.toString()); + } + aAttribute.setMlMaxsize(&aMaxsize); + break; + } + case XML_MINSIZE: + { + SmMlMinsize aMinsize; + aMinsize.m_aLengthValue = handleLengthAttribute(aIter.toString()); + aAttribute.setMlMinsize(&aMinsize); + break; + } + case XML_MOVABLELIMITS: + if (IsXMLToken(aIter, XML_TRUE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlMovablelimits); + SmMlMovablelimits aMovablelimits = { SmMlAttributeValueMovablelimits::MlTrue }; + aAttribute.setMlMovablelimits(&aMovablelimits); + } + else if (IsXMLToken(aIter, XML_FALSE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlMovablelimits); + SmMlMovablelimits aMovablelimits = { SmMlAttributeValueMovablelimits::MlFalse }; + aAttribute.setMlMovablelimits(&aMovablelimits); + } + else + { + declareMlError(); + } + break; + case XML_RSPACE: + { + SmMlRspace aRspace; + aRspace.m_aLengthValue = handleLengthAttribute(aIter.toString()); + aAttribute.setMlRspace(&aRspace); + break; + } + case XML_SEPARATOR: + if (IsXMLToken(aIter, XML_TRUE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlSeparator); + SmMlSeparator aSeparator = { SmMlAttributeValueSeparator::MlTrue }; + aAttribute.setMlSeparator(&aSeparator); + } + else if (IsXMLToken(aIter, XML_FALSE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlSeparator); + SmMlSeparator aSeparator = { SmMlAttributeValueSeparator::MlFalse }; + aAttribute.setMlSeparator(&aSeparator); + } + else + { + declareMlError(); + } + break; + case XML_STRETCHY: + if (IsXMLToken(aIter, XML_TRUE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlStretchy); + SmMlStretchy aStretchy = { SmMlAttributeValueStretchy::MlTrue }; + aAttribute.setMlStretchy(&aStretchy); + } + else if (IsXMLToken(aIter, XML_FALSE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlStretchy); + SmMlStretchy aStretchy = { SmMlAttributeValueStretchy::MlFalse }; + aAttribute.setMlStretchy(&aStretchy); + } + else + { + declareMlError(); + } + break; + case XML_SYMMETRIC: + if (IsXMLToken(aIter, XML_TRUE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlSymmetric); + SmMlSymmetric aSymmetric = { SmMlAttributeValueSymmetric::MlTrue }; + aAttribute.setMlSymmetric(&aSymmetric); + } + else if (IsXMLToken(aIter, XML_FALSE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlSymmetric); + SmMlSymmetric aSymmetric = { SmMlAttributeValueSymmetric::MlFalse }; + aAttribute.setMlSymmetric(&aSymmetric); + } + else + { + declareMlError(); + } + break; + default: + declareMlError(); + break; + } + if (aAttribute.isNullAttribute()) + declareMlError(); + else + m_pElement->setAttribute(aAttribute); + } +} + +void SmMLImportContext::characters(const OUString& aChars) { m_pElement->setText(aChars); } + +void SmMLImportContext::startFastElement(sal_Int32 nElement, + const Reference& aAttributeList) +{ + switch (nElement) + { + case XML_ELEMENT(MATH, XML_MATH): + m_pElement = new SmMlElement(SmMlElementType::MlMath); + break; + case XML_ELEMENT(MATH, XML_MI): + m_pElement = new SmMlElement(SmMlElementType::MlMi); + break; + case XML_ELEMENT(MATH, XML_MERROR): + m_pElement = new SmMlElement(SmMlElementType::MlMerror); + break; + case XML_ELEMENT(MATH, XML_MN): + m_pElement = new SmMlElement(SmMlElementType::MlMn); + break; + case XML_ELEMENT(MATH, XML_MO): + m_pElement = new SmMlElement(SmMlElementType::MlMo); + break; + case XML_ELEMENT(MATH, XML_MROW): + m_pElement = new SmMlElement(SmMlElementType::MlMrow); + break; + case XML_ELEMENT(MATH, XML_MTEXT): + m_pElement = new SmMlElement(SmMlElementType::MlMtext); + break; + case XML_ELEMENT(MATH, XML_MSTYLE): + m_pElement = new SmMlElement(SmMlElementType::MlMstyle); + break; + default: + m_pElement = new SmMlElement(SmMlElementType::NMlEmpty); + declareMlError(); + break; + } + SmMlElement* pParent = *m_pParent; + pParent->setSubElement(pParent->getSubElementsCount(), m_pElement); + inheritStyle(); + handleAttributes(aAttributeList); +} + +void SmMLImportContext::endFastElement(sal_Int32) { inheritStyleEnd(); } +} + +// SmMLImport +/*************************************************************************************************/ + +const uno::Sequence& SmMLImport::getUnoTunnelId() noexcept +{ + static const comphelper::UnoIdInit theSmMLImportUnoTunnelId; + return theSmMLImportUnoTunnelId.getSeq(); +} + +sal_Int64 SAL_CALL SmMLImport::getSomething(const uno::Sequence& rId) +{ + return comphelper::getSomethingImpl(rId, this, + comphelper::FallbackToGetSomethingOf{}); +} + +SvXMLImportContext* +SmMLImport::CreateFastContext(sal_Int32 nElement, + const uno::Reference& /*xAttrList*/) +{ + SvXMLImportContext* pContext = nullptr; + + switch (nElement) + { + case XML_ELEMENT(OFFICE, XML_DOCUMENT): + { + if (m_pElementTree == nullptr) + m_pElementTree = new SmMlElement(SmMlElementType::NMlEmpty); + uno::Reference xDPS(GetModel(), + uno::UNO_QUERY_THROW); + pContext = new SmMLImportContext(*this, &m_pElementTree); + break; + } + case XML_ELEMENT(OFFICE, XML_DOCUMENT_META): + { + uno::Reference xDPS(GetModel(), + uno::UNO_QUERY_THROW); + pContext = new SvXMLMetaDocumentContext(*this, xDPS->getDocumentProperties()); + break; + } + case XML_ELEMENT(OFFICE, XML_DOCUMENT_SETTINGS): + { + uno::Reference xDPS(GetModel(), + uno::UNO_QUERY_THROW); + pContext = new XMLDocumentSettingsContext(*this); + break; + } + default: + declareMlError(); + break; + } + return pContext; +} + +void SmMLImport::endDocument() +{ + uno::Reference xModel = GetModel(); + if (!xModel.is()) + { + SAL_WARN("starmath", "Failed to set view settings because missing model"); + SvXMLImport::endDocument(); + return; + } + + SmModel* pModel = comphelper::getFromUnoTunnel(xModel); + if (!pModel) + { + SAL_WARN("starmath", "Failed to set view settings because missing sm model"); + SvXMLImport::endDocument(); + return; + } + + SmDocShell* pDocShell = static_cast(pModel->GetObjectShell()); + if (!pDocShell) + { + SAL_WARN("starmath", "Failed to set view settings because missing sm doc shell"); + SvXMLImport::endDocument(); + return; + } + + // Check if there is element tree + if (m_pElementTree == nullptr) + { + m_bSuccess = true; + SvXMLImport::endDocument(); + return; + } + + // Get element tree and setup + + if (m_pElementTree->getSubElementsCount() == 0) + { + delete m_pElementTree; + m_pElementTree = nullptr; + } + else + { + SmMlElement* pTmpElememt = m_pElementTree->getSubElement(0); + delete m_pElementTree; + m_pElementTree = pTmpElememt; + } + pDocShell->SetMlElementTree(m_pElementTree); + + m_bSuccess = true; + SvXMLImport::endDocument(); +} + +void SmMLImport::SetViewSettings(const Sequence& aViewProps) +{ + uno::Reference xModel = GetModel(); + if (!xModel.is()) + { + SAL_WARN("starmath", "Failed to set view settings because missing model"); + return; + } + + SmModel* pModel = comphelper::getFromUnoTunnel(xModel); + if (!pModel) + { + SAL_WARN("starmath", "Failed to set view settings because missing sm model"); + return; + } + + SmDocShell* pDocShell = static_cast(pModel->GetObjectShell()); + if (!pDocShell) + { + SAL_WARN("starmath", "Failed to set view settings because missing sm doc shell"); + return; + } + + tools::Rectangle aRect(pDocShell->GetVisArea()); + + tools::Long nTmp = 0; + + for (const PropertyValue& rValue : aViewProps) + { + if (rValue.Name == "ViewAreaTop") + { + rValue.Value >>= nTmp; + aRect.SaturatingSetPosY(nTmp); + } + else if (rValue.Name == "ViewAreaLeft") + { + rValue.Value >>= nTmp; + aRect.SaturatingSetPosX(nTmp); + } + else if (rValue.Name == "ViewAreaWidth") + { + rValue.Value >>= nTmp; + Size aSize(aRect.GetSize()); + aSize.setWidth(nTmp); + aRect.SaturatingSetSize(aSize); + } + else if (rValue.Name == "ViewAreaHeight") + { + rValue.Value >>= nTmp; + Size aSize(aRect.GetSize()); + aSize.setHeight(nTmp); + aRect.SaturatingSetSize(aSize); + } + } + + pDocShell->SetVisArea(aRect); +} + +void SmMLImport::SetConfigurationSettings(const Sequence& aConfProps) +{ + uno::Reference xModel = GetModel(); + if (!xModel.is()) + { + SAL_WARN("starmath", "Failed to set view settings because missing model"); + return; + } + + uno::Reference xProps(xModel, UNO_QUERY); + if (!xProps.is()) + { + SAL_WARN("starmath", "Failed to set view settings because missing model properties"); + return; + } + + Reference xInfo(xProps->getPropertySetInfo()); + if (!xInfo.is()) + { + SAL_WARN("starmath", + "Failed to set view settings because missing model properties information"); + return; + } + + static const OUStringLiteral sFormula(u"Formula"); + static const OUStringLiteral sBasicLibraries(u"BasicLibraries"); + static const OUStringLiteral sDialogLibraries(u"DialogLibraries"); + for (const PropertyValue& rValue : aConfProps) + { + if (rValue.Name != sFormula && rValue.Name != sBasicLibraries + && rValue.Name != sDialogLibraries) + { + try + { + if (xInfo->hasPropertyByName(rValue.Name)) + xProps->setPropertyValue(rValue.Name, rValue.Value); + } + catch (const beans::PropertyVetoException&) + { + // dealing with read-only properties here. Nothing to do... + } + catch (const Exception&) + { + SAL_WARN("starmath", "Unexpected issue while loading document properties"); + } + } + } +} + +SmMLImport::SmMLImport(const css::uno::Reference& rContext, + OUString const& implementationName, SvXMLImportFlags nImportFlags) + : SvXMLImport(rContext, implementationName, nImportFlags) + , m_pElementTree(nullptr) + , m_bSuccess(false) + , m_nSmSyntaxVersion(SM_MOD()->GetConfig()->GetDefaultSmSyntaxVersion()) +{ +} + +/** Handles an error on the mathml structure + */ +void SmMLImport::declareMlError() +{ + m_bSuccess = false; + SAL_WARN("starmath", "MathML error"); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ -- cgit v1.2.3