diff options
author | Takeshi Abe <tabe@fixedpoint.jp> | 2016-11-13 21:06:55 +0900 |
---|---|---|
committer | Takeshi Abe <tabe@fixedpoint.jp> | 2016-11-15 00:27:30 +0000 |
commit | 074f0ab1d76f16fe92493868e2f2de75e67792ef (patch) | |
tree | 75e27850211fc1d12618b795eaa2d6e853f1f711 | |
parent | bb50b1609abe83265311613db4a18e992dc666c8 (diff) |
tdf#76296 Import MathML's <mspace>
as possibly multiple "~" and/or "`" by honoring its width attribute.
Change-Id: I17e361c3f8f5d061c856b72266332369497d16b9
Reviewed-on: https://gerrit.libreoffice.org/30809
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Takeshi Abe <tabe@fixedpoint.jp>
-rw-r--r-- | starmath/Library_sm.mk | 1 | ||||
-rw-r--r-- | starmath/inc/node.hxx | 1 | ||||
-rw-r--r-- | starmath/qa/extras/data/mspace.mml | 12 | ||||
-rw-r--r-- | starmath/qa/extras/mmlexport-test.cxx | 10 | ||||
-rw-r--r-- | starmath/qa/extras/mmlimport-test.cxx | 8 | ||||
-rw-r--r-- | starmath/source/cursor.cxx | 5 | ||||
-rw-r--r-- | starmath/source/mathmlattr.cxx | 147 | ||||
-rw-r--r-- | starmath/source/mathmlattr.hxx | 52 | ||||
-rw-r--r-- | starmath/source/mathmlimport.cxx | 87 | ||||
-rw-r--r-- | starmath/source/mathmlimport.hxx | 6 | ||||
-rw-r--r-- | starmath/source/node.cxx | 14 | ||||
-rw-r--r-- | starmath/source/visitors.cxx | 11 |
12 files changed, 347 insertions, 7 deletions
diff --git a/starmath/Library_sm.mk b/starmath/Library_sm.mk index 08a4b50d102a..d9c2d6d56da1 100644 --- a/starmath/Library_sm.mk +++ b/starmath/Library_sm.mk @@ -73,6 +73,7 @@ $(eval $(call gb_Library_add_exception_objects,sm,\ starmath/source/document \ starmath/source/edit \ starmath/source/format \ + starmath/source/mathmlattr \ starmath/source/mathmlexport \ starmath/source/mathmlimport \ starmath/source/mathtype \ diff --git a/starmath/inc/node.hxx b/starmath/inc/node.hxx index 68f3704883d8..c0e5de4ed2d5 100644 --- a/starmath/inc/node.hxx +++ b/starmath/inc/node.hxx @@ -1136,6 +1136,7 @@ public: virtual void Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell) override; virtual void Arrange(OutputDevice &rDev, const SmFormat &rFormat) override; void Accept(SmVisitor* pVisitor) override; + virtual void CreateTextFromNode(OUString &rText) override; }; diff --git a/starmath/qa/extras/data/mspace.mml b/starmath/qa/extras/data/mspace.mml new file mode 100644 index 000000000000..1906510940ea --- /dev/null +++ b/starmath/qa/extras/data/mspace.mml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<math xmlns="http://www.w3.org/1998/Math/MathML"> + <mrow> + <mi>a</mi> + <mspace /> + <mi>b</mi> + <mspace width="2em" /> + <mi>c</mi> + <mspace width="5.5em" /> + <mi>d</mi> + </mrow> +</math> diff --git a/starmath/qa/extras/mmlexport-test.cxx b/starmath/qa/extras/mmlexport-test.cxx index 1266ca0ec3e5..b63b22f9b48d 100644 --- a/starmath/qa/extras/mmlexport-test.cxx +++ b/starmath/qa/extras/mmlexport-test.cxx @@ -35,10 +35,12 @@ public: virtual void setUp() override; virtual void tearDown() override; + void testBlank(); void testTdf97049(); void testTdf101022(); CPPUNIT_TEST_SUITE(MathMLExportTest); + CPPUNIT_TEST(testBlank); CPPUNIT_TEST(testTdf97049); CPPUNIT_TEST(testTdf101022); CPPUNIT_TEST_SUITE_END(); @@ -87,6 +89,14 @@ xmlDocPtr MathMLExportTest::exportAndParse() return pDoc; } +void MathMLExportTest::testBlank() +{ + mxDocShell->SetText("x`y~~z"); + xmlDocPtr pDoc = exportAndParse(); + assertXPath(pDoc, "/m:math/m:semantics/m:mrow/m:mspace[1]", "width", "0.5em"); + assertXPath(pDoc, "/m:math/m:semantics/m:mrow/m:mspace[2]", "width", "4em"); +} + void MathMLExportTest::testTdf97049() { mxDocShell->SetText("intd {{1 over x} dx}"); diff --git a/starmath/qa/extras/mmlimport-test.cxx b/starmath/qa/extras/mmlimport-test.cxx index 5ca5d70e8012..9bbc08ae39a9 100644 --- a/starmath/qa/extras/mmlimport-test.cxx +++ b/starmath/qa/extras/mmlimport-test.cxx @@ -32,12 +32,14 @@ public: void testSimple(); void testNsPrefixMath(); void testMaction(); + void testMspace(); void testtdf99556(); CPPUNIT_TEST_SUITE(Test); CPPUNIT_TEST(testSimple); CPPUNIT_TEST(testNsPrefixMath); CPPUNIT_TEST(testMaction); + CPPUNIT_TEST(testMspace); CPPUNIT_TEST(testtdf99556); CPPUNIT_TEST_SUITE_END(); @@ -105,6 +107,12 @@ void Test::testMaction() CPPUNIT_ASSERT_EQUAL_MESSAGE("loaded text", sExpected, mxDocShell->GetText()); } +void Test::testMspace() +{ + loadURL(m_directories.getURLFromSrc("starmath/qa/extras/data/mspace.mml")); + CPPUNIT_ASSERT_EQUAL(OUString("{a b ~ c ~~``` d}"), mxDocShell->GetText()); +} + void Test::testtdf99556() { loadURL(m_directories.getURLFromSrc("starmath/qa/extras/data/tdf99556-1.mml")); diff --git a/starmath/source/cursor.cxx b/starmath/source/cursor.cxx index c96174167b2e..fadb103c8b0f 100644 --- a/starmath/source/cursor.cxx +++ b/starmath/source/cursor.cxx @@ -996,9 +996,12 @@ void SmCursor::InsertElement(SmFormulaElement element){ case BlankElement: { SmToken token; + token.eType = TBLANK; token.nGroup = TG::Blank; token.aText = "~"; - pNewNode = new SmBlankNode(token); + SmBlankNode* pBlankNode = new SmBlankNode(token); + pBlankNode->IncreaseBy(token); + pNewNode = pBlankNode; }break; case FactorialElement: { diff --git a/starmath/source/mathmlattr.cxx b/starmath/source/mathmlattr.cxx new file mode 100644 index 000000000000..262609f1875d --- /dev/null +++ b/starmath/source/mathmlattr.cxx @@ -0,0 +1,147 @@ +/* -*- 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/. + */ + +#include "mathmlattr.hxx" + +#include <cassert> + +namespace { + +sal_Int32 lcl_GetPowerOf10(sal_Int32 nPower) +{ + assert(nPower > 0); + sal_Int32 nResult = 1; + while (nPower--) + nResult *= 10; + return nResult; +} + +} + +sal_Int32 ParseMathMLUnsignedNumber(const OUString &rStr, Fraction *pUN) +{ + assert(pUN); + auto nLen = rStr.getLength(); + sal_Int32 nDecimalPoint = -1; + sal_Int32 nIdx; + for (nIdx = 0; nIdx < nLen; nIdx++) + { + auto cD = rStr[nIdx]; + if (cD == sal_Unicode('.')) + { + if (nDecimalPoint >= 0) + return -1; + nDecimalPoint = nIdx; + continue; + } + if (cD < sal_Unicode('0') || sal_Unicode('9') < cD) + break; + } + if (nIdx == 0 || (nIdx == 1 && nDecimalPoint == 0)) + return -1; + if (nDecimalPoint == -1) + { + assert(nIdx > 0); + *pUN = Fraction(rStr.copy(0, nIdx).toInt32(), 1); + return nIdx; + } + if (nDecimalPoint == 0) + { + assert(nIdx > 1); + *pUN = Fraction(rStr.copy(1, nIdx-1).toInt32(), lcl_GetPowerOf10(nIdx-1)); + return nIdx; + } + assert(0 < nDecimalPoint); + assert(nDecimalPoint < nIdx); + *pUN = Fraction(rStr.copy(0, nDecimalPoint).toInt32(), 1); + if (++nDecimalPoint < nIdx) + *pUN += Fraction(rStr.copy(nDecimalPoint, nIdx-nDecimalPoint).toInt32(), + lcl_GetPowerOf10(nIdx-nDecimalPoint)); + return nIdx; +} + +sal_Int32 ParseMathMLNumber(const OUString &rStr, Fraction *pN) +{ + assert(pN); + if (rStr.isEmpty()) + return -1; + bool bNegative = (rStr[0] == sal_Unicode('-')); + sal_Int32 nOffset = bNegative ? 1 : 0; + Fraction aF; + auto nIdx = ParseMathMLUnsignedNumber(rStr.copy(nOffset), &aF); + if (nIdx <= 0) + return -1; + if (bNegative) + *pN = Fraction(aF.GetNumerator(), aF.GetDenominator()); + else + *pN = aF; + return nOffset + nIdx; +} + +sal_Int32 ParseMathMLAttributeLengthValue(const OUString &rStr, MathMLAttributeLengthValue *pV) +{ + assert(pV); + auto nIdx = ParseMathMLNumber(rStr, &pV->aNumber); + if (nIdx <= 0) + return -1; + OUString sRest = rStr.copy(nIdx); + if (sRest.isEmpty()) + { + pV->eUnit = MathMLLengthUnit::None; + return nIdx; + } + if (sRest.startsWith("em")) + { + pV->eUnit = MathMLLengthUnit::Em; + return nIdx + 2; + } + if (sRest.startsWith("ex")) + { + pV->eUnit = MathMLLengthUnit::Ex; + return nIdx + 2; + } + if (sRest.startsWith("px")) + { + pV->eUnit = MathMLLengthUnit::Px; + return nIdx + 2; + } + if (sRest.startsWith("in")) + { + pV->eUnit = MathMLLengthUnit::In; + return nIdx + 2; + } + if (sRest.startsWith("cm")) + { + pV->eUnit = MathMLLengthUnit::Cm; + return nIdx + 2; + } + if (sRest.startsWith("mm")) + { + pV->eUnit = MathMLLengthUnit::Mm; + return nIdx + 2; + } + if (sRest.startsWith("pt")) + { + pV->eUnit = MathMLLengthUnit::Pt; + return nIdx + 2; + } + if (sRest.startsWith("pc")) + { + pV->eUnit = MathMLLengthUnit::Pc; + return nIdx + 2; + } + if (sRest[0] == sal_Unicode('%')) + { + pV->eUnit = MathMLLengthUnit::Percent; + return nIdx + 2; + } + return nIdx; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/mathmlattr.hxx b/starmath/source/mathmlattr.hxx new file mode 100644 index 000000000000..75dddb06d402 --- /dev/null +++ b/starmath/source/mathmlattr.hxx @@ -0,0 +1,52 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_STARMATH_SOURCE_MATHMLATTR_HXX +#define INCLUDED_STARMATH_SOURCE_MATHMLATTR_HXX + +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <tools/fract.hxx> + +// MathML 3: 2.1.5.1 Syntax notation used in the MathML specification +// <https://www.w3.org/TR/MathML/chapter2.html#id.2.1.5.1> +// MathML 2: 2.4.4.2 Attributes with units +// <https://www.w3.org/TR/MathML2/chapter2.html#fund.attval> + +sal_Int32 ParseMathMLUnsignedNumber(const OUString &rStr, Fraction *pUN); + +sal_Int32 ParseMathMLNumber(const OUString &rStr, Fraction *pN); + +// MathML 3: 2.1.5.2 Length Valued Attributes +// <https://www.w3.org/TR/MathML/chapter2.html#fund.units> + +enum class MathMLLengthUnit { + None, + Em, + Ex, + Px, + In, + Cm, + Mm, + Pt, + Pc, + Percent +}; + +struct MathMLAttributeLengthValue +{ + Fraction aNumber; + MathMLLengthUnit eUnit; +}; + +sal_Int32 ParseMathMLAttributeLengthValue(const OUString &rStr, MathMLAttributeLengthValue *pV); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/mathmlimport.cxx b/starmath/source/mathmlimport.cxx index b318406234c0..dc0d03212fa0 100644 --- a/starmath/source/mathmlimport.cxx +++ b/starmath/source/mathmlimport.cxx @@ -63,6 +63,7 @@ one go*/ #include <memory> +#include "mathmlattr.hxx" #include "mathmlimport.hxx" #include "register.hxx" #include <starmath.hrc> @@ -1333,17 +1334,80 @@ public: void StartElement(const uno::Reference< xml::sax::XAttributeList >& xAttrList ) override; }; +namespace { + +bool lcl_CountBlanks(const MathMLAttributeLengthValue &rLV, + sal_Int32 *pWide, sal_Int32 *pNarrow) +{ + assert(pWide); + assert(pNarrow); + if (rLV.aNumber.GetNumerator() == 0) + { + *pWide = *pNarrow = 0; + return true; + } + // TODO: honor other units than em + if (rLV.eUnit != MathMLLengthUnit::Em) + return false; + if (rLV.aNumber.GetNumerator() < 0) + return false; + const Fraction aTwo(2, 1); + auto aWide = rLV.aNumber / aTwo; + auto nWide = static_cast<sal_Int32>(static_cast<long>(aWide)); + if (nWide < 0) + return false; + const Fraction aPointFive(1, 2); + auto aNarrow = (rLV.aNumber - Fraction(nWide, 1) * aTwo) / aPointFive; + auto nNarrow = static_cast<sal_Int32>(static_cast<long>(aNarrow)); + if (nNarrow < 0) + return false; + *pWide = nWide; + *pNarrow = nNarrow; + return true; +} + +} + void SmXMLSpaceContext_Impl::StartElement( - const uno::Reference<xml::sax::XAttributeList > & /*xAttrList*/ ) + const uno::Reference<xml::sax::XAttributeList > & xAttrList ) { - // There is not any syntax in Math to specify blank nodes of arbitrary - // size. Hence we always interpret an <mspace> as a large gap "~". + // There is no syntax in Math to specify blank nodes of arbitrary size yet. + MathMLAttributeLengthValue aLV; + sal_Int32 nWide = 0, nNarrow = 0; + + sal_Int16 nAttrCount = xAttrList.is() ? xAttrList->getLength() : 0; + for (sal_Int16 i=0;i<nAttrCount;i++) + { + OUString sAttrName = xAttrList->getNameByIndex(i); + OUString aLocalName; + sal_uInt16 nPrefix = GetImport().GetNamespaceMap().GetKeyByAttrName(sAttrName, &aLocalName); + OUString sValue = xAttrList->getValueByIndex(i); + const SvXMLTokenMap &rAttrTokenMap = GetSmImport().GetMspaceAttrTokenMap(); + switch (rAttrTokenMap.Get(nPrefix, aLocalName)) + { + case XML_TOK_WIDTH: + if ( ParseMathMLAttributeLengthValue(sValue.trim(), &aLV) <= 0 || + !lcl_CountBlanks(aLV, &nWide, &nNarrow) ) + SAL_WARN("starmath", "ignore mspace's width: " << sValue); + break; + default: + break; + } + } SmToken aToken; - aToken.cMathChar = '\0'; aToken.eType = TBLANK; + aToken.cMathChar = '\0'; + aToken.nGroup = TG::Blank; aToken.nLevel = 5; std::unique_ptr<SmBlankNode> pBlank(new SmBlankNode(aToken)); - pBlank->IncreaseBy(aToken); + for (sal_Int32 i = 0; i < nWide; i++) + pBlank->IncreaseBy(aToken); + if (nNarrow > 0) + { + aToken.eType = TSBLANK; + for (sal_Int32 i = 0; i < nNarrow; i++) + pBlank->IncreaseBy(aToken); + } GetSmImport().GetNodeStack().push_front(std::move(pBlank)); } @@ -1898,6 +1962,12 @@ static const SvXMLTokenMapEntry aActionAttrTokenMap[] = XML_TOKEN_MAP_END }; +static const SvXMLTokenMapEntry aMspaceAttrTokenMap[] = +{ + { XML_NAMESPACE_MATH, XML_WIDTH, XML_TOK_WIDTH }, + XML_TOKEN_MAP_END +}; + const SvXMLTokenMap& SmXMLImport::GetPresLayoutElemTokenMap() { @@ -1971,6 +2041,13 @@ const SvXMLTokenMap& SmXMLImport::GetActionAttrTokenMap() return *pActionAttrTokenMap; } +const SvXMLTokenMap& SmXMLImport::GetMspaceAttrTokenMap() +{ + if (!pMspaceAttrTokenMap) + pMspaceAttrTokenMap.reset(new SvXMLTokenMap(aMspaceAttrTokenMap)); + return *pMspaceAttrTokenMap; +} + SvXMLImportContext *SmXMLDocContext_Impl::CreateChildContext( sal_uInt16 nPrefix, diff --git a/starmath/source/mathmlimport.hxx b/starmath/source/mathmlimport.hxx index 04d49c82950d..626ee0bb0fb8 100644 --- a/starmath/source/mathmlimport.hxx +++ b/starmath/source/mathmlimport.hxx @@ -78,6 +78,7 @@ class SmXMLImport : public SvXMLImport std::unique_ptr<SvXMLTokenMap> pPresTableElemTokenMap; std::unique_ptr<SvXMLTokenMap> pColorTokenMap; std::unique_ptr<SvXMLTokenMap> pActionAttrTokenMap; + std::unique_ptr<SvXMLTokenMap> pMspaceAttrTokenMap; SmNodeStack aNodeStack; bool bSuccess; @@ -236,6 +237,7 @@ public: const SvXMLTokenMap &GetPresTableElemTokenMap(); const SvXMLTokenMap &GetColorTokenMap(); const SvXMLTokenMap &GetActionAttrTokenMap(); + const SvXMLTokenMap &GetMspaceAttrTokenMap(); SmNodeStack & GetNodeStack() { return aNodeStack; } @@ -334,6 +336,10 @@ enum SmXMLActionAttrTokenMap XML_TOK_SELECTION }; +enum SmXMLMspaceAttrTokenMap +{ + XML_TOK_WIDTH +}; #endif diff --git a/starmath/source/node.cxx b/starmath/source/node.cxx index febddc2f4785..dfbc8196e723 100644 --- a/starmath/source/node.cxx +++ b/starmath/source/node.cxx @@ -2774,6 +2774,20 @@ void SmBlankNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) SetWidth(nSpace); } +void SmBlankNode::CreateTextFromNode(OUString &rText) +{ + if (mnNum <= 0) + return; + sal_uInt16 nWide = mnNum / 4; + sal_uInt16 nNarrow = mnNum % 4; + for (sal_uInt16 i = 0; i < nWide; i++) + rText += "~"; + for (sal_uInt16 i = 0; i < nNarrow; i++) + rText += "`"; + rText += " "; +} + + /**************************************************************************/ //Implementation of all accept methods for SmVisitor diff --git a/starmath/source/visitors.cxx b/starmath/source/visitors.cxx index d9de2bed20a2..0397c4e8a1f5 100644 --- a/starmath/source/visitors.cxx +++ b/starmath/source/visitors.cxx @@ -2293,7 +2293,16 @@ void SmNodeToTextVisitor::Visit( SmMathSymbolNode* pNode ) void SmNodeToTextVisitor::Visit( SmBlankNode* pNode ) { - Append( pNode->GetToken( ).aText ); + sal_uInt16 nNum = pNode->GetBlankNum(); + if (nNum <= 0) + return; + sal_uInt16 nWide = nNum / 4; + sal_uInt16 nNarrow = nNum % 4; + for (sal_uInt16 i = 0; i < nWide; i++) + Append( "~" ); + for (sal_uInt16 i = 0; i < nNarrow; i++) + Append( "`" ); + Append( " " ); } void SmNodeToTextVisitor::Visit( SmErrorNode* ) |