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:
parent
bb50b1609a
commit
074f0ab1d7
@ -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 \
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
12
starmath/qa/extras/data/mspace.mml
Normal file
12
starmath/qa/extras/data/mspace.mml
Normal 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>
|
@ -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}");
|
||||
|
@ -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"));
|
||||
|
@ -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:
|
||||
{
|
||||
|
147
starmath/source/mathmlattr.cxx
Normal file
147
starmath/source/mathmlattr.cxx
Normal 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: */
|
52
starmath/source/mathmlattr.hxx
Normal file
52
starmath/source/mathmlattr.hxx
Normal 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: */
|
@ -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));
|
||||
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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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* )
|
||||
|
Loading…
x
Reference in New Issue
Block a user