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>
This commit is contained in:
Takeshi Abe 2016-11-13 21:06:55 +09:00
parent bb50b1609a
commit 074f0ab1d7
12 changed files with 348 additions and 8 deletions

View File

@ -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 \

View File

@ -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;
};

View File

@ -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>

View File

@ -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}");

View File

@ -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"));

View File

@ -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:
{

View File

@ -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: */

View File

@ -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: */

View File

@ -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;
};
void SmXMLSpaceContext_Impl::StartElement(
const uno::Reference<xml::sax::XAttributeList > & /*xAttrList*/ )
namespace {
bool lcl_CountBlanks(const MathMLAttributeLengthValue &rLV,
sal_Int32 *pWide, sal_Int32 *pNarrow)
{
// There is not any syntax in Math to specify blank nodes of arbitrary
// size. Hence we always interpret an <mspace> as a large gap "~".
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 )
{
// 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,

View File

@ -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

View File

@ -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

View File

@ -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* )