/* -*- 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_SW_QA_INC_SWMODELTESTBASE_HXX
#define INCLUDED_SW_QA_INC_SWMODELTESTBASE_HXX

#include <memory>
#include <com/sun/star/container/XContentEnumerationAccess.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/document/XFilter.hpp>
#include <com/sun/star/document/XImporter.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/packages/zip/ZipFileAccess.hpp>
#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
#include <com/sun/star/style/XAutoStylesSupplier.hpp>
#include <com/sun/star/style/XAutoStyleFamily.hpp>
#include <com/sun/star/text/XPageCursor.hpp>
#include <com/sun/star/text/XTextDocument.hpp>
#include <com/sun/star/text/XTextRange.hpp>
#include <com/sun/star/text/XTextTable.hpp>
#include <com/sun/star/text/XTextViewCursorSupplier.hpp>
#include <com/sun/star/table/XCell.hpp>
#include <com/sun/star/table/BorderLine2.hpp>
#include <com/sun/star/xml/AttributeData.hpp>

#include <test/bootstrapfixture.hxx>
#include <test/xmltesttools.hxx>
#include <test/testinteractionhandler.hxx>
#include <unotest/macros_test.hxx>
#include <unotools/streamwrap.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <rtl/ustrbuf.hxx>
#include <sfx2/app.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/sequence.hxx>
#include <unotools/tempfile.hxx>
#include <unotools/mediadescriptor.hxx>

#include <unotxdoc.hxx>
#include <docsh.hxx>
#include <doc.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <rootfrm.hxx>

using namespace css;

/**
 * Macro to declare a new test (with full round-trip. To test
 * import only use the DECLARE_SW_IMPORT_TEST macro instead).
 * In order to add a new test, one only needs to use this macro
 * and then specify the test content, like this:
 *
 * DECLARE_SW_ROUNDTRIP_TEST(MyTest, "myfilename.docx", Test)
 * {
 *      CPPUNIT_ASSERT_EQUAL(blabla);
 * }
 *
 */
#define DECLARE_SW_ROUNDTRIP_TEST(TestName, filename, password, BaseClass) \
    class TestName : public BaseClass { \
        protected:\
    virtual OUString getTestName() override { return #TestName; } \
        public:\
    CPPUNIT_TEST_SUITE(TestName); \
    CPPUNIT_TEST(Load_Verify_Reload_Verify); \
    CPPUNIT_TEST_SUITE_END(); \
    \
    void Load_Verify_Reload_Verify() {\
        executeLoadVerifyReloadVerify(filename, password);\
    }\
    void verify() override;\
    }; \
    CPPUNIT_TEST_SUITE_REGISTRATION(TestName); \
    void TestName::verify()

#define DECLARE_SW_EXPORTONLY_TEST(TestName, filename, password, BaseClass) \
    class TestName : public BaseClass { \
        protected:\
    virtual OUString getTestName() override { return #TestName; } \
        public:\
    CPPUNIT_TEST_SUITE(TestName); \
    CPPUNIT_TEST(Load_Reload_Verify); \
    CPPUNIT_TEST_SUITE_END(); \
    \
    void Load_Reload_Verify() {\
        executeLoadReloadVerify(filename, password);\
    }\
    void verify() override;\
    }; \
    CPPUNIT_TEST_SUITE_REGISTRATION(TestName); \
    void TestName::verify()

#define DECLARE_OOXMLIMPORT_TEST(TestName, filename) DECLARE_SW_IMPORT_TEST(TestName, filename, nullptr, Test)
#define DECLARE_OOXMLEXPORT_TEST(TestName, filename) DECLARE_SW_ROUNDTRIP_TEST(TestName, filename, nullptr, Test)
#define DECLARE_OOXMLEXPORT_EXPORTONLY_TEST(TestName, filename) DECLARE_SW_EXPORTONLY_TEST(TestName, filename, nullptr, Test)
#define DECLARE_RTFEXPORT_TEST(TestName, filename) DECLARE_SW_ROUNDTRIP_TEST(TestName, filename, nullptr, Test)
#define DECLARE_ODFIMPORT_TEST(TestName, filename) DECLARE_SW_IMPORT_TEST(TestName, filename, nullptr, Test)
#define DECLARE_ODFEXPORT_TEST(TestName, filename) DECLARE_SW_ROUNDTRIP_TEST(TestName, filename, nullptr, Test)
#define DECLARE_FODFEXPORT_TEST(TestName, filename) DECLARE_SW_ROUNDTRIP_TEST(TestName, filename, nullptr, Test)
#define DECLARE_WW8EXPORT_TEST(TestName, filename) DECLARE_SW_ROUNDTRIP_TEST(TestName, filename, nullptr, Test)

#define DECLARE_SW_IMPORT_TEST(TestName, filename, password, BaseClass) \
    class TestName : public BaseClass { \
        protected:\
    virtual OUString getTestName() override { return #TestName; } \
        public:\
    CPPUNIT_TEST_SUITE(TestName); \
    CPPUNIT_TEST(Import); \
    CPPUNIT_TEST_SUITE_END(); \
    \
    void Import() { \
        executeImportTest(filename, password);\
    }\
    void verify() override;\
    }; \
    CPPUNIT_TEST_SUITE_REGISTRATION(TestName); \
    void TestName::verify()

#define DECLARE_SW_EXPORT_TEST(TestName, filename, password, BaseClass) \
    class TestName : public BaseClass { \
        protected:\
    virtual OUString getTestName() override { return #TestName; } \
        public:\
    CPPUNIT_TEST_SUITE(TestName); \
    CPPUNIT_TEST(Import_Export); \
    CPPUNIT_TEST_SUITE_END(); \
    \
    void Import_Export() {\
        executeImportExport(filename, password);\
    }\
    void verify() override;\
    }; \
    CPPUNIT_TEST_SUITE_REGISTRATION(TestName); \
    void TestName::verify()

/// Base class for filter tests loading or roundtripping a document, then asserting the document model.
class SwModelTestBase : public test::BootstrapFixture, public unotest::MacrosTest, public XmlTestTools
{
private:
    OUString maFilterOptions;
    OUString maImportFilterOptions;
    OUString maImportFilterName;

protected:
    uno::Reference< lang::XComponent > mxComponent;

    rtl::Reference<TestInteractionHandler> xInteractionHandler;

    xmlBufferPtr mpXmlBuffer;
    const OUString mpTestDocumentPath;
    const char* mpFilter;

    sal_uInt32 mnStartTime;
    utl::TempFile maTempFile;
    bool mbExported; ///< Does maTempFile already contain something useful?

protected:

    class Resetter
    {
    private:
        std::function<void ()> m_Func;

    public:
        Resetter(std::function<void ()> const& rFunc)
            : m_Func(rFunc)
        {
        }
        ~Resetter()
        {
            try
            {
                m_Func();
            }
            catch (...) // has to be reliable
            {
                fprintf(stderr, "resetter failed with exception\n");
                abort();
            }
        }
    };

    virtual OUString getTestName() { return OUString(); }

    /// Copy&paste helper.
    void paste(const OUString& aFilename, uno::Reference<text::XTextRange> const& xTextRange)
    {
        uno::Reference<document::XFilter> xFilter(
            m_xSFactory->createInstance("com.sun.star.comp.Writer.RtfFilter"),
            uno::UNO_QUERY_THROW);
        uno::Reference<document::XImporter> xImporter(xFilter, uno::UNO_QUERY_THROW);
        xImporter->setTargetDocument(mxComponent);
        uno::Sequence<beans::PropertyValue> aDescriptor(3);
        aDescriptor[0].Name = "InputStream";
        std::unique_ptr<SvStream> pStream = utl::UcbStreamHelper::CreateStream(
            m_directories.getURLFromSrc("/sw/qa/extras/") + aFilename,
            StreamMode::STD_READ);
        CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, pStream->GetError());
        uno::Reference<io::XStream> xStream(new utl::OStreamWrapper(std::move(pStream)));
        aDescriptor[0].Value <<= xStream;
        aDescriptor[1].Name = "InsertMode";
        aDescriptor[1].Value <<= true;
        aDescriptor[2].Name = "TextInsertModeRange";
        aDescriptor[2].Value <<= xTextRange;
        CPPUNIT_ASSERT(xFilter->filter(aDescriptor));
    }

public:
    void setFilterOptions(const OUString &rFilterOptions)
    {
        maFilterOptions = rFilterOptions;
    }

    void setImportFilterOptions(const OUString &rFilterOptions)
    {
        maImportFilterOptions = rFilterOptions;
    }

    void setImportFilterName(const OUString &rFilterName)
    {
        maImportFilterName = rFilterName;
    }

    SwModelTestBase(const OUString& pTestDocumentPath = OUString(), const char* pFilter = "")
        : mpXmlBuffer(nullptr)
        , mpTestDocumentPath(pTestDocumentPath)
        , mpFilter(pFilter)
        , mnStartTime(0)
        , mbExported(false)
    {
        maTempFile.EnableKillingFile();
    }

    virtual void setUp() override
    {
        test::BootstrapFixture::setUp();
        mxDesktop.set(css::frame::Desktop::create(comphelper::getComponentContext(getMultiServiceFactory())));
        SfxApplication::GetOrCreate();
    }

    virtual void tearDown() override
    {
        if (mxComponent.is())
            mxComponent->dispose();

        test::BootstrapFixture::tearDown();
    }

protected:
    /**
     * Helper func used by each unit test to test the 'import' code.
     * (Loads the requested file and then calls 'verify' method)
     */
    void executeImportTest(const char* filename, const char* pPassword = nullptr)
    {
        // If the testcase is stored in some other format, it's pointless to test.
        if (mustTestImportOf(filename))
        {
            maTempFile.EnableKillingFile(false);
            header();
            std::unique_ptr<Resetter> const pChanges(preTest(filename));
            load(mpTestDocumentPath, filename, pPassword);
            verify();
            finish();
            maTempFile.EnableKillingFile();
        }
    }

    /**
     * Helper func used by each unit test to test the 'export' code.
     * (Loads the requested file, calls 'verify' function, save it to temp file, load the
     * temp file and then calls 'verify' function again)
     */
    void executeLoadVerifyReloadVerify(const char* filename, const char* pPassword = nullptr)
    {
        maTempFile.EnableKillingFile(false);
        header();
        std::unique_ptr<Resetter> const pChanges(preTest(filename));
        load(mpTestDocumentPath, filename, pPassword);
        if (mustTestImportOf(filename))
        {
            verify();
        }
        postLoad(filename);
        reload(mpFilter, filename, pPassword);
        verify();
        finish();
        maTempFile.EnableKillingFile();
    }

    /**
     * Helper func used by each unit test to test the 'export' code.
     * (Loads the requested file, save it to temp file, load the
     * temp file and then calls 'verify' method)
     */
    void executeLoadReloadVerify(const char* filename, const char* pPassword = nullptr)
    {
        maTempFile.EnableKillingFile(false);
        header();
        std::unique_ptr<Resetter> const pChanges(preTest(filename));
        load(mpTestDocumentPath, filename, pPassword);
        postLoad(filename);
        reload(mpFilter, filename, pPassword);
        verify();
        finish();
        maTempFile.EnableKillingFile();
    }

    /**
     * Helper func used by each unit test to test the 'export' code.
     * (Loads the requested file for document base (this represents
     * the initial document condition), exports with the desired
     * export filter and then calls 'verify' method)
     */
    void executeImportExport(const char* filename, const char* pPassword)
    {
        maTempFile.EnableKillingFile(false);
        header();
        std::unique_ptr<Resetter> const pChanges(preTest(filename));
        load(mpTestDocumentPath, filename, pPassword);
        save(OUString::createFromAscii(mpFilter), maTempFile);
        maTempFile.EnableKillingFile(false);
        verify();
        finish();
        maTempFile.EnableKillingFile();
    }

    /**
     * Function overridden by unit test. See DECLARE_SW_*_TEST macros
     */
    virtual void verify()
    {
        CPPUNIT_FAIL( "verify method must be overridden" );
    }

    /**
     * Override this function if interested in skipping import test for this file
     */
     virtual bool mustTestImportOf(const char* /* filename */) const
     {
        return true;
     }
    /**
     * Override this function if some special filename-specific setup is needed
     */
    virtual std::unique_ptr<Resetter> preTest(const char* /*filename*/)
    {
        return nullptr;
    }

    /// Override this function if some special file-specific setup is needed during export test: after load, but before save.
    virtual void postLoad(const char* /*pFilename*/)
    {
    }

    /**
     * Override this function if calc layout is not needed
     */
    virtual bool mustCalcLayoutOf(const char* /*filename*/)
    {
        return true;
    }

    /**
     * Override this function if validation is wanted
     */
    virtual bool mustValidate(const char* /*filename*/) const
    {
        return false;
    }

protected:
    void dumpLayout(const uno::Reference< lang::XComponent > & rComponent)
    {
        // create the xml writer
        mpXmlBuffer = xmlBufferCreate();
        xmlTextWriterPtr pXmlWriter = xmlNewTextWriterMemory(mpXmlBuffer, 0);
        xmlTextWriterStartDocument(pXmlWriter, nullptr, nullptr, nullptr);

        // create the dump
        SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(rComponent.get());
        CPPUNIT_ASSERT(pTextDoc);
        SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
        SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
        pLayout->dumpAsXml(pXmlWriter);

        // delete xml writer
        xmlTextWriterEndDocument(pXmlWriter);
        xmlFreeTextWriter(pXmlWriter);
    }

    void discardDumpedLayout()
    {
        if (mpXmlBuffer)
        {
            xmlBufferFree(mpXmlBuffer);
            mpXmlBuffer = nullptr;
        }
    }

    void calcLayout()
    {
        SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
        CPPUNIT_ASSERT(pTextDoc);
        SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
        pDoc->getIDocumentLayoutAccess().GetCurrentViewShell()->CalcLayout();
    }

    /// Get the length of the whole document.
    int getLength() const
    {
        uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
        uno::Reference<container::XEnumerationAccess> xParaEnumAccess(xTextDocument->getText(), uno::UNO_QUERY);
        uno::Reference<container::XEnumeration> xParaEnum = xParaEnumAccess->createEnumeration();
        OUStringBuffer aBuf;
        while (xParaEnum->hasMoreElements())
        {
            uno::Reference<container::XEnumerationAccess> xRangeEnumAccess(xParaEnum->nextElement(), uno::UNO_QUERY);
            uno::Reference<container::XEnumeration> xRangeEnum = xRangeEnumAccess->createEnumeration();
            while (xRangeEnum->hasMoreElements())
            {
                uno::Reference<text::XTextRange> xRange(xRangeEnum->nextElement(), uno::UNO_QUERY);
                aBuf.append(xRange->getString());
            }
        }
        return aBuf.getLength();
    }

    /// Get a family of styles, see com.sun.star.style.StyleFamilies for possible values.
    uno::Reference<container::XNameAccess> getStyles(const OUString& aFamily)
    {
        uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(mxComponent, uno::UNO_QUERY);
        uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies();
        uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(aFamily), uno::UNO_QUERY);
        return xStyleFamily;
    }

    /// Get a family of auto styles, see com.sun.star.style.StyleFamilies for possible values.
    uno::Reference<style::XAutoStyleFamily> getAutoStyles(const OUString& aFamily)
    {
        uno::Reference< style::XAutoStylesSupplier > xAutoStylesSupplier(mxComponent, uno::UNO_QUERY);
        uno::Reference< style::XAutoStyles > xAutoStyles(xAutoStylesSupplier->getAutoStyles());
        uno::Reference< style::XAutoStyleFamily > xAutoStyleFamily(xAutoStyles->getByName(aFamily), uno::UNO_QUERY);
        return xAutoStyleFamily;
    }

    /// Similar to parseExport(), but this gives the xmlDocPtr of the layout dump.
    xmlDocUniquePtr parseLayoutDump()
    {
        if (!mpXmlBuffer)
            dumpLayout(mxComponent);

        return xmlDocUniquePtr(xmlParseMemory(reinterpret_cast<const char*>(xmlBufferContent(mpXmlBuffer)), xmlBufferLength(mpXmlBuffer)));
    }

    /**
     * Extract a value from the layout dump using an XPath expression and an attribute name.
     *
     * If the attribute is omitted, the text of the node is returned.
     */
    OUString parseDump(const OString& aXPath, const OString& aAttribute = OString())
    {
        xmlDocUniquePtr pXmlDoc = parseLayoutDump();

        xmlXPathContextPtr pXmlXpathCtx = xmlXPathNewContext(pXmlDoc.get());
        xmlXPathObjectPtr pXmlXpathObj = xmlXPathEvalExpression(BAD_CAST(aXPath.getStr()), pXmlXpathCtx);
        CPPUNIT_ASSERT_MESSAGE("xpath evaluation failed", pXmlXpathObj);
        xmlChar *pXpathStrResult;
        if (pXmlXpathObj->type == XPATH_NODESET)
        {
            xmlNodeSetPtr pXmlNodes = pXmlXpathObj->nodesetval;
            CPPUNIT_ASSERT_EQUAL_MESSAGE("xpath should match exactly 1 node",
                1, xmlXPathNodeSetGetLength(pXmlNodes));
            xmlNodePtr pXmlNode = pXmlNodes->nodeTab[0];
            if (aAttribute.getLength())
                pXpathStrResult = xmlGetProp(pXmlNode, BAD_CAST(aAttribute.getStr()));
            else
                pXpathStrResult = xmlNodeGetContent(pXmlNode);
        }
        else
        {
            // the xpath expression evaluated to a value, not a node
            CPPUNIT_ASSERT_EQUAL_MESSAGE(
                "attr name should not be supplied when xpath evals to a value",
                sal_Int32(0), aAttribute.getLength());
            pXpathStrResult = xmlXPathCastToString(pXmlXpathObj);
            CPPUNIT_ASSERT_MESSAGE("xpath result cannot be cast to string",
                pXpathStrResult);
        }

        OUString aRet(reinterpret_cast<char*>(pXpathStrResult),
                      xmlStrlen(pXpathStrResult), RTL_TEXTENCODING_UTF8);
        xmlFree(pXpathStrResult);
        xmlFree(pXmlXpathObj);
        xmlFree(pXmlXpathCtx);

        return aRet;
    }

    template< typename T >
    T getProperty( const uno::Any& obj, const OUString& name ) const
    {
        uno::Reference< beans::XPropertySet > properties( obj, uno::UNO_QUERY_THROW );
        T data;
        if (!css::uno::fromAny(properties->getPropertyValue(name), &data))
        {
            OString const msg("the property is of unexpected type or void: "
                    + OUStringToOString(name, RTL_TEXTENCODING_UTF8));
            CPPUNIT_FAIL(msg.getStr());
        }
        return data;
    }

    template< typename T >
    T getProperty( const uno::Reference< uno::XInterface >& obj, const OUString& name ) const
    {
        uno::Reference< beans::XPropertySet > properties( obj, uno::UNO_QUERY_THROW );
        T data = T();
        if (!(properties->getPropertyValue(name) >>= data))
        {
            OString const msg("the property is of unexpected type or void: "
                    + OUStringToOString(name, RTL_TEXTENCODING_UTF8));
            CPPUNIT_FAIL(msg.getStr());
        }
        return data;
    }

    bool hasProperty(const uno::Reference<uno::XInterface>& obj, const OUString& name) const
    {
        uno::Reference<beans::XPropertySet> properties(obj, uno::UNO_QUERY_THROW);
        return properties->getPropertySetInfo()->hasPropertyByName(name);
    }

    xml::AttributeData getUserDefineAttribute(const uno::Any& obj, const OUString& name, const OUString& rValue) const
    {
        uno::Reference<container::XNameContainer> attrsCnt(getProperty<uno::Any>(obj, "UserDefinedAttributes"), uno::UNO_QUERY_THROW);

        xml::AttributeData aValue;
        attrsCnt->getByName(name) >>= aValue;
        if (!rValue.isEmpty())
            CPPUNIT_ASSERT_EQUAL_MESSAGE("attribute of cell does not contain expected value", rValue, aValue.Value);

        return aValue;
    }

    int getParagraphs( uno::Reference<text::XText> const & xText )
    {
        int nRet = 0;
        if ( ! xText.is() )
            return nRet;

        uno::Reference<container::XEnumerationAccess> xParaEnumAccess(xText->getText(), uno::UNO_QUERY);
        uno::Reference<container::XEnumeration> xParaEnum = xParaEnumAccess->createEnumeration();
        while (xParaEnum->hasMoreElements())
        {
            xParaEnum->nextElement();
            nRet++;
        }
        return nRet;
    }

    /// Get number of paragraphs of the document.
    int getParagraphs()
    {
        uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
        return getParagraphs( xTextDocument->getText() );
    }

    uno::Reference<text::XTextContent> getParagraphOrTable(int number, uno::Reference<text::XText> const & xText = uno::Reference<text::XText>()) const
    {
        assert(number != 0); // this thing is 1-based
        uno::Reference<container::XEnumerationAccess> paraEnumAccess;
        if (xText.is())
            paraEnumAccess.set(xText, uno::UNO_QUERY);
        else
        {
            uno::Reference<text::XTextDocument> textDocument(mxComponent, uno::UNO_QUERY);
            paraEnumAccess.set(textDocument->getText(), uno::UNO_QUERY);
        }
        uno::Reference<container::XEnumeration> paraEnum = paraEnumAccess->createEnumeration();
        for( int i = 1;
             i < number;
             ++i )
            paraEnum->nextElement();
        uno::Reference< text::XTextContent> const xElem(paraEnum->nextElement(),
                uno::UNO_QUERY_THROW);
        return xElem;
    }

    // Get paragraph (counted from 1), optionally check it contains the given text.
    uno::Reference< text::XTextRange > getParagraph( int number, const OUString& content = OUString() ) const
    {
        uno::Reference<text::XTextRange> const xParagraph(
                getParagraphOrTable(number), uno::UNO_QUERY_THROW);
        if( !content.isEmpty())
            CPPUNIT_ASSERT_EQUAL_MESSAGE( "paragraph does not have expected content", content, xParagraph->getString());
        return xParagraph;
    }

    sal_Int16 getNumberingTypeOfParagraph(int nPara)
    {
        sal_Int16 nNumberingType = -1;
        uno::Reference<text::XTextRange> xPara(getParagraph(nPara));
        uno::Reference< beans::XPropertySet > properties( xPara, uno::UNO_QUERY);
        bool isNumber = false;
        properties->getPropertyValue("NumberingIsNumber") >>= isNumber;
        if (isNumber)
        {
            uno::Reference<container::XIndexAccess> xLevels( properties->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
            sal_Int16 nNumberingLevel = -1;
            properties->getPropertyValue("NumberingLevel") >>= nNumberingLevel;
            if (nNumberingLevel >= 0 && nNumberingLevel < xLevels->getCount())
            {
                uno::Sequence< beans::PropertyValue > aPropertyValue;
                xLevels->getByIndex(nNumberingLevel) >>= aPropertyValue;
                auto pProp = std::find_if(aPropertyValue.begin(), aPropertyValue.end(),
                    [](const beans::PropertyValue& rProp) { return rProp.Name == "NumberingType"; });
                if (pProp != aPropertyValue.end())
                    nNumberingType = pProp->Value.get<sal_Int16>();
            }
        }
        return nNumberingType;
    }

    uno::Reference<text::XTextRange> getParagraphOfText(int number, uno::Reference<text::XText> const & xText, const OUString& content = OUString()) const
    {
        uno::Reference<text::XTextRange> const xParagraph(getParagraphOrTable(number, xText), uno::UNO_QUERY_THROW);
        if (!content.isEmpty())
            CPPUNIT_ASSERT_EQUAL_MESSAGE( "paragraph does not contain expected content", content, xParagraph->getString());
        return xParagraph;
    }

    /// get nth object/fly that is anchored AT paragraph
    uno::Reference<beans::XPropertySet> getParagraphAnchoredObject(
        int const index, uno::Reference<text::XTextRange> const & xPara) const
    {
        uno::Reference<container::XContentEnumerationAccess> xContentEnumAccess(xPara, uno::UNO_QUERY);
        uno::Reference<container::XEnumeration> xContentEnum = xContentEnumAccess->createContentEnumeration("com.sun.star.text.TextContent");
        for (int i = 1; i < index; ++i)
        {
            xContentEnum->nextElement();
        }
        return uno::Reference<beans::XPropertySet>(xContentEnum->nextElement(), uno::UNO_QUERY);
    }

    /// Get run (counted from 1) of a paragraph, optionally check it contains the given text.
    uno::Reference<text::XTextRange> getRun(uno::Reference<text::XTextRange> const & xParagraph, int number, const OUString& content = OUString()) const
    {
        uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParagraph, uno::UNO_QUERY);
        uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration();
        for (int i = 1; i < number; ++i)
            xRunEnum->nextElement();
        uno::Reference<text::XTextRange> xRun(xRunEnum->nextElement(), uno::UNO_QUERY);
        if( !content.isEmpty())
            CPPUNIT_ASSERT_EQUAL_MESSAGE( "run does not contain expected content", content, xRun->getString());
        return xRun;
    }

    /// Get math formula string of a run.
    OUString getFormula(uno::Reference<text::XTextRange> const & xRun) const
    {
        uno::Reference<container::XContentEnumerationAccess> xContentEnumAccess(xRun, uno::UNO_QUERY);
        uno::Reference<container::XEnumeration> xContentEnum = xContentEnumAccess->createContentEnumeration("");
        uno::Reference<beans::XPropertySet> xFormula(xContentEnum->nextElement(), uno::UNO_QUERY);
        return getProperty<OUString>(getProperty< uno::Reference<beans::XPropertySet> >(xFormula, "Model"), "Formula");
    }

    /// get cell of a table; table can be retrieved with getParagraphOrTable
    uno::Reference<table::XCell> getCell(
            uno::Reference<uno::XInterface> const& xTableIfc,
            OUString const& rCell, OUString const& rContent = OUString())
    {
        uno::Reference<text::XTextTable> const xTable(xTableIfc,
                uno::UNO_QUERY_THROW);
        uno::Reference<table::XCell> const xCell(
                xTable->getCellByName(rCell), uno::UNO_SET_THROW);
        if (!rContent.isEmpty())
        {
            uno::Reference<text::XText> const xCellText(xCell,
                    uno::UNO_QUERY_THROW);
            CPPUNIT_ASSERT_EQUAL_MESSAGE("cell does not contain expected content", rContent, xCellText->getString());
        }
        return xCell;
    }

    /// Get shape (counted from 1)
    uno::Reference<drawing::XShape> getShape(int number)
    {
        uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
        uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
        uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(number - 1), uno::UNO_QUERY);
        return xShape;
    }

    /// Get shape by name
    uno::Reference<drawing::XShape> getShapeByName(const OUString& aName)
    {
        uno::Reference<drawing::XShape> xRet;

        uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
        uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
        for (sal_Int32 i = 0; i < xDrawPage->getCount(); ++i)
        {
            uno::Reference<container::XNamed> xShape(xDrawPage->getByIndex(i), uno::UNO_QUERY);
            if (xShape->getName() == aName)
            {
                xRet.set(xShape, uno::UNO_QUERY);
                break;
            }
        }

        return xRet;
    }
    /// Get TextFrame by name
    uno::Reference<drawing::XShape> getTextFrameByName(const OUString& aName)
    {
        uno::Reference<text::XTextFramesSupplier> xTextFramesSupplier(mxComponent, uno::UNO_QUERY);
        uno::Reference<container::XNameAccess> xNameAccess = xTextFramesSupplier->getTextFrames();
        uno::Reference<drawing::XShape> xShape(xNameAccess->getByName(aName), uno::UNO_QUERY);
        return xShape;
    }

    void header()
    {
        std::cout << "File tested,Execution Time (ms)" << std::endl;
    }

    void load(const OUString& pDir, const char* pName, const char* pPassword = nullptr)
    {
        return loadURL(m_directories.getURLFromSrc(pDir) + OUString::createFromAscii(pName), pName, pPassword);
    }

    void setTestInteractionHandler(const char* pPassword, std::vector<beans::PropertyValue>& rFilterOptions)
    {
        OUString sPassword = OUString::createFromAscii(pPassword);
        rFilterOptions.emplace_back();
        xInteractionHandler = rtl::Reference<TestInteractionHandler>(new TestInteractionHandler(sPassword));
        uno::Reference<task::XInteractionHandler2> const xInteraction(xInteractionHandler.get());
        rFilterOptions[0].Name = "InteractionHandler";
        rFilterOptions[0].Value <<= xInteraction;
    }

    void loadURL(OUString const& rURL, const char* pName, const char* pPassword = nullptr)
    {
        if (mxComponent.is())
            mxComponent->dispose();

        std::vector<beans::PropertyValue> aFilterOptions;

        if (pPassword)
        {
            setTestInteractionHandler(pPassword, aFilterOptions);
        }

        if (!maImportFilterOptions.isEmpty())
        {
            beans::PropertyValue aValue;
            aValue.Name = "FilterOptions";
            aValue.Value <<= maImportFilterOptions;
            aFilterOptions.push_back(aValue);
        }

        if (!maImportFilterName.isEmpty())
        {
            beans::PropertyValue aValue;
            aValue.Name = "FilterName";
            aValue.Value <<= maImportFilterName;
            aFilterOptions.push_back(aValue);
        }

        // Output name early, so in the case of a hang, the name of the hanging input file is visible.
        if (pName)
            std::cout << pName << ":\n";
        mnStartTime = osl_getGlobalTimer();
        mxComponent = loadFromDesktop(rURL, "com.sun.star.text.TextDocument", comphelper::containerToSequence(aFilterOptions));

        if (pPassword)
        {
            CPPUNIT_ASSERT_MESSAGE("Password set but not requested", xInteractionHandler->wasPasswordRequested());
        }

        discardDumpedLayout();
        if (pName && mustCalcLayoutOf(pName))
            calcLayout();
    }

    void reload(const char* pFilter, const char* filename, const char* pPassword = nullptr)
    {
        uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
        OUString aFilterName = OUString::createFromAscii(pFilter);
        utl::MediaDescriptor aMediaDescriptor;
        aMediaDescriptor["FilterName"] <<= aFilterName;
        if (!maFilterOptions.isEmpty())
            aMediaDescriptor["FilterOptions"] <<= maFilterOptions;
        if (pPassword)
        {
            if (strcmp(pFilter, "Office Open XML Text"))
            {
                aMediaDescriptor["Password"] <<= OUString::createFromAscii(pPassword);
            }
            else
            {
                OUString sPassword = OUString::createFromAscii(pPassword);
                css::uno::Sequence<css::beans::NamedValue> aEncryptionData {
                    { "CryptoType", css::uno::makeAny(OUString("Standard")) },
                    { "OOXPassword", css::uno::makeAny(sPassword) }
                };
                aMediaDescriptor[utl::MediaDescriptor::PROP_ENCRYPTIONDATA()] <<= aEncryptionData;
            }
        }
        xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
        uno::Reference<lang::XComponent> xComponent(xStorable, uno::UNO_QUERY);
        xComponent->dispose();
        mbExported = true;

        std::vector<beans::PropertyValue> aFilterOptions;
        if (pPassword)
        {
            setTestInteractionHandler(pPassword, aFilterOptions);
        }

        if (!maImportFilterOptions.isEmpty())
        {
            beans::PropertyValue aValue;
            aValue.Name = "FilterOptions";
            aValue.Value <<= maImportFilterOptions;
            aFilterOptions.push_back(aValue);
        }

        if (!maImportFilterName.isEmpty())
        {
            beans::PropertyValue aValue;
            aValue.Name = "FilterName";
            aValue.Value <<= maImportFilterName;
            aFilterOptions.push_back(aValue);
        }

        mxComponent = loadFromDesktop(maTempFile.GetURL(), "com.sun.star.text.TextDocument", comphelper::containerToSequence(aFilterOptions));
        if (pPassword)
        {
            CPPUNIT_ASSERT_MESSAGE("Password set but not requested", xInteractionHandler->wasPasswordRequested());
        }
        if (mustValidate(filename) || aFilterName == "writer8"
                || aFilterName == "OpenDocument Text Flat XML")
        {
            if(aFilterName == "Office Open XML Text")
            {
                // too many validation errors right now
                validate(maTempFile.GetFileName(), test::OOXML);
            }
            else if(aFilterName == "writer8"
                || aFilterName == "OpenDocument Text Flat XML")
            {
                validate(maTempFile.GetFileName(), test::ODF);
            }
            else if(aFilterName == "MS Word 97")
            {
                validate(maTempFile.GetFileName(), test::MSBINARY);
            }
            else
            {
                OString aMessage = OStringLiteral("validation requested, but don't know how to validate ") +
                    filename +
                    " (" +
                    OUStringToOString(aFilterName, RTL_TEXTENCODING_UTF8) +
                    ")";
                CPPUNIT_FAIL(aMessage.getStr());
            }
        }
        discardDumpedLayout();
        if (mustCalcLayoutOf(filename))
            calcLayout();
    }

    /// Save the loaded document to a tempfile. Can be used to check the resulting docx/odt directly as a ZIP file.
    void save(const OUString& aFilterName, utl::TempFile& rTempFile)
    {
        rTempFile.EnableKillingFile();
        uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
        utl::MediaDescriptor aMediaDescriptor;
        aMediaDescriptor["FilterName"] <<= aFilterName;
        if (!maFilterOptions.isEmpty())
            aMediaDescriptor["FilterOptions"] <<= maFilterOptions;
        xStorable->storeToURL(rTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
        // TODO: for now, validate only ODF here
        if (aFilterName == "writer8"
            || aFilterName == "OpenDocument Text Flat XML")
        {
            validate(rTempFile.GetFileName(), test::ODF);
        }
    }

    void finish()
    {
        sal_uInt32 nEndTime = osl_getGlobalTimer();
        std::cout << (nEndTime - mnStartTime) << std::endl;
        discardDumpedLayout();
    }

    /// Get page count.
    int getPages() const
    {
        uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
        uno::Reference<text::XTextViewCursorSupplier> xTextViewCursorSupplier(xModel->getCurrentController(), uno::UNO_QUERY);
        uno::Reference<text::XPageCursor> xCursor(xTextViewCursorSupplier->getViewCursor(), uno::UNO_QUERY);
        xCursor->jumpToLastPage();
        return xCursor->getPage();
    }

    /// Get shape count.
    int getShapes() const
    {
        uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
        uno::Reference<container::XIndexAccess> xDraws = xDrawPageSupplier->getDrawPage();
        return xDraws->getCount();
    }

    /**
     * Given that some problem doesn't affect the result in the importer, we
     * test the resulting file directly, by opening the zip file, parsing an
     * xml stream, and asserting an XPath expression. This method returns the
     * xml stream, so that you can do the asserting.
     */
    xmlDocUniquePtr parseExport(const OUString& rStreamName = OUString("word/document.xml"))
    {
        if (!mbExported)
            return nullptr;

        return parseExportInternal( maTempFile.GetURL(), rStreamName );
    }

    /**
     * Returns an xml stream of an exported file.
     * To be used when the exporter doesn't create zip archives, but single files
     * (like Flat ODF Export)
     */
    xmlDocUniquePtr parseExportedFile()
    {
        auto stream(SvFileStream(maTempFile.GetURL(), StreamMode::READ | StreamMode::TEMPORARY));
        return parseXmlStream(&stream);
    }

    std::unique_ptr<SvStream> parseExportStream(const OUString& url, const OUString& rStreamName)
    {
        // Read the stream we're interested in.
        uno::Reference<packages::zip::XZipFileAccess2> xNameAccess = packages::zip::ZipFileAccess::createWithURL(comphelper::getComponentContext(m_xSFactory), url);
        uno::Reference<io::XInputStream> xInputStream(xNameAccess->getByName(rStreamName), uno::UNO_QUERY);
        CPPUNIT_ASSERT(xInputStream.is());
        std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream, true));
        return pStream;
    }

    xmlDocUniquePtr parseExportInternal(const OUString& url, const OUString& rStreamName)
    {
        std::unique_ptr<SvStream> pStream(parseExportStream(url, rStreamName));

        xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get());
        pXmlDoc->name = reinterpret_cast<char *>(xmlStrdup(reinterpret_cast<xmlChar const *>(OUStringToOString(url, RTL_TEXTENCODING_UTF8).getStr())));
        return pXmlDoc;
    }

    /**
     * Helper method to return nodes represented by rXPath.
     */
    virtual void registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx) override
    {
        // docx
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("w"), BAD_CAST("http://schemas.openxmlformats.org/wordprocessingml/2006/main"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("v"), BAD_CAST("urn:schemas-microsoft-com:vml"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("mc"), BAD_CAST("http://schemas.openxmlformats.org/markup-compatibility/2006"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("wps"), BAD_CAST("http://schemas.microsoft.com/office/word/2010/wordprocessingShape"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("wpg"), BAD_CAST("http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("wp"), BAD_CAST("http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("wp14"), BAD_CAST("http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("a"), BAD_CAST("http://schemas.openxmlformats.org/drawingml/2006/main"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("pic"), BAD_CAST("http://schemas.openxmlformats.org/drawingml/2006/picture"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("rels"), BAD_CAST("http://schemas.openxmlformats.org/package/2006/relationships"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("w14"), BAD_CAST("http://schemas.microsoft.com/office/word/2010/wordml"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("m"), BAD_CAST("http://schemas.openxmlformats.org/officeDocument/2006/math"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("ContentType"), BAD_CAST("http://schemas.openxmlformats.org/package/2006/content-types"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("lc"), BAD_CAST("http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("cp"), BAD_CAST("http://schemas.openxmlformats.org/package/2006/metadata/core-properties"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("extended-properties"), BAD_CAST("http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("custom-properties"), BAD_CAST("http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("vt"), BAD_CAST("http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("dcterms"), BAD_CAST("http://purl.org/dc/terms/"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("a14"), BAD_CAST("http://schemas.microsoft.com/office/drawing/2010/main"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("c"), BAD_CAST("http://schemas.openxmlformats.org/drawingml/2006/chart"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("o"), BAD_CAST("urn:schemas-microsoft-com:office:office"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("w10"), BAD_CAST("urn:schemas-microsoft-com:office:word"));
        // odt
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("office"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("style"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:style:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("text"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:text:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("table"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:table:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("draw"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("fo"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("config"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:config:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("xlink"), BAD_CAST("http://www.w3.org/1999/xlink"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("dc"), BAD_CAST("http://purl.org/dc/elements/1.1/"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("meta"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:meta:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("number"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("svg"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("chart"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:chart:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("dr3d"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("math"), BAD_CAST("http://www.w3.org/1998/Math/MathML"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("form"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:form:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("script"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:script:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("ooo"), BAD_CAST("http://openoffice.org/2004/office"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("ooow"), BAD_CAST("http://openoffice.org/2004/writer"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("oooc"), BAD_CAST("http://openoffice.org/2004/calc"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("dom"), BAD_CAST("http://www.w3.org/2001/xml-events"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("xforms"), BAD_CAST("http://www.w3.org/2002/xforms"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("xsd"), BAD_CAST("http://www.w3.org/2001/XMLSchema"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("xsi"), BAD_CAST("http://www.w3.org/2001/XMLSchema-instance"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("rpt"), BAD_CAST("http://openoffice.org/2005/report"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("of"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:of:1.2"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("xhtml"), BAD_CAST("http://www.w3.org/1999/xhtml"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("grddl"), BAD_CAST("http://www.w3.org/2003/g/data-view#"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("officeooo"), BAD_CAST("http://openoffice.org/2009/office"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("tableooo"), BAD_CAST("http://openoffice.org/2009/table"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("drawooo"), BAD_CAST("http://openoffice.org/2010/draw"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("calcext"), BAD_CAST("urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("loext"), BAD_CAST("urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("field"), BAD_CAST("urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("formx"), BAD_CAST("urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0"));
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("css3t"), BAD_CAST("http://www.w3.org/TR/css3-text/"));
        // reqif-xhtml
        xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("reqif-xhtml"), BAD_CAST("http://www.w3.org/1999/xhtml"));
    }

    /**
     * Creates a new document to be used with the internal sw/ API.
     *
     * Examples:
     * SwDoc* pDoc = createSwDoc();
     * SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "test.fodt");
     */
    SwDoc* createSwDoc(const OUString& rDataDirectory = OUString(), const char* pName = nullptr)
    {
        if (rDataDirectory.isEmpty() || !pName)
            loadURL("private:factory/swriter", nullptr);
        else
            load(rDataDirectory, pName);

        SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
        CPPUNIT_ASSERT(pTextDoc);
        return pTextDoc->GetDocShell()->GetDoc();
    }
};

/**
 * Test whether the expected and actual borderline parameters are equal
 * and assert if not.
 *
 * @param[in]   rExpected    expected borderline object
 * @param[in]   rActual      actual borderline object
 * @param[in]   rSourceLine  line from where the assertion is called
 * Note: This method is the implementation of CPPUNIT_ASSERT_BORDER_EQUAL, so
 *       use that macro instead.
**/
inline void assertBorderEqual(
    const table::BorderLine2& rExpected, const table::BorderLine2& rActual,
    const CppUnit::SourceLine& rSourceLine )
{
    CPPUNIT_NS::assertEquals<util::Color>( rExpected.Color, rActual.Color, rSourceLine, "different Color" );
    CPPUNIT_NS::assertEquals<sal_Int16>( rExpected.InnerLineWidth, rActual.InnerLineWidth, rSourceLine, "different InnerLineWidth" );
    CPPUNIT_NS::assertEquals<sal_Int16>( rExpected.OuterLineWidth, rActual.OuterLineWidth, rSourceLine, "different OuterLineWidth" );
    CPPUNIT_NS::assertEquals<sal_Int16>( rExpected.LineDistance, rActual.LineDistance, rSourceLine, "different LineDistance" );
    CPPUNIT_NS::assertEquals<sal_Int16>( rExpected.LineStyle, rActual.LineStyle, rSourceLine, "different LineStyle" );
    CPPUNIT_NS::assertEquals<sal_Int32>( rExpected.LineWidth, rActual.LineWidth, rSourceLine, "different LineWidth" );
}

#define CPPUNIT_ASSERT_BORDER_EQUAL(aExpected, aActual) \
        assertBorderEqual( aExpected, aActual, CPPUNIT_SOURCELINE() ) \

inline std::ostream& operator<<(std::ostream& rStrm, const Color& rColor)
{
    rStrm << "Color: R:" << static_cast<int>(rColor.GetRed())
          << " G:" << static_cast<int>(rColor.GetGreen())
          << " B:" << static_cast<int>(rColor.GetBlue())
          << " A:" << static_cast<int>(rColor.GetTransparency());
    return rStrm;
}

#endif // INCLUDED_SW_QA_INC_SWMODELTESTBASE_HXX

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
