Blob Blame History Raw
From 9f15c9ec6fa129bf17da4cb86209a1ff2c013af7 Mon Sep 17 00:00:00 2001
From: Kohei Yoshida <kohei.yoshida@collabora.com>
Date: Wed, 16 Jul 2014 19:59:35 -0400
Subject: [PATCH 040/137] bnc#885548: Initial work on importing revisions from
 xlsx.

Change-Id: Ie0528490d024093cbabf38541fe70be96a9caa2e
(cherry picked from commit b92fdaa1a40bd54d279b4ac2faf19bf15468cc2f)
(cherry picked from commit 6c24bf4cab7e2a9514cf99160a31728835198741)
Signed-off-by: Andras Timar <andras.timar@collabora.com>
---
 sc/Library_scfilt.mk                      |   1 +
 sc/inc/chgtrack.hxx                       |   2 +-
 sc/source/filter/inc/revisionfragment.hxx |  79 +++++
 sc/source/filter/oox/revisionfragment.cxx | 462 ++++++++++++++++++++++++++++++
 sc/source/filter/oox/workbookfragment.cxx |  11 +-
 5 files changed, 553 insertions(+), 2 deletions(-)
 create mode 100644 sc/source/filter/inc/revisionfragment.hxx
 create mode 100644 sc/source/filter/oox/revisionfragment.cxx

diff --git a/sc/Library_scfilt.mk b/sc/Library_scfilt.mk
index eb0d5d2..c034e61 100644
--- a/sc/Library_scfilt.mk
+++ b/sc/Library_scfilt.mk
@@ -198,6 +198,7 @@ $(eval $(call gb_Library_add_exception_objects,scfilt,\
 	sc/source/filter/oox/pivottablefragment \
 	sc/source/filter/oox/querytablebuffer \
 	sc/source/filter/oox/querytablefragment \
+	sc/source/filter/oox/revisionfragment \
 	sc/source/filter/oox/richstringcontext \
 	sc/source/filter/oox/richstring \
 	sc/source/filter/oox/scenariobuffer \
diff --git a/sc/inc/chgtrack.hxx b/sc/inc/chgtrack.hxx
index 5fdb50b..1f6ddcd 100644
--- a/sc/inc/chgtrack.hxx
+++ b/sc/inc/chgtrack.hxx
@@ -1101,7 +1101,7 @@ public:
                         sal_uLong nOldFormat, ScDocument* pRefDoc = NULL );
                         // after new value was set in the document,
                         // old value from pOldCell, format from Doc
-    void AppendContent( const ScAddress& rPos, const ScCellValue& rOldCell );
+    SC_DLLPUBLIC void AppendContent( const ScAddress& rPos, const ScCellValue& rOldCell );
                         // after new values were set in the document,
                         // old values from RefDoc/UndoDoc.
                         // All contents with a cell in RefDoc
diff --git a/sc/source/filter/inc/revisionfragment.hxx b/sc/source/filter/inc/revisionfragment.hxx
new file mode 100644
index 0000000..473549e
--- /dev/null
+++ b/sc/source/filter/inc/revisionfragment.hxx
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * 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_SC_OOX_XLS_REVISIONFRAGMENT_HXX
+#define INCLUDED_SC_OOX_XLS_REVISIONFRAGMENT_HXX
+
+#include <excelhandlers.hxx>
+
+class ScChangeTrack;
+
+namespace oox { namespace xls {
+
+class RevisionHeadersFragment : public WorkbookFragmentBase
+{
+    struct Impl;
+    Impl* mpImpl;
+
+public:
+    explicit RevisionHeadersFragment(
+        const WorkbookHelper& rHelper, const OUString& rFragmentPath );
+
+    virtual ~RevisionHeadersFragment();
+
+protected:
+    virtual oox::core::ContextHandlerRef onCreateContext(
+        sal_Int32 nElement, const AttributeList& rAttribs ) SAL_OVERRIDE;
+
+    virtual void onStartElement( const AttributeList& rAttribs ) SAL_OVERRIDE;
+    virtual void onCharacters( const OUString& rChars ) SAL_OVERRIDE;
+    virtual void onEndElement() SAL_OVERRIDE;
+
+    virtual void finalizeImport() SAL_OVERRIDE;
+
+private:
+    void importHeaders( const AttributeList& rAttribs );
+    void importHeader( const AttributeList& rAttribs );
+};
+
+class RevisionLogFragment : public WorkbookFragmentBase
+{
+    struct Impl;
+    Impl* mpImpl;
+
+public:
+    explicit RevisionLogFragment(
+        const WorkbookHelper& rHelper, const OUString& rFragmentPath, ScChangeTrack& rChangeTrack );
+
+    virtual ~RevisionLogFragment();
+
+protected:
+    virtual oox::core::ContextHandlerRef onCreateContext(
+        sal_Int32 nElement, const AttributeList& rAttribs ) SAL_OVERRIDE;
+
+    virtual void onStartElement( const AttributeList& rAttribs ) SAL_OVERRIDE;
+    virtual void onCharacters( const OUString& rChars ) SAL_OVERRIDE;
+    virtual void onEndElement() SAL_OVERRIDE;
+
+    virtual void finalizeImport() SAL_OVERRIDE;
+
+private:
+    void importCommon( const AttributeList& rAttribs );
+    void importRcc( const AttributeList& rAttribs );
+    void importRrc( const AttributeList& rAttribs );
+
+    void pushRevision();
+};
+
+}}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+
diff --git a/sc/source/filter/oox/revisionfragment.cxx b/sc/source/filter/oox/revisionfragment.cxx
new file mode 100644
index 0000000..dd8cc4b
--- /dev/null
+++ b/sc/source/filter/oox/revisionfragment.cxx
@@ -0,0 +1,462 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * 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 <revisionfragment.hxx>
+#include <oox/core/relations.hxx>
+#include <oox/core/xmlfilterbase.hxx>
+#include <oox/core/fastparser.hxx>
+#include <o3tl/heap_ptr.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <sax/tools/converter.hxx>
+#include <editeng/editobj.hxx>
+
+#include <chgtrack.hxx>
+#include <document.hxx>
+#include <compiler.hxx>
+#include <editutil.hxx>
+#include <formulacell.hxx>
+#include <chgviset.hxx>
+#include <richstringcontext.hxx>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <com/sun/star/util/DateTime.hpp>
+
+using namespace com::sun::star;
+
+namespace oox { namespace xls {
+
+namespace {
+
+enum RevisionType
+{
+    REV_UNKNOWN = 0,
+    REV_CELLCHANGE,
+    REV_INSERTROW,
+    REV_DELETEROW,
+    REV_INSERTCOL,
+    REV_DELETECOL
+};
+
+/**
+ * For nc (new cell) or oc (old cell) elements under rcc (cell content
+ * revision).
+ */
+class RCCCellValueContext : public WorkbookContextBase
+{
+    sal_Int32 mnSheetIndex;
+    ScAddress& mrPos;
+    ScCellValue& mrCellValue;
+    sal_Int32 mnType;
+
+    RichStringRef mxRichString;
+
+public:
+    RCCCellValueContext(
+        RevisionLogFragment& rParent, sal_Int32 nSheetIndex, ScAddress& rPos, ScCellValue& rCellValue ) :
+        WorkbookContextBase(rParent),
+        mnSheetIndex(nSheetIndex),
+        mrPos(rPos),
+        mrCellValue(rCellValue),
+        mnType(-1) {}
+
+protected:
+    virtual oox::core::ContextHandlerRef onCreateContext(
+        sal_Int32 nElement, const AttributeList& /*rAttribs*/ ) SAL_OVERRIDE
+    {
+        if (nElement == XLS_TOKEN(is))
+        {
+            mxRichString.reset(new RichString(*this));
+            return new RichStringContext(*this, mxRichString);
+        }
+
+        return this;
+    }
+
+    virtual void onStartElement( const AttributeList& rAttribs ) SAL_OVERRIDE
+    {
+        switch (getCurrentElement())
+        {
+            case XLS_TOKEN(nc):
+            case XLS_TOKEN(oc):
+                importCell(rAttribs);
+            break;
+            default:
+                ;
+        }
+    }
+
+    virtual void onCharacters( const OUString& rChars ) SAL_OVERRIDE
+    {
+        switch (getCurrentElement())
+        {
+            case XLS_TOKEN(v):
+            {
+                if (mnType == XML_n || mnType == XML_b)
+                    mrCellValue.set(rChars.toDouble());
+            }
+            break;
+            case XLS_TOKEN(t):
+            {
+                if (mnType == XML_inlineStr)
+                {
+                    ScDocument& rDoc = getScDocument();
+                    svl::SharedStringPool& rPool = rDoc.GetSharedStringPool();
+                    mrCellValue.set(rPool.intern(rChars));
+                }
+            }
+            break;
+            case XLS_TOKEN(f):
+            {
+                // formula string
+                ScDocument& rDoc = getScDocument();
+                ScCompiler aComp(&rDoc, mrPos);
+                aComp.SetGrammar(formula::FormulaGrammar::GRAM_OOXML);
+                ScTokenArray* pArray = aComp.CompileString(rChars);
+                if (!pArray)
+                    break;
+
+                mrCellValue.set(new ScFormulaCell(&rDoc, mrPos, pArray));
+            }
+            break;
+            default:
+                ;
+        }
+    }
+
+    virtual void onEndElement() SAL_OVERRIDE
+    {
+        switch (getCurrentElement())
+        {
+            case XLS_TOKEN(nc):
+            case XLS_TOKEN(oc):
+            {
+                if (mrCellValue.isEmpty() && mxRichString)
+                {
+                    // The value is a rich text string.
+                    ScDocument& rDoc = getScDocument();
+                    EditTextObject* pTextObj = mxRichString->convert(rDoc.GetEditEngine(), NULL);
+                    if (pTextObj)
+                    {
+                        svl::SharedStringPool& rPool = rDoc.GetSharedStringPool();
+                        pTextObj->NormalizeString(rPool);
+                        mrCellValue.set(pTextObj);
+                    }
+                }
+            }
+            break;
+            default:
+                ;
+        }
+    }
+
+private:
+    void importCell( const AttributeList& rAttribs )
+    {
+        mnType = rAttribs.getToken(XML_t, XML_n);
+        OUString aRefStr = rAttribs.getString(XML_r, OUString());
+        if (!aRefStr.isEmpty())
+        {
+            mrPos.Parse(aRefStr, NULL, formula::FormulaGrammar::CONV_XL_OOX);
+            if (mnSheetIndex != -1)
+                mrPos.SetTab(mnSheetIndex-1);
+        }
+    }
+};
+
+struct RevisionMetadata
+{
+    OUString maUserName;
+    DateTime maDateTime;
+
+    RevisionMetadata() : maDateTime(DateTime::EMPTY) {}
+    RevisionMetadata( const RevisionMetadata& r ) :
+        maUserName(r.maUserName), maDateTime(r.maDateTime) {}
+};
+
+}
+
+typedef std::map<OUString, RevisionMetadata> RevDataType;
+
+struct RevisionHeadersFragment::Impl
+{
+    RevDataType maRevData;
+
+    Impl() {}
+};
+
+RevisionHeadersFragment::RevisionHeadersFragment(
+    const WorkbookHelper& rHelper, const OUString& rFragmentPath ) :
+    WorkbookFragmentBase(rHelper, rFragmentPath),
+    mpImpl(new Impl) {}
+
+RevisionHeadersFragment::~RevisionHeadersFragment()
+{
+    delete mpImpl;
+}
+
+oox::core::ContextHandlerRef RevisionHeadersFragment::onCreateContext(
+    sal_Int32 /*nElement*/, const AttributeList& /*rAttribs*/ )
+{
+    return this;
+}
+
+void RevisionHeadersFragment::onStartElement( const AttributeList& rAttribs )
+{
+    switch (getCurrentElement())
+    {
+        case XLS_TOKEN(headers):
+            importHeaders(rAttribs);
+        break;
+        case XLS_TOKEN(header):
+            importHeader(rAttribs);
+        break;
+        case XLS_TOKEN(sheetIdMap):
+        break;
+        case XLS_TOKEN(sheetId):
+        break;
+        default:
+            ;
+    }
+}
+
+void RevisionHeadersFragment::onCharacters( const OUString& /*rChars*/ ) {}
+
+void RevisionHeadersFragment::onEndElement()
+{
+    switch (getCurrentElement())
+    {
+        case XLS_TOKEN(headers):
+        break;
+        case XLS_TOKEN(header):
+        break;
+        case XLS_TOKEN(sheetIdMap):
+        break;
+        case XLS_TOKEN(sheetId):
+        break;
+        default:
+            ;
+    }
+}
+
+void RevisionHeadersFragment::finalizeImport()
+{
+    ScDocument& rDoc = getScDocument();
+    o3tl::heap_ptr<ScChangeTrack> pCT(new ScChangeTrack(&rDoc));
+    pCT->SetUseFixDateTime(true);
+
+    const oox::core::Relations& rRels = getRelations();
+    RevDataType::const_iterator it = mpImpl->maRevData.begin(), itEnd = mpImpl->maRevData.end();
+    for (; it != itEnd; ++it)
+    {
+        OUString aPath = rRels.getFragmentPathFromRelId(it->first);
+        if (aPath.isEmpty())
+            continue;
+
+        // Parse each reivison log fragment.
+        const RevisionMetadata& rData = it->second;
+        pCT->SetUser(rData.maUserName);
+        pCT->SetFixDateTimeLocal(rData.maDateTime);
+        boost::scoped_ptr<oox::core::FastParser> xParser(getOoxFilter().createParser());
+        rtl::Reference<oox::core::FragmentHandler> xFragment(new RevisionLogFragment(*this, aPath, *pCT));
+        importOoxFragment(xFragment, *xParser);
+    }
+
+    rDoc.SetChangeTrack(pCT.release());
+
+    // Turn on visibility of tracked changes.
+    ScChangeViewSettings aSettings;
+    aSettings.SetShowChanges(true);
+    rDoc.SetChangeViewSettings(aSettings);
+}
+
+void RevisionHeadersFragment::importHeaders( const AttributeList& /*rAttribs*/ )
+{
+    // Nothing for now.
+}
+
+void RevisionHeadersFragment::importHeader( const AttributeList& rAttribs )
+{
+    OUString aRId = rAttribs.getString(R_TOKEN(id), OUString());
+    if (aRId.isEmpty())
+        // All bets are off if we don't have a relation ID.
+        return;
+
+    RevisionMetadata aMetadata;
+    OUString aDateTimeStr = rAttribs.getString(XML_dateTime, OUString());
+    if (!aDateTimeStr.isEmpty())
+    {
+        util::DateTime aDateTime;
+        sax::Converter::parseDateTime(aDateTime, 0, aDateTimeStr);
+        Date aDate(aDateTime.Day, aDateTime.Month, aDateTime.Year);
+        Time aTime(aDateTime.Hours, aDateTime.Minutes, aDateTime.Seconds, aDateTime.NanoSeconds);
+        aMetadata.maDateTime.SetDate(aDate.GetDate());
+        aMetadata.maDateTime.SetTime(aTime.GetTime());
+    }
+
+    aMetadata.maUserName = rAttribs.getString(XML_userName, OUString());
+
+    mpImpl->maRevData.insert(RevDataType::value_type(aRId, aMetadata));
+}
+
+struct RevisionLogFragment::Impl
+{
+    ScChangeTrack& mrChangeTrack;
+
+    sal_Int32 mnRevIndex;
+    sal_Int32 mnSheetIndex;
+
+    RevisionType meType;
+
+    // rcc
+    ScAddress maOldCellPos;
+    ScAddress maNewCellPos;
+    ScCellValue maOldCellValue;
+    ScCellValue maNewCellValue;
+
+    // rrc
+    ScRange maRange;
+
+    bool mbEndOfList;
+
+    Impl( ScChangeTrack& rChangeTrack ) :
+        mrChangeTrack(rChangeTrack),
+        mnRevIndex(-1),
+        mnSheetIndex(-1),
+        meType(REV_UNKNOWN),
+        mbEndOfList(false) {}
+};
+
+RevisionLogFragment::RevisionLogFragment(
+    const WorkbookHelper& rHelper, const OUString& rFragmentPath, ScChangeTrack& rChangeTrack ) :
+    WorkbookFragmentBase(rHelper, rFragmentPath),
+    mpImpl(new Impl(rChangeTrack)) {}
+
+RevisionLogFragment::~RevisionLogFragment()
+{
+    delete mpImpl;
+}
+
+oox::core::ContextHandlerRef RevisionLogFragment::onCreateContext(
+    sal_Int32 nElement, const AttributeList& /*rAttribs*/ )
+{
+    switch (nElement)
+    {
+        case XLS_TOKEN(nc):
+            return new RCCCellValueContext(*this, mpImpl->mnSheetIndex, mpImpl->maNewCellPos, mpImpl->maNewCellValue);
+        case XLS_TOKEN(oc):
+            return new RCCCellValueContext(*this, mpImpl->mnSheetIndex, mpImpl->maOldCellPos, mpImpl->maOldCellValue);
+        default:
+            ;
+    }
+    return this;
+}
+
+void RevisionLogFragment::onStartElement( const AttributeList& rAttribs )
+{
+    switch (getCurrentElement())
+    {
+        case XLS_TOKEN(rcc):
+            mpImpl->maNewCellPos.SetInvalid();
+            mpImpl->maOldCellPos.SetInvalid();
+            mpImpl->maNewCellValue.clear();
+            mpImpl->maOldCellValue.clear();
+            importRcc(rAttribs);
+        break;
+        case XLS_TOKEN(rrc):
+            importRrc(rAttribs);
+        break;
+        default:
+            ;
+    }
+}
+
+void RevisionLogFragment::onCharacters( const OUString& /*rChars*/ ) {}
+
+void RevisionLogFragment::onEndElement()
+{
+    switch (getCurrentElement())
+    {
+        case XLS_TOKEN(rcc):
+        case XLS_TOKEN(rrc):
+            pushRevision();
+        break;
+        default:
+            ;
+    }
+}
+
+void RevisionLogFragment::finalizeImport() {}
+
+void RevisionLogFragment::importCommon( const AttributeList& rAttribs )
+{
+    mpImpl->mnRevIndex   = rAttribs.getInteger(XML_rId, -1);
+    mpImpl->mnSheetIndex = rAttribs.getInteger(XML_sId, -1);
+}
+
+void RevisionLogFragment::importRcc( const AttributeList& rAttribs )
+{
+    importCommon(rAttribs);
+
+    mpImpl->meType = REV_CELLCHANGE;
+}
+
+void RevisionLogFragment::importRrc( const AttributeList& rAttribs )
+{
+    importCommon(rAttribs);
+
+    if (mpImpl->mnSheetIndex == -1)
+        // invalid sheet index, or sheet index not given.
+        return;
+
+    mpImpl->meType = REV_UNKNOWN;
+    sal_Int32 nAction = rAttribs.getToken(XML_action, -1);
+    if (nAction == -1)
+        return;
+
+    OUString aRefStr = rAttribs.getString(XML_ref, OUString());
+    mpImpl->maRange.Parse(aRefStr, &getScDocument(), formula::FormulaGrammar::CONV_XL_OOX);
+    if (!mpImpl->maRange.IsValid())
+        return;
+
+    switch (nAction)
+    {
+        case XML_insertRow:
+            mpImpl->meType = REV_INSERTROW;
+            mpImpl->maRange.aEnd.SetCol(MAXCOL);
+            mpImpl->maRange.aStart.SetTab(mpImpl->mnSheetIndex-1);
+            mpImpl->maRange.aEnd.SetTab(mpImpl->mnSheetIndex-1);
+        break;
+        default:
+            // Unknown action type.  Ignore it.
+            return;
+    }
+
+    mpImpl->mbEndOfList = rAttribs.getBool(XML_eol, false);
+}
+
+void RevisionLogFragment::pushRevision()
+{
+    switch (mpImpl->meType)
+    {
+        case REV_CELLCHANGE:
+            mpImpl->mrChangeTrack.AppendContentOnTheFly(
+                mpImpl->maNewCellPos, mpImpl->maOldCellValue, mpImpl->maNewCellValue);
+        break;
+        case REV_INSERTROW:
+            mpImpl->mrChangeTrack.AppendInsert(mpImpl->maRange, mpImpl->mbEndOfList);
+        break;
+        default:
+            ;
+    }
+}
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/oox/workbookfragment.cxx b/sc/source/filter/oox/workbookfragment.cxx
index bf42f46..283aa74 100644
--- a/sc/source/filter/oox/workbookfragment.cxx
+++ b/sc/source/filter/oox/workbookfragment.cxx
@@ -36,6 +36,7 @@
 #include "pivotcachebuffer.hxx"
 #include "sharedstringsbuffer.hxx"
 #include "sharedstringsfragment.hxx"
+#include "revisionfragment.hxx"
 #include "stylesfragment.hxx"
 #include "tablebuffer.hxx"
 #include "themebuffer.hxx"
@@ -300,7 +301,7 @@ public:
     }
 };
 
-static void importSheetFragments( WorkbookFragment& rWorkbookHandler, SheetFragmentVector& rSheets )
+void importSheetFragments( WorkbookFragment& rWorkbookHandler, SheetFragmentVector& rSheets )
 {
     sal_Int32 nThreads = std::min( rSheets.size(), (size_t) 4 /* FIXME: ncpus/2 */ );
 
@@ -499,6 +500,14 @@ void WorkbookFragment::finalizeImport()
 
     // final conversions, e.g. calculation settings and view settings
     finalizeWorkbookImport();
+
+    OUString aRevHeadersPath = getFragmentPathFromFirstType(CREATE_OFFICEDOC_RELATION_TYPE("revisionHeaders"));
+    if (!aRevHeadersPath.isEmpty())
+    {
+        boost::scoped_ptr<oox::core::FastParser> xParser(getOoxFilter().createParser());
+        rtl::Reference<oox::core::FragmentHandler> xFragment(new RevisionHeadersFragment(*this, aRevHeadersPath));
+        importOoxFragment(xFragment, *xParser);
+    }
 }
 
 // private --------------------------------------------------------------------
-- 
1.9.3