[Libreoffice-commits] core.git: sw/qa writerfilter/source

László Németh (via logerrit) logerrit at kemper.freedesktop.org
Thu Sep 10 12:42:20 UTC 2020


 sw/qa/extras/ooxmlexport/data/tdf123355.docx             |binary
 sw/qa/extras/ooxmlexport/ooxmlexport15.cxx               |   47 ++++
 writerfilter/source/dmapper/DomainMapperTableHandler.cxx |  147 +++++++++++++++
 writerfilter/source/dmapper/DomainMapper_Impl.cxx        |    6 
 4 files changed, 199 insertions(+), 1 deletion(-)

New commits:
commit 436cf6d31deb7f9594a5da52ec7883d7e3d34344
Author:     László Németh <nemeth at numbertext.org>
AuthorDate: Wed Sep 9 15:08:07 2020 +0200
Commit:     László Németh <nemeth at numbertext.org>
CommitDate: Thu Sep 10 14:41:39 2020 +0200

    tdf#123355 DOCX import: fix cell range ABOVE, BELOW,
    
    LEFT and RIGHT by converting them to Writer cell ranges.
    
    These ranges start at the first cell above/below/right/left
    (including empty cells or cells with text content!) and end
    at the end of the first value cell range. If there is no
    value cell range, they end at the table border.
    
    Note: In MSO, table functions COUNT(), AVERAGE() etc. use
    non-value cells, as zero with ABOVE, BELOW, LEFT and RIGHT,
    but skip them using A1:A10 like range names.
    
    Change-Id: I906f27ecccaa8ded5460cd861ef8808000722860
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/102320
    Tested-by: Jenkins
    Reviewed-by: László Németh <nemeth at numbertext.org>

diff --git a/sw/qa/extras/ooxmlexport/data/tdf123355.docx b/sw/qa/extras/ooxmlexport/data/tdf123355.docx
new file mode 100644
index 000000000000..da36db29f7fe
Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf123355.docx differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport15.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport15.cxx
index b3c3d79962a0..104e867a33ba 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport15.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport15.cxx
@@ -255,6 +255,53 @@ DECLARE_OOXMLEXPORT_TEST(testTdf123354, "tdf123354.fodt")
     CPPUNIT_ASSERT_EQUAL(OUString("0"), xEnumerationAccess3->getPresentation(false).trim());
 }
 
+DECLARE_OOXMLEXPORT_TEST(testTdf123355, "tdf123355.docx")
+{
+    uno::Reference<text::XTextFieldsSupplier> xTextFieldsSupplier(mxComponent, uno::UNO_QUERY);
+    uno::Reference<container::XEnumerationAccess> xFieldsAccess(xTextFieldsSupplier->getTextFields());
+    uno::Reference<container::XEnumeration> xFields(xFieldsAccess->createEnumeration());
+
+    // Tests conversion of range IDs ABOVE, BELOW, LEFT and RIGHT
+    uno::Reference<text::XTextField> xEnumerationAccess1(xFields->nextElement(), uno::UNO_QUERY);
+    // Note: range ends at B4 here, which is a cell with text content
+    CPPUNIT_ASSERT_EQUAL(OUString("average( <B2:B3> )"), xEnumerationAccess1->getPresentation(true).trim());
+    CPPUNIT_ASSERT_EQUAL(OUString("5,5"), xEnumerationAccess1->getPresentation(false).trim());
+
+    // range ends at the end of the empty cells
+    uno::Reference<text::XTextField> xEnumerationAccess6(xFields->nextElement(), uno::UNO_QUERY);
+    CPPUNIT_ASSERT_EQUAL(OUString("SUM(<C6:A6>)"), xEnumerationAccess6->getPresentation(true).trim());
+    CPPUNIT_ASSERT_EQUAL(OUString("0"), xEnumerationAccess6->getPresentation(false).trim());
+
+    // range starts at the first cell above D5
+    uno::Reference<text::XTextField> xEnumerationAccess2(xFields->nextElement(), uno::UNO_QUERY);
+    CPPUNIT_ASSERT_EQUAL(OUString("AVERAGE(<D4:D1>)"), xEnumerationAccess2->getPresentation(true).trim());
+    CPPUNIT_ASSERT_EQUAL(OUString("5,33"), xEnumerationAccess2->getPresentation(false).trim());
+
+    uno::Reference<text::XTextField> xEnumerationAccess3(xFields->nextElement(), uno::UNO_QUERY);
+    CPPUNIT_ASSERT_EQUAL(OUString("AVERAGE(<C2:C1>)"), xEnumerationAccess3->getPresentation(true).trim());
+    CPPUNIT_ASSERT_EQUAL(OUString("3,5"), xEnumerationAccess3->getPresentation(false).trim());
+
+    uno::Reference<text::XTextField> xEnumerationAccess4(xFields->nextElement(), uno::UNO_QUERY);
+    CPPUNIT_ASSERT_EQUAL(OUString("AVERAGE(<D2:D2>)"), xEnumerationAccess4->getPresentation(true).trim());
+    CPPUNIT_ASSERT_EQUAL(OUString("5"), xEnumerationAccess4->getPresentation(false).trim());
+
+    uno::Reference<text::XTextField> xEnumerationAccess5(xFields->nextElement(), uno::UNO_QUERY);
+    CPPUNIT_ASSERT_EQUAL(OUString("AVERAGE(<A2:A2>)"), xEnumerationAccess5->getPresentation(true).trim());
+    CPPUNIT_ASSERT_EQUAL(OUString("4"), xEnumerationAccess5->getPresentation(false).trim());
+
+    xmlDocUniquePtr pXmlDoc = parseExport();
+    if (!pXmlDoc)
+        return;
+
+    // keep original formula IDs
+    assertXPathContent(pXmlDoc, "/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:p/w:r[2]/w:instrText", " =average( below )");
+    assertXPathContent(pXmlDoc, "/w:document/w:body/w:tbl/w:tr[2]/w:tc[2]/w:p/w:r[2]/w:instrText", " =AVERAGE(LEFT)");
+    assertXPathContent(pXmlDoc, "/w:document/w:body/w:tbl/w:tr[2]/w:tc[3]/w:p/w:r[2]/w:instrText", " =AVERAGE(RIGHT)");
+    assertXPathContent(pXmlDoc, "/w:document/w:body/w:tbl/w:tr[3]/w:tc[3]/w:p/w:r[2]/w:instrText", " =AVERAGE(ABOVE)");
+    assertXPathContent(pXmlDoc, "/w:document/w:body/w:tbl/w:tr[5]/w:tc[4]/w:p/w:r[2]/w:instrText", " =AVERAGE(ABOVE)");
+    assertXPathContent(pXmlDoc, "/w:document/w:body/w:tbl/w:tr[6]/w:tc[4]/w:p/w:r[2]/w:instrText", " =SUM(LEFT)");
+}
+
 DECLARE_OOXMLEXPORT_TEST(testTdf98000_changePageStyle, "tdf98000_changePageStyle.odt")
 {
     uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
diff --git a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
index 8d8724f2241d..896950e89110 100644
--- a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
+++ b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
@@ -27,6 +27,7 @@
 #include <com/sun/star/table/XCellRange.hpp>
 #include <com/sun/star/text/HoriOrientation.hpp>
 #include <com/sun/star/text/SizeType.hpp>
+#include <com/sun/star/text/XTextField.hpp>
 #include <com/sun/star/text/XTextRangeCompare.hpp>
 #include <com/sun/star/beans/XPropertySet.hpp>
 #include <com/sun/star/beans/XPropertyState.hpp>
@@ -41,6 +42,8 @@
 #include <tools/diagnose_ex.h>
 #include <comphelper/sequence.hxx>
 #include <comphelper/propertyvalue.hxx>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+#include <boost/lexical_cast.hpp>
 
 #ifdef DBG_UTIL
 #include "PropertyMapHelper.hxx"
@@ -1213,6 +1216,147 @@ void DomainMapperTableHandler::ApplyParagraphPropertiesFromTableStyle(TableParag
     }
 }
 
+// convert formula range identifier ABOVE, BELOW, LEFT and RIGHT
+static void lcl_convertFormulaRanges(const uno::Reference<text::XTextTable> & xTable)
+{
+    uno::Reference<table::XCellRange> xCellRange(xTable, uno::UNO_QUERY_THROW);
+    uno::Reference<container::XIndexAccess> xTableRows(xTable->getRows(), uno::UNO_QUERY_THROW);
+    sal_Int32 nRows = xTableRows->getCount();
+    for (sal_Int32 nRow = 0; nRow < nRows; ++nRow)
+    {
+        sal_Int16 nCol = 0;
+        while (++nCol)
+        {
+            try
+            {
+                uno::Reference<beans::XPropertySet> xCellProperties(xCellRange->getCellByPosition(nCol, nRow), uno::UNO_QUERY_THROW);
+                uno::Sequence<beans::PropertyValue> aCellGrabBag;
+                xCellProperties->getPropertyValue("CellInteropGrabBag") >>= aCellGrabBag;
+                OUString sFormula;
+                bool bReplace = false;
+                for (const auto& rProp : std::as_const(aCellGrabBag))
+                {
+                    if ( rProp.Name == "CellFormulaConverted" )
+                    {
+                        rProp.Value >>= sFormula;
+                        struct RangeDirection
+                        {
+                            OUString m_sName;
+                            sal_Int16 m_nCol;
+                            sal_Int16 m_nRow;
+                        };
+                        static const RangeDirection pDirections[] =
+                        {
+                            { OUString(" LEFT "), -1, 0},
+                            { OUString(" RIGHT "), 1, 0},
+                            { OUString(" ABOVE "), 0, -1},
+                            { OUString(" BELOW "), 0, 1 }
+                        };
+                        for (const RangeDirection& rRange : pDirections)
+                        {
+                            if ( sFormula.indexOf(rRange.m_sName) > -1 )
+                            {
+                                // range starts at the first cell above/below/left/right, but ends at the
+                                // table border or at the first non-value cell after a value cell
+                                bool bFoundFirst = false;
+                                OUString sNextCell;
+                                OUString sLastCell;
+                                OUString sLastValueCell;
+                                // walk through the cells of the range
+                                try
+                                {
+                                    sal_Int32 nCell = 0;
+                                    while (++nCell)
+                                    {
+                                        uno::Reference<beans::XPropertySet> xCell(
+                                                xCellRange->getCellByPosition(nCol + nCell * rRange.m_nCol, nRow + nCell * rRange.m_nRow),
+                                                uno::UNO_QUERY_THROW);
+                                        // empty cell or cell with text content is end of the range
+                                        uno::Reference<text::XText> xText(xCell, uno::UNO_QUERY_THROW);
+                                        sLastCell = xCell->getPropertyValue("CellName").get<OUString>();
+                                        if (sNextCell.isEmpty())
+                                            sNextCell = sLastCell;
+                                        try
+                                        {
+                                            // accept numbers with comma and percent
+                                            OUString sCellText = xText->getString().replace(',', '.');
+                                            if (sCellText.endsWith("%"))
+                                                sCellText = sCellText.copy(0, sCellText.getLength()-1);
+                                            boost::lexical_cast<double>(sCellText);
+                                        }
+                                        catch( boost::bad_lexical_cast const& )
+                                        {
+                                            if ( !bFoundFirst )
+                                            {
+                                                // still search value cells
+                                                continue;
+                                            }
+                                            else
+                                            {
+                                                // end of range
+                                                break;
+                                            }
+                                        }
+                                        sLastValueCell = sLastCell;
+                                        bFoundFirst = true;
+                                    }
+                                }
+                                catch ( const lang::IndexOutOfBoundsException & )
+                                {
+                                }
+
+                                if ( !sNextCell.isEmpty() )
+                                {
+                                    OUString sRange = "<" + sNextCell + ":" +
+                                            ( sLastValueCell.isEmpty() ? sLastCell : sLastValueCell ) + ">";
+                                    sFormula = sFormula.replaceAll(rRange.m_sName, sRange);
+                                    bReplace = true;
+                                }
+                            }
+                        }
+
+                        // update formula field
+                        if (bReplace)
+                        {
+                            uno::Reference<text::XText> xCell(xCellRange->getCellByPosition(nCol, nRow), uno::UNO_QUERY);
+                            uno::Reference<container::XEnumerationAccess> xParaEnumAccess(xCell, uno::UNO_QUERY);
+                            uno::Reference<container::XEnumeration> xParaEnum = xParaEnumAccess->createEnumeration();
+                            uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParaEnum->nextElement(), uno::UNO_QUERY);
+                            uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration();
+                            while ( xRunEnum->hasMoreElements() )
+                            {
+                                uno::Reference<text::XTextRange> xRun(xRunEnum->nextElement(), uno::UNO_QUERY);
+                                uno::Reference< beans::XPropertySet > xRunProperties( xRun, uno::UNO_QUERY_THROW );
+                                if ( xRunProperties->getPropertyValue("TextPortionType") == uno::makeAny(OUString("TextField")) )
+                                {
+                                    uno::Reference<text::XTextField> const xField(xRunProperties->getPropertyValue("TextField").get<uno::Reference<text::XTextField>>());
+                                    uno::Reference< beans::XPropertySet > xFieldProperties( xField, uno::UNO_QUERY_THROW );
+                                    // cell can contain multiple text fields, but only one is handled now (~formula cell)
+                                    if ( rProp.Value != xFieldProperties->getPropertyValue("Content") )
+                                        continue;
+                                    xFieldProperties->setPropertyValue("Content", uno::makeAny(sFormula));
+                                    // update grab bag
+                                    auto aGrabBag = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(aCellGrabBag);
+                                    beans::PropertyValue aValue;
+                                    aValue.Name = "CellFormulaConverted";
+                                    aValue.Value <<= sFormula;
+                                    aGrabBag.push_back(aValue);
+                                    xCellProperties->setPropertyValue("CellInteropGrabBag", uno::makeAny(comphelper::containerToSequence(aGrabBag)));
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            catch ( const lang::IndexOutOfBoundsException & )
+            {
+                // jump to next table row
+                break;
+            }
+        }
+    }
+}
+
 void DomainMapperTableHandler::endTable(unsigned int nestedTableLevel, bool bTableStartsAtCellStart)
 {
 #ifdef DBG_UTIL
@@ -1367,6 +1511,9 @@ void DomainMapperTableHandler::endTable(unsigned int nestedTableLevel, bool bTab
                             }
                         }
                     }
+
+                    // convert special range IDs ABOVE, BELOW, LEFT and RIGHT
+                    lcl_convertFormulaRanges(xTable);
                 }
             }
         }
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index e7ecfffd4338..6387d22b12a3 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -4276,7 +4276,7 @@ OUString DomainMapper_Impl::convertFieldFormula(const OUString& input) {
     usInput = rmatch2.replaceAll(icu::UnicodeString("<$1>"), status);
 
     /* Cell references must be upper case */
-    icu::RegexMatcher rmatch3("<[a-z]{1,3}[0-9]+>", usInput, rMatcherFlags, status);
+    icu::RegexMatcher rmatch3("(<[a-z]{1,3}[0-9]+>|\\b(above|below|left|right)\\b)", usInput, rMatcherFlags, status);
     icu::UnicodeString replacedCellRefs;
     while (rmatch3.find(status) && status.isSuccess()) {
         rmatch3.appendReplacement(replacedCellRefs, rmatch3.group(status).toUpper(), status);
@@ -4291,6 +4291,10 @@ OUString DomainMapper_Impl::convertFieldFormula(const OUString& input) {
     icu::RegexMatcher rmatch5("\\bDEFINED\\s*\\(<([A-Z]+[0-9]+)>\\)", usInput, rMatcherFlags, status);
     usInput = rmatch5.replaceAll(icu::UnicodeString("DEFINED($1)"), status);
 
+    /* Prepare replace of ABOVE/BELOW/LEFT/RIGHT by adding spaces around them */
+    icu::RegexMatcher rmatch6("\\b(ABOVE|BELOW|LEFT|RIGHT)\\b", usInput, rMatcherFlags, status);
+    usInput = rmatch6.replaceAll(icu::UnicodeString(" $1 "), status);
+
     return OUString(usInput.getTerminatedBuffer());
 }
 


More information about the Libreoffice-commits mailing list