/* -*- 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 "htmlmodeltestbase.hxx"
#include <config_poppler.h>
#include <memory>

#include <com/sun/star/document/XTypeDetection.hpp>
#include <com/sun/star/table/TableBorder2.hpp>
#include <com/sun/star/view/XSelectionSupplier.hpp>

#include <officecfg/Office/Common.hxx>

#include <vcl/svapp.hxx>
#include <comphelper/processfactory.hxx>
#include <vcl/graphicfilter.hxx>
#include <vcl/dibtools.hxx>
#include <editeng/brushitem.hxx>

#include <wrtsh.hxx>
#include <ndtxt.hxx>
#include <docsh.hxx>
#include <unotxdoc.hxx>
#include <formatlinebreak.hxx>
#include <itabenum.hxx>

namespace
{
CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqifEmbedPNGShapeAsOLE)
{
    // Given a document with an image shape:
    createSwDoc();
    uno::Reference<css::lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
    uno::Reference<drawing::XShape> xShape(
        xFactory->createInstance(u"com.sun.star.drawing.GraphicObjectShape"_ustr), uno::UNO_QUERY);
    xShape->setSize(awt::Size(10000, 10000));
    uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
    xShapeProps->setPropertyValue(u"GraphicURL"_ustr, uno::Any(createFileURL(u"ole2.png")));
    uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
    xDrawPageSupplier->getDrawPage()->add(xShape);

    // When exporting to XHTML:
    uno::Sequence<beans::PropertyValue> aStoreProperties = {
        comphelper::makePropertyValue(u"FilterName"_ustr, u"HTML (StarWriter)"_ustr),
        comphelper::makePropertyValue(u"FilterOptions"_ustr, u"xhtmlns=reqif-xhtml"_ustr),
        comphelper::makePropertyValue(u"ExportImagesAsOLE"_ustr, true),
    };
    saveWithParams(aStoreProperties);

    // Then make sure the PNG is embedded with an RTF wrapper:
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: text/rtf
    // - Actual  : image/png
    // i.e. the OLE wrapper around the PNG was missing.
    assertXPath(pXmlDoc, "//reqif-xhtml:p/reqif-xhtml:object", "type", u"text/rtf");
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqifEmbedShapeAsPNG)
{
    // FIXME: the DPI check should be removed when either (1) the test is fixed to work with
    // non-default DPI; or (2) unit tests on Windows are made to use svp VCL plugin.
    if (!IsDefaultDPI())
        return;
    // Given a document with a shape:
    createSwDoc();
    uno::Reference<css::lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
    uno::Reference<drawing::XShape> xShape(
        xFactory->createInstance(u"com.sun.star.drawing.RectangleShape"_ustr), uno::UNO_QUERY);
    xShape->setSize(awt::Size(10000, 10000));
    uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
    xDrawPageSupplier->getDrawPage()->add(xShape);

    // When exporting to XHTML:
    ExportToReqif();

    // Then make sure the shape is embedded as a PNG:
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: image/png
    // - Actual  : image/x-vclgraphic
    // i.e. the result was invalid ReqIF.
    assertXPath(pXmlDoc, "//reqif-xhtml:p/reqif-xhtml:object", "type", u"image/png");

    // Then check the pixel size of the shape:
    Size aPixelSize(Application::GetDefaultDevice()->LogicToPixel(Size(10000, 10000),
                                                                  MapMode(MapUnit::Map100thMM)));
    // Without the accompanying fix in place, this test would have failed with:
    // - no attribute 'width' exist
    // i.e. shapes had no width.
    assertXPath(pXmlDoc, "//reqif-xhtml:p/reqif-xhtml:object", "width",
                OUString::number(aPixelSize.getWidth()));
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testShapeAsImageHtml)
{
    // Given a document with a shape:
    createSwDoc();
    uno::Reference<css::lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
    uno::Reference<drawing::XShape> xShape(
        xFactory->createInstance(u"com.sun.star.drawing.RectangleShape"_ustr), uno::UNO_QUERY);
    xShape->setSize(awt::Size(5080, 2540));
    uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
    xDrawPageSupplier->getDrawPage()->add(xShape);

    // When exporting to plain HTML:
    saveAndReload(TestFilter::HTML_WRITER);

    // Without the accompanying fix in place, this test would have failed with:
    // - Expected:
    // - Actual  :  />
    // i.e. the output was not well-formed.
    CPPUNIT_ASSERT_EQUAL(u" "_ustr, getParagraph(1)->getString());
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testJson)
{
    // Given a document with a shape:
    createSwDoc();
    uno::Reference<css::lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
    uno::Reference<drawing::XShape> xShape(
        xFactory->createInstance(u"com.sun.star.drawing.RectangleShape"_ustr), uno::UNO_QUERY);
    xShape->setSize(awt::Size(2540, 2540));
    uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
    xDrawPageSupplier->getDrawPage()->add(xShape);

    // When exporting to HTML, and specifying options as JSON:
    setFilterOptions(u"{\"XhtmlNs\":{\"type\":\"string\", \"value\":\"reqif-xhtml\"},"
                     "\"ShapeDPI\":{\"type\":\"long\",\"value\":\"192\"}}"_ustr);
    save(TestFilter::HTML_WRITER);

    // Then make sure those options are not ignored:
    // Without the accompanying fix in place, this test would have failed, as GetPngPath() expects
    // XML output, but xhtmlns=reqif-xhtml was ignored.
    OUString aPngUrl = GetPngPath();
    SvFileStream aFileStream(aPngUrl, StreamMode::READ);
    GraphicDescriptor aDescriptor(aFileStream, nullptr);
    aDescriptor.Detect(/*bExtendedInfo=*/true);
    // Make sure that the increased DPI is taken into account:
    tools::Long nExpected = 192;
    CPPUNIT_ASSERT_EQUAL(nExpected, aDescriptor.GetSizePixel().getWidth());
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqifEmbedShapeAsPNGCustomDPI)
{
    // FIXME: the DPI check should be removed when either (1) the test is fixed to work with
    // non-default DPI; or (2) unit tests on Windows are made to use svp VCL plugin.
    if (!IsDefaultDPI())
        return;
    // Given a document with a shape:
    createSwDoc();
    uno::Reference<css::lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
    uno::Reference<drawing::XShape> xShape(
        xFactory->createInstance(u"com.sun.star.drawing.RectangleShape"_ustr), uno::UNO_QUERY);
    xShape->setSize(awt::Size(5080, 2540));
    uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
    xDrawPageSupplier->getDrawPage()->add(xShape);
    sal_Int32 nDPI = 600;

    // When exporting to XHTML:
    uno::Sequence<beans::PropertyValue> aStoreProperties = {
        comphelper::makePropertyValue(u"FilterName"_ustr, u"HTML (StarWriter)"_ustr),
        comphelper::makePropertyValue(u"FilterOptions"_ustr, u"xhtmlns=reqif-xhtml"_ustr),
        comphelper::makePropertyValue(u"ShapeDPI"_ustr, nDPI),
    };
    saveWithParams(aStoreProperties);

    // Then make sure the shape is embedded as a PNG:
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    assertXPath(pXmlDoc, "//reqif-xhtml:p/reqif-xhtml:object", "type", u"image/png");

    // Then check the pixel size of the shape:
    Size aPixelSize(Application::GetDefaultDevice()->LogicToPixel(Size(5080, 2540),
                                                                  MapMode(MapUnit::Map100thMM)));
    tools::Long nPNGWidth = 1200;
    OUString aPngUrl = GetPngPath();
    SvFileStream aFileStream(aPngUrl, StreamMode::READ);
    GraphicDescriptor aDescriptor(aFileStream, nullptr);
    aDescriptor.Detect(/*bExtendedInfo=*/true);
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: 1200
    // - Actual  : 1000
    // i.e. first setting a double DPI didn't result in larger pixel width of the PNG, then it was
    // limited to 1000 pixels (because the pixel limit was 500k).
    CPPUNIT_ASSERT_EQUAL(nPNGWidth, aDescriptor.GetSizePixel().getWidth());

    // Then make sure the shape's logic size (in CSS pixels) don't change:
    assertXPath(pXmlDoc, "//reqif-xhtml:p/reqif-xhtml:object", "width",
                OUString::number(aPixelSize.getWidth()));
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqifOleBmpTransparent)
{
    // Given a document with a transparent image:
    createSwDoc();
    uno::Sequence<beans::PropertyValue> aArgs = {
        comphelper::makePropertyValue(u"FileName"_ustr, createFileURL(u"transparent.png")),
    };
    dispatchCommand(mxComponent, u".uno:InsertGraphic"_ustr, aArgs);

    // When exporting to reqif with ExportImagesAsOLE=true:
    uno::Sequence<beans::PropertyValue> aStoreProperties = {
        comphelper::makePropertyValue(u"FilterName"_ustr, u"HTML (StarWriter)"_ustr),
        comphelper::makePropertyValue(u"FilterOptions"_ustr, u"xhtmlns=reqif-xhtml"_ustr),
        comphelper::makePropertyValue(u"ExportImagesAsOLE"_ustr, true),
    };
    saveWithParams(aStoreProperties);

    // Then make sure the transparent pixel turns into white:
    SvMemoryStream aOle1;
    ParseOle1FromRtfUrl(GetOlePath(), aOle1);
    OLE1Reader aOle1Reader(aOle1);
    SvMemoryStream aBitmapStream(aOle1Reader.m_aNativeData.data(), aOle1Reader.m_aNativeData.size(),
                                 StreamMode::READ);
    Bitmap aBitmap;
    ReadDIB(aBitmap, aBitmapStream, /*bFileHeader=*/true);
    Size aBitmapSize = aBitmap.GetSizePixel();
    Color nActualColor
        = aBitmap.GetPixelColor(aBitmapSize.getWidth() - 1, aBitmapSize.getHeight() - 1);
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: Color: R:255 G:255 B:255 A:0
    // - Actual  : Color: R:0 G:0 B:0 A:0
    // i.e. the bitmap without an alpha channel was black, not white.
    CPPUNIT_ASSERT_EQUAL(COL_WHITE, nActualColor);
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testListsHeading)
{
    // Given a document with lh, lh, li, li, lh and lh nodes:
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Insert(u"list 1, header 1"_ustr);
    pWrtShell->SplitNode();
    pWrtShell->Insert(u"list 1, header 2"_ustr);
    pWrtShell->SplitNode();
    pWrtShell->Insert(u"list 2, item 1"_ustr);
    pWrtShell->SplitNode();
    pWrtShell->Insert(u"list 2, item 2"_ustr);
    pWrtShell->SplitNode();
    pWrtShell->Insert(u"list 3, header 1"_ustr);
    pWrtShell->SplitNode();
    pWrtShell->Insert(u"list 3, header 2"_ustr);
    SwDoc* pDoc = pWrtShell->GetDoc();
    pWrtShell->Up(false, 5);
    {
        sal_uInt16 nPos = pDoc->MakeNumRule(pDoc->GetUniqueNumRuleName());
        SwNumRule* pNumRule = pDoc->GetNumRuleTable()[nPos];
        {
            SwNode& rNode = pWrtShell->GetCursor()->GetPoint()->GetNode();
            SwTextNode& rTextNode = *rNode.GetTextNode();
            rTextNode.SetAttr(SwNumRuleItem(pNumRule->GetName()));
            rTextNode.SetCountedInList(false);
        }
        pWrtShell->Down(false, 1);
        {
            SwNode& rNode = pWrtShell->GetCursor()->GetPoint()->GetNode();
            SwTextNode& rTextNode = *rNode.GetTextNode();
            rTextNode.SetAttr(SwNumRuleItem(pNumRule->GetName()));
            rTextNode.SetCountedInList(false);
        }
    }
    pWrtShell->Down(false, 1);
    {
        sal_uInt16 nPos = pDoc->MakeNumRule(pDoc->GetUniqueNumRuleName());
        SwNumRule* pNumRule = pDoc->GetNumRuleTable()[nPos];
        {
            SwNode& rNode = pWrtShell->GetCursor()->GetPoint()->GetNode();
            SwTextNode& rTextNode = *rNode.GetTextNode();
            rTextNode.SetAttr(SwNumRuleItem(pNumRule->GetName()));
        }
        pWrtShell->Down(false, 1);
        {
            SwNode& rNode = pWrtShell->GetCursor()->GetPoint()->GetNode();
            SwTextNode& rTextNode = *rNode.GetTextNode();
            rTextNode.SetAttr(SwNumRuleItem(pNumRule->GetName()));
        }
    }
    pWrtShell->Down(false, 1);
    {
        sal_uInt16 nPos = pDoc->MakeNumRule(pDoc->GetUniqueNumRuleName());
        SwNumRule* pNumRule = pDoc->GetNumRuleTable()[nPos];
        {
            SwNode& rNode = pWrtShell->GetCursor()->GetPoint()->GetNode();
            SwTextNode& rTextNode = *rNode.GetTextNode();
            rTextNode.SetAttr(SwNumRuleItem(pNumRule->GetName()));
            rTextNode.SetCountedInList(false);
        }
        pWrtShell->Down(false, 1);
        {
            SwNode& rNode = pWrtShell->GetCursor()->GetPoint()->GetNode();
            SwTextNode& rTextNode = *rNode.GetTextNode();
            rTextNode.SetAttr(SwNumRuleItem(pNumRule->GetName()));
            rTextNode.SetCountedInList(false);
        }
    }

    // When exporting to ReqIF:
    ExportToReqif();

    // Then make sure the output is valid xhtml:
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();

    OUString aContent
        = getXPathContent(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ol/"
                                   "reqif-xhtml:li[@style='display: block']/reqif-xhtml:p");
    CPPUNIT_ASSERT_EQUAL(u"list 1, header 1"_ustr, aContent.trim());
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testOleEmfPreviewToHtml)
{
    // Given a document containing an embedded object, with EMF preview:
    createSwDoc("ole2.odt");

    // When exporting to HTML:
    ExportToHTML();

    // Then make sure the <img> tag has matching file extension and data:
    htmlDocUniquePtr pDoc = parseHtml(maTempFile);
    OUString aPath = getXPath(pDoc, "/html/body/p/img", "src");
    // Without the accompanying fix in place, this test would have failed, as aPath was
    // ole_html_3978e5f373402b43.JPG, with EMF data.
    CPPUNIT_ASSERT(aPath.endsWith("gif"));
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testNestedBullets)
{
    // Given a documented with nested lists:
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Insert(u"first"_ustr);
    sal_uInt16 nPos = pDoc->MakeNumRule(pDoc->GetUniqueNumRuleName());
    SwNumRule* pNumRule = pDoc->GetNumRuleTable()[nPos];
    {
        SwNode& rNode = pWrtShell->GetCursor()->GetPoint()->GetNode();
        SwTextNode& rTextNode = *rNode.GetTextNode();
        rTextNode.SetAttr(SwNumRuleItem(pNumRule->GetName()));
        rTextNode.SetAttrListLevel(0);
    }
    pWrtShell->SplitNode();
    pWrtShell->Insert(u"second"_ustr);
    {
        SwNode& rNode = pWrtShell->GetCursor()->GetPoint()->GetNode();
        SwTextNode& rTextNode = *rNode.GetTextNode();
        rTextNode.SetAttr(SwNumRuleItem(pNumRule->GetName()));
        rTextNode.SetAttrListLevel(1);
    }

    // When exporting to xhtml:
    ExportToReqif();

    // Then make sure that there is a <li> between the outer and the inner <ol>:
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    // Without the accompanying fix in place, this test would have failed with:
    // - XPath '//reqif-xhtml:ol/reqif-xhtml:li/reqif-xhtml:ol/reqif-xhtml:li/reqif-xhtml:p' not found
    // i.e. the <li> inside the outer <ol> was missing.
    assertXPathContent(
        pXmlDoc, "//reqif-xhtml:ol/reqif-xhtml:li/reqif-xhtml:ol/reqif-xhtml:li/reqif-xhtml:p",
        u"second");
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testTrailingLineBreak)
{
    // Given a document with a trailing line-break:
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Insert(u"test\n"_ustr);

    // When exporting to reqif-xhtml:
    ExportToReqif();

    // Then make sure that we still have a single line-break:
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: 1
    // - Actual  : 2
    // - XPath '//reqif-xhtml:br' number of nodes is incorrect
    assertXPath(pXmlDoc, "//reqif-xhtml:br", 1);

    // Then test the import side:
    // Given an empty document:
    // When importing a <br> from reqif-xhtml:
    ImportFromReqif(maTempFile.GetURL());

    // Then make sure that line-break is not lost:
    pWrtShell = getSwDocShell()->GetWrtShell();
    OUString aActual = pWrtShell->GetCursor()->GetPointNode().GetTextNode()->GetText();
    // Without the accompanying fix in place, this test would have failed, as the trailing
    // line-break was lost.
    CPPUNIT_ASSERT_EQUAL(u"test\n"_ustr, aActual);
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testLeadingTab)
{
    // Given a document with leading tabs:
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Insert(u"\t first"_ustr);
    pWrtShell->SplitNode();
    pWrtShell->Insert(u"\t\t second"_ustr);
    pWrtShell->SplitNode();
    pWrtShell->Insert(u"thi \t rd"_ustr);

    // When exporting to HTML, using LeadingTabWidth=2:
    uno::Sequence<beans::PropertyValue> aStoreProperties = {
        comphelper::makePropertyValue(u"FilterName"_ustr, u"HTML (StarWriter)"_ustr),
        comphelper::makePropertyValue(u"FilterOptions"_ustr, u"xhtmlns=reqif-xhtml"_ustr),
        comphelper::makePropertyValue(u"LeadingTabWidth"_ustr, static_cast<sal_Int32>(2)),
    };
    saveWithParams(aStoreProperties);

    // Then make sure that leading tabs are replaced with 2 nbsps:
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: <nbsp><nbsp><space>first
    // - Actual  : <tab><space>first
    // i.e. the leading tab was not replaced by 2 nbsps.
    assertXPathContent(pXmlDoc, "//reqif-xhtml:p[1]", u"\xa0\xa0 first");
    // Test a leading tab that is not at the start of the paragraph:
    assertXPathContent(pXmlDoc, "//reqif-xhtml:p[2]", u"\xa0\xa0\xa0\xa0 second");
    // Test a tab which is not leading:
    assertXPathContent(pXmlDoc, "//reqif-xhtml:p[3]", u"thi \t rd");
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testLeadingTabHTML)
{
    // Given a document with leading tabs:
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Insert(u"\t test"_ustr);

    // When exporting to plain HTML, using LeadingTabWidth=2:
    uno::Sequence<beans::PropertyValue> aStoreProperties = {
        comphelper::makePropertyValue(u"FilterName"_ustr, u"HTML (StarWriter)"_ustr),
        comphelper::makePropertyValue(u"LeadingTabWidth"_ustr, static_cast<sal_Int32>(2)),
    };
    saveWithParams(aStoreProperties);

    // Then make sure that leading tabs are replaced with 2 nbsps:
    htmlDocUniquePtr pHtmlDoc = parseHtml(maTempFile);
    CPPUNIT_ASSERT(pHtmlDoc);
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: <newline><nbsp><nbsp><space>test
    // - Actual  : <newline><tab><space>test
    // i.e. the leading tab was not replaced by 2 nbsps.
    assertXPathContent(pHtmlDoc, "/html/body/p", u"\n\xa0\xa0 test");
}

CPPUNIT_TEST_FIXTURE(HtmlExportTest, testClearingBreak)
{
    auto verify = [this]() {
        uno::Reference<container::XEnumerationAccess> xParagraph(getParagraph(1), uno::UNO_QUERY);
        uno::Reference<container::XEnumeration> xPortions = xParagraph->createEnumeration();
        uno::Reference<beans::XPropertySet> xPortion;
        OUString aPortionType;
        while (true)
        {
            // Ignore leading comments.
            xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
            xPortion->getPropertyValue(u"TextPortionType"_ustr) >>= aPortionType;
            if (aPortionType != "Annotation")
            {
                break;
            }
        }
        // Skip "foo".
        // Without the accompanying fix in place, this test would have failed with:
        // An uncaught exception of type com.sun.star.container.NoSuchElementException
        // i.e. the first para was just comments + text portion, the clearing break was lost.
        xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
        xPortion->getPropertyValue(u"TextPortionType"_ustr) >>= aPortionType;
        CPPUNIT_ASSERT_EQUAL(u"LineBreak"_ustr, aPortionType);
        uno::Reference<text::XTextContent> xLineBreak;
        xPortion->getPropertyValue(u"LineBreak"_ustr) >>= xLineBreak;
        sal_Int16 eClear{};
        uno::Reference<beans::XPropertySet> xLineBreakProps(xLineBreak, uno::UNO_QUERY);
        xLineBreakProps->getPropertyValue(u"Clear"_ustr) >>= eClear;
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(SwLineBreakClear::ALL), eClear);
    };

    // Given a document with an at-para anchored image + a clearing break:
    // When loading that file:
    createSwWebDoc("clearing-break.html");
    // Then make sure that the clear property of the break is not ignored:
    verify();
    saveAndReload(TestFilter::HTML_WRITER);
    // Make sure that the clear property of the break is not ignored during export:
    verify();
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testTableBackground)
{
    // Given a document with two tables: first stable has a background, second table has a
    // background in its first row:
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    SwInsertTableOptions aInsertTableOptions(SwInsertTableFlags::DefaultBorder,
                                             /*nRowsToRepeat=*/0);
    pWrtShell->InsertTable(aInsertTableOptions, /*nRows=*/1, /*nCols=*/1);
    pWrtShell->MoveTable(GotoPrevTable, fnTableStart);
    SvxBrushItem aBrush(COL_LIGHTRED, RES_BACKGROUND);
    pWrtShell->SetTabBackground(aBrush);
    pWrtShell->Down(/*bSelect=*/false);
    pWrtShell->SplitNode();
    pWrtShell->InsertTable(aInsertTableOptions, /*nRows=*/2, /*nCols=*/1);
    pWrtShell->MoveTable(GotoPrevTable, fnTableStart);
    aBrush.SetColor(COL_LIGHTGREEN);
    pWrtShell->SetRowBackground(aBrush);
    pWrtShell->Down(/*bSelect=*/false);
    // Second row has an explicit transparent background.
    aBrush.SetColor(COL_TRANSPARENT);
    pWrtShell->SetRowBackground(aBrush);

    // When exporting to reqif-xhtml:
    ExportToReqif();

    // Then make sure that CSS markup is used, not HTML one:
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    // Without the accompanying fix in place, this test would have failed with:
    // - XPath '//reqif-xhtml:table[1]' no attribute 'style' exist
    // i.e. HTML markup was used for the table background color.
    assertXPath(pXmlDoc, "//reqif-xhtml:table[1]", "style", u"background: #ff0000");
    assertXPathNoAttribute(pXmlDoc, "//reqif-xhtml:table[1]", "bgcolor");
    assertXPath(pXmlDoc, "//reqif-xhtml:table[2]/reqif-xhtml:tr[1]", "style",
                u"background: #00ff00");
    assertXPathNoAttribute(pXmlDoc, "//reqif-xhtml:table[2]/reqif-xhtml:tr[1]", "bgcolor");
    // Second row has no explicit style, the default is not written.
    assertXPathNoAttribute(pXmlDoc, "//reqif-xhtml:table[2]/reqif-xhtml:tr[2]", "style");
}

CPPUNIT_TEST_FIXTURE(HtmlExportTest, testImageKeepRatio)
{
    // Given a document with an image: width is relative, height is "keep ratio":
    createSwDoc();
    uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> xTextGraphic(
        xFactory->createInstance(u"com.sun.star.text.TextGraphicObject"_ustr), uno::UNO_QUERY);
    xTextGraphic->setPropertyValue(u"AnchorType"_ustr,
                                   uno::Any(text::TextContentAnchorType_AS_CHARACTER));
    xTextGraphic->setPropertyValue(u"RelativeWidth"_ustr, uno::Any(static_cast<sal_Int16>(42)));
    xTextGraphic->setPropertyValue(u"IsSyncHeightToWidth"_ustr, uno::Any(true));
    uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XText> xBodyText = xTextDocument->getText();
    uno::Reference<text::XTextCursor> xCursor(xBodyText->createTextCursor());
    uno::Reference<text::XTextContent> xTextContent(xTextGraphic, uno::UNO_QUERY);
    xBodyText->insertTextContent(xCursor, xTextContent, false);

    // When exporting to HTML:
    save(TestFilter::HTML_WRITER);

    // Then make sure that the width is not a fixed size, that would break on resizing the browser
    // window:
    htmlDocUniquePtr pDoc = parseHtml(maTempFile);
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: auto
    // - Actual  : 2
    // i.e. a static (CSS pixel) height was written.
    assertXPath(pDoc, "/html/body/p/img", "height", u"auto");
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testSectionDir)
{
    // Given a document with a section:
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Insert(u"test"_ustr);
    pWrtShell->SelAll();
    SwSectionData aSectionData(SectionType::Content, UIName(u"mysect"_ustr));
    pWrtShell->InsertSection(aSectionData);

    // When exporting to (reqif-)xhtml:
    ExportToReqif();

    // Then make sure CSS is used to export the text direction of the section:
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    // Without the accompanying fix in place, this test would have failed with:
    // - XPath '//reqif-xhtml:div[@id='mysect']' no attribute 'style' exist
    // i.e. the dir="ltr" HTML attribute was used instead.
    assertXPath(pXmlDoc, "//reqif-xhtml:div[@id='mysect']", "style", u"dir: ltr");
}

CPPUNIT_TEST_FIXTURE(HtmlExportTest, testTdf114769)
{
    // Create document from scratch since relative urls to filesystem can be replaced
    // by absolute during save/load
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Insert(u"Hyperlink1"_ustr);
    pWrtShell->SplitNode();
    pWrtShell->Insert(u"Hyperlink2"_ustr);
    pWrtShell->SplitNode();
    pWrtShell->Insert(u"Hyperlink3"_ustr);
    pWrtShell->SplitNode();
    pWrtShell->Insert(u"Hyperlink4"_ustr);
    pWrtShell->SplitNode();
    pWrtShell->Insert(u"Hyperlink5"_ustr);
    pWrtShell->SplitNode();

    // Normal external URL
    uno::Reference<beans::XPropertySet> xRun(getRun(getParagraph(1), 1), uno::UNO_QUERY);
    xRun->setPropertyValue(u"HyperLinkURL"_ustr, uno::Any(u"http://libreoffice.org/"_ustr));

    // Bookmark reference
    xRun.set(getRun(getParagraph(2), 1), uno::UNO_QUERY);
    xRun->setPropertyValue(u"HyperLinkURL"_ustr, uno::Any(u"#some_bookmark"_ustr));

    // Filesystem absolute link
    xRun.set(getRun(getParagraph(3), 1), uno::UNO_QUERY);
    xRun->setPropertyValue(u"HyperLinkURL"_ustr, uno::Any(u"C:\\test.txt"_ustr));

    // Filesystem relative link
    xRun.set(getRun(getParagraph(4), 1), uno::UNO_QUERY);
    xRun->setPropertyValue(u"HyperLinkURL"_ustr, uno::Any(u"..\\..\\test.odt"_ustr));

    // Filesystem relative link
    xRun.set(getRun(getParagraph(5), 1), uno::UNO_QUERY);
    xRun->setPropertyValue(u"HyperLinkURL"_ustr, uno::Any(u".\\another.odt"_ustr));

    // Export
    save(TestFilter::HTML_WRITER);

    htmlDocUniquePtr pHtmlDoc = parseHtml(maTempFile);
    CPPUNIT_ASSERT(pHtmlDoc);

    assertXPath(pHtmlDoc, "/html/body/p[1]/a", "href", u"http://libreoffice.org/");
    assertXPath(pHtmlDoc, "/html/body/p[2]/a", "href", u"#some_bookmark");
    assertXPath(pHtmlDoc, "/html/body/p[3]/a", "href", u"C:\\test.txt");
    assertXPath(pHtmlDoc, "/html/body/p[4]/a", "href", u"..\\..\\test.odt");
    assertXPath(pHtmlDoc, "/html/body/p[5]/a", "href", u".\\another.odt");
}

CPPUNIT_TEST_FIXTURE(HtmlExportTest, testTdf153923)
{
    createSwDoc("TableWithIndent.fodt");
    save(TestFilter::HTML_WRITER);

    // Parse it as XML (strict!)
    xmlDocUniquePtr pDoc = parseXml(maTempFile);
    // Without the fix in place, this would fail
    CPPUNIT_ASSERT(pDoc);

    assertXPath(pDoc, "/html/body//dl", 3);
    // The 'dd' tag was not closed
    assertXPath(pDoc, "/html/body//dd", 3);
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testTdf153923_ReqIF)
{
    createSwDoc("TableWithIndent.fodt");
    ExportToReqif();

    xmlDocUniquePtr pDoc = WrapReqifFromTempFile();

    assertXPath(pDoc, "//reqif-xhtml:table");
    // There should be no 'dd' or 'dl' tags, used as a hack for table indentation
    assertXPath(pDoc, "//reqif-xhtml:dl", 0);
    assertXPath(pDoc, "//reqif-xhtml:dd", 0);
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIfTransparentTifImg)
{
    // reqIf export must keep the TIF encoding of the image
    createSwDoc("reqif-transparent-tif-img.odt");
    ExportToReqif();

    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    assertXPath(pXmlDoc, "//reqif-xhtml:p/reqif-xhtml:object[1]", "type", u"image/tiff");
    OUString imageName = getXPath(pXmlDoc, "//reqif-xhtml:p/reqif-xhtml:object[1]", "data");
    // Without the accompanying fix in place, this test would have failed,
    // ending with .gif, because XOutFlags::UseGifIfSensible flag combined
    // with the transparent image would result in GIF export
    CPPUNIT_ASSERT(imageName.endsWith(".tif"));

    INetURLObject aURL(maTempFile.GetURL());
    aURL.setName(imageName);
    GraphicDescriptor aDescriptor(aURL);
    aDescriptor.Detect();
    CPPUNIT_ASSERT_EQUAL(GraphicFileFormat::TIF, aDescriptor.GetFileFormat());
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testTdf155387)
{
    createSwDoc("sub_li_and_ctd.fodt");
    ExportToReqif();

    // Without the fix in place, this would fail
    xmlDocUniquePtr pDoc = WrapReqifFromTempFile();

    // Single top-level list
    assertXPath(pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul");
    // Single top-level item
    assertXPath(pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul/reqif-xhtml:li");
    // 4 top-level paragraphs in the item
    assertXPath(pDoc,
                "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul/reqif-xhtml:li/reqif-xhtml:p", 4);
    // 2 sublists in the item
    assertXPath(
        pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul/reqif-xhtml:li/reqif-xhtml:ul", 2);
    // 2 items in the first sublist
    assertXPath(pDoc,
                "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul/reqif-xhtml:li/reqif-xhtml:ul[1]/"
                "reqif-xhtml:li",
                2);
    // Check the last (most nested) subitem's text
    assertXPathContent(
        pDoc,
        "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul/reqif-xhtml:li/reqif-xhtml:ul[2]/"
        "reqif-xhtml:li/reqif-xhtml:ul/reqif-xhtml:li/reqif-xhtml:p",
        u"l3");
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testTdf155496)
{
    createSwDoc("listItemSubheader.fodt");
    ExportToReqif();

    // Without the fix in place, this would fail
    xmlDocUniquePtr pDoc = WrapReqifFromTempFile();

    // Two top-level lists
    assertXPath(pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul", 2);
    // Single top-level item
    assertXPath(pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul[1]/reqif-xhtml:li");
    // One top-level paragraph in the item
    assertXPath(pDoc,
                "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul[1]/reqif-xhtml:li/reqif-xhtml:p");
    // One sublist in the item
    assertXPath(
        pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul[1]/reqif-xhtml:li/reqif-xhtml:ul");
    // One item in the sublist
    assertXPath(pDoc,
                "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul[1]/reqif-xhtml:li/reqif-xhtml:ul/"
                "reqif-xhtml:li");
    // Check its text
    OUString aContent = getXPathContent(
        pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul[1]/reqif-xhtml:li/reqif-xhtml:ul/"
              "reqif-xhtml:li/reqif-xhtml:p");
    CPPUNIT_ASSERT_EQUAL(u"list 1 item 1\n\t\tsub-header"_ustr, aContent.trim());
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_RightAlignedTable)
{
    createSwDoc("tableRight.fodt");
    ExportToReqif();

    xmlDocUniquePtr pDoc = WrapReqifFromTempFile();

    // No 'align' attribute must be present in 'div'
    assertXPathNoAttribute(pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:div", "align");
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_ListsWithNumFormat)
{
    createSwDoc("listsWithNumFormat.fodt");
    ExportToReqif();

    xmlDocUniquePtr pDoc = WrapReqifFromTempFile();

    // No 'type' attribute must be present in 'ol'
    assertXPathNoAttribute(pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ol[1]", "type");
    assertXPathNoAttribute(pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ol[2]", "type");
    assertXPathNoAttribute(pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ol[3]", "type");
    assertXPathNoAttribute(pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ol[4]", "type");
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testTdf155871)
{
    createSwDoc("tdf155871.fodt");
    ExportToReqif();

    // Without the fix in place, this would fail
    xmlDocUniquePtr pDoc = WrapReqifFromTempFile();
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_ListsNoStartAttribute)
{
    createSwDoc("twoListsWithSameStyle.fodt");
    ExportToReqif();

    xmlDocUniquePtr pDoc = WrapReqifFromTempFile();

    // No 'start' attribute must be present in 'ol'
    assertXPath(pDoc, "//reqif-xhtml:ol[@start]", 0);
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_FrameTextAsObjectAltText)
{
    createSwDoc("frameWithText.fodt");
    ExportToReqif();

    xmlDocUniquePtr pDoc = WrapReqifFromTempFile();

    // Without the fix, this would fail with
    // - Expected: Some text in frame & <foo>
    // - Actual  : Frame1
    // i.e., frame name was used as the object element content, not frame text
    assertXPathContent(pDoc,
                       "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p[2]/reqif-xhtml:object",
                       u"Some text in frame & <foo>");
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testSingleOleExport)
{
    // Given a document containing an embedded OLE object:
    createSwDoc("ole2.odt");

    // Create a selection for that object:
    auto xDrawPageSupplier(mxComponent.queryThrow<css::drawing::XDrawPageSupplier>());
    auto xDrawPage(xDrawPageSupplier->getDrawPage());
    auto xModel(mxComponent.queryThrow<css::frame::XModel>());
    auto xController(xModel->getCurrentController().queryThrow<css::view::XSelectionSupplier>());
    xController->select(xDrawPage->getByIndex(0));

    // Store only the selection
    css::uno::Sequence<css::beans::PropertyValue> aStoreProperties = {
        comphelper::makePropertyValue(u"FilterName"_ustr, u"HTML (StarWriter)"_ustr),
        comphelper::makePropertyValue(u"FilterOptions"_ustr, u"xhtmlns=reqif-xhtml"_ustr),
        comphelper::makePropertyValue(u"RTFOLEMimeType"_ustr, u"text/rtf"_ustr),
        comphelper::makePropertyValue(u"SelectionOnly"_ustr, true),
    };
    saveWithParams(aStoreProperties);

    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();

    // The root element must be reqif-xhtml:object
    assertXPath(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:object", "type", u"text/rtf");
    // It has no children
    assertXPathChildren(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:object", 0);
    // And the content is empty
    assertXPathContent(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:object", u"");

    OUString aRtfData = getXPath(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:object", "data");
    INetURLObject aUrl(maTempFile.GetURL());
    aUrl.setName(aRtfData);
    SvMemoryStream aRtf;
    HtmlExportTest::wrapRtfFragment(aUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE), aRtf);
    tools::SvRef<TestReqIfRtfReader> xReader(new TestReqIfRtfReader(aRtf));
    // The RTF OLE exports correctly
    CPPUNIT_ASSERT(xReader->CallParser() != SvParserState::Error);
    CPPUNIT_ASSERT_EQUAL(tools::Long(9358), xReader->GetObjw());
    CPPUNIT_ASSERT_EQUAL(tools::Long(450), xReader->GetObjh());
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_Tdf156602)
{
    createSwDoc("NestingInA1.fodt");
    ExportToReqif();

    xmlDocUniquePtr pDoc = WrapReqifFromTempFile();

    // The outer table must be kept in the document where the outer table is the first element,
    // and its A1 starts with a nested table

    // Only two sub-elements must be inside the div: an outer table and a trailing paragraph
    assertXPathChildren(pDoc, "/reqif-xhtml:html/reqif-xhtml:div", 2);
    // The outer table must have exactly two rows
    assertXPath(pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:table/reqif-xhtml:tr", 2);
    // First outer table cell must have two sub-elements: an inner table and a trailing paragraph
    assertXPathChildren(
        pDoc,
        "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:table/reqif-xhtml:tr[1]/reqif-xhtml:td[1]",
        2);
    // The inner table must have exactly two rows
    assertXPath(
        pDoc,
        "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:table/reqif-xhtml:tr[1]/reqif-xhtml:td[1]/"
        "reqif-xhtml:table/reqif-xhtml:tr",
        2);
    // Check all the elements' content
    assertXPathContent(
        pDoc,
        "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:table/reqif-xhtml:tr[1]/reqif-xhtml:td[1]/"
        "reqif-xhtml:table/reqif-xhtml:tr[1]/reqif-xhtml:td[1]/reqif-xhtml:p",
        u"Inner.A1");
    assertXPathContent(
        pDoc,
        "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:table/reqif-xhtml:tr[1]/reqif-xhtml:td[1]/"
        "reqif-xhtml:table/reqif-xhtml:tr[1]/reqif-xhtml:td[2]/reqif-xhtml:p",
        u"Inner.B1");
    assertXPathContent(
        pDoc,
        "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:table/reqif-xhtml:tr[1]/reqif-xhtml:td[1]/"
        "reqif-xhtml:table/reqif-xhtml:tr[2]/reqif-xhtml:td[1]/reqif-xhtml:p",
        u"Inner.A2");
    assertXPathContent(
        pDoc,
        "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:table/reqif-xhtml:tr[1]/reqif-xhtml:td[1]/"
        "reqif-xhtml:table/reqif-xhtml:tr[2]/reqif-xhtml:td[2]/reqif-xhtml:p",
        u"Inner.B2");
    assertXPathContent(
        pDoc,
        "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:table/reqif-xhtml:tr[1]/reqif-xhtml:td[1]/"
        "reqif-xhtml:p",
        u"Outer.A1");
    assertXPathContent(
        pDoc,
        "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:table/reqif-xhtml:tr[1]/reqif-xhtml:td[2]/"
        "reqif-xhtml:p",
        u"Outer.B1");
    assertXPathContent(
        pDoc,
        "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:table/reqif-xhtml:tr[2]/reqif-xhtml:td[1]/"
        "reqif-xhtml:p",
        u"Outer.A2");
    assertXPathContent(
        pDoc,
        "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:table/reqif-xhtml:tr[2]/reqif-xhtml:td[2]/"
        "reqif-xhtml:p",
        u"Outer.B2");
    assertXPathContent(pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p", u"Following text");
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testTdf156647_CellPaddingRoundtrip)
{
    // Given a document with a table with cell padding:
    createSwDoc("table_cell_padding.fodt");
    {
        auto xTable = getParagraphOrTable(1);
        auto aTableBorder = getProperty<css::table::TableBorder2>(xTable, u"TableBorder2"_ustr);
        CPPUNIT_ASSERT_EQUAL(sal_Int16(1270), aTableBorder.Distance);
        CPPUNIT_ASSERT(aTableBorder.IsDistanceValid);
    }
    // When exporting to reqif-xhtml:
    ExportToReqif();
    // Make sure that we export it:
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    assertXPath(pXmlDoc, "//reqif-xhtml:table", "cellpadding", u"48"); // px
    // Now import it
    ImportFromReqif(maTempFile.GetURL());
    // Then make sure that padding is not lost:
    {
        auto xTable = getParagraphOrTable(1);
        auto aTableBorder = getProperty<css::table::TableBorder2>(xTable, u"TableBorder2"_ustr);
        // Without the accompanying fix in place, this test would have failed:
        // - Expected: 1270
        // - Actual  : 97
        // as the padding was lost, and the default 55 twip padding was used.
        CPPUNIT_ASSERT_EQUAL(sal_Int16(1270), aTableBorder.Distance);
        CPPUNIT_ASSERT(aTableBorder.IsDistanceValid);
    }
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testTdf157643_WideHBorder)
{
    // Given a document with a table with a wide border between its two rows:
    createSwDoc("table_with_wide_horizontal_border.fodt");
    // When exporting to reqif-xhtml:
    ExportToReqif();
    // Make sure that there's no extra tr's:
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    assertXPath(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:table/reqif-xhtml:tr", 2);
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testHTML_PreserveSpaces)
{
    // Given a document with leading, trailing, and repeating intermediate spaces:
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    static constexpr OUString paraText = u"\t test  \t more  text \t"_ustr;
    pWrtShell->Insert(paraText);

    // When exporting to plain HTML, using PreserveSpaces:
    saveWithParams({
        comphelper::makePropertyValue(u"FilterName"_ustr, u"HTML (StarWriter)"_ustr),
        comphelper::makePropertyValue(u"PreserveSpaces"_ustr, true),
    });

    // Then make sure that "white-space: pre-wrap" is written into the paragraph's style:
    htmlDocUniquePtr pHtmlDoc = parseHtml(maTempFile);
    CPPUNIT_ASSERT(pHtmlDoc);
    const OUString style = getXPath(pHtmlDoc, "/html/body/p", "style");
    CPPUNIT_ASSERT(style.indexOf("white-space: pre-wrap") >= 0);
    // Also check that the paragraph text is correct, without modifications in whitespace
    assertXPathContent(pHtmlDoc, "/html/body/p", paraText);

    // Test import

    setImportFilterName(TestFilter::HTML_WRITER);
    loadFromURL(maTempFile.GetURL());
    CPPUNIT_ASSERT_EQUAL(paraText, getParagraph(1)->getString());
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_PreserveSpaces)
{
    // Given a document with leading, trailing, and repeating intermediate spaces:
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    static constexpr OUString paraText = u"\t test  \t more  text \t"_ustr;
    pWrtShell->Insert(paraText);

    // When exporting to ReqIF, using PreserveSpaces:
    saveWithParams({
        comphelper::makePropertyValue(u"FilterName"_ustr, u"HTML (StarWriter)"_ustr),
        comphelper::makePropertyValue(u"FilterOptions"_ustr, u"xhtmlns=reqif-xhtml"_ustr),
        comphelper::makePropertyValue(u"PreserveSpaces"_ustr, true),
    });

    // Then make sure that xml:space="preserve" attribute exists in the paragraph element:
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    assertXPath(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p", "space", u"preserve");
    // Also check that the paragraph text is correct, without modifications in whitespace
    assertXPathContent(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p", paraText);

    // Test import

    setImportFilterOptions(u"xhtmlns=reqif-xhtml"_ustr);
    setImportFilterName(TestFilter::HTML_WRITER);
    loadFromURL(maTempFile.GetURL());
    CPPUNIT_ASSERT_EQUAL(paraText, getParagraph(1)->getString());
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testHTML_NoPreserveSpaces)
{
    // Test cases where "PreserveSpaces" should not introduce respective markup

    const auto assertXPath_NoWhiteSpaceInStyle
        = [this](const xmlDocUniquePtr& pDoc, const char* pXPath) {
              xmlXPathObjectPtr pXmlObj = getXPathNode(pDoc, pXPath);
              xmlNodeSetPtr pXmlNodes = pXmlObj->nodesetval;
              CPPUNIT_ASSERT_EQUAL_MESSAGE(pXPath, 1, xmlXPathNodeSetGetLength(pXmlNodes));
              CPPUNIT_ASSERT(pXmlNodes);
              xmlNodePtr pXmlNode = pXmlNodes->nodeTab[0];
              if (xmlChar* prop = xmlGetProp(pXmlNode, BAD_CAST("style")))
              {
                  OUString style = OUString::fromUtf8(reinterpret_cast<const char*>(prop));
                  CPPUNIT_ASSERT_MESSAGE(pXPath, style.indexOf("white-space:") < 0);
              }
              xmlXPathFreeObject(pXmlObj);
          };
    const auto assertXPath_HasWhiteSpaceInStyle
        = [this](const xmlDocUniquePtr& pDoc, const char* pXPath) {
              const OUString style = getXPath(pDoc, pXPath, "style");
              CPPUNIT_ASSERT_MESSAGE(pXPath, style.indexOf("white-space: pre-wrap") >= 0);
          };

    createSwDoc("test_no_space_preserve.fodt");

    // Export to plain HTML, using PreserveSpaces:
    saveWithParams({
        comphelper::makePropertyValue(u"FilterName"_ustr, u"HTML (StarWriter)"_ustr),
        comphelper::makePropertyValue(u"PreserveSpaces"_ustr, true),
    });

    htmlDocUniquePtr pHtmlDoc = parseHtml(maTempFile);
    CPPUNIT_ASSERT(pHtmlDoc);

    // No whitespace preservation, where no leading / trailing / double whitespace
    assertXPath_NoWhiteSpaceInStyle(pHtmlDoc, "/html/body/p[1]");
    // Whitespace preserved for a leading space
    assertXPath_HasWhiteSpaceInStyle(pHtmlDoc, "/html/body/p[2]");
    // Whitespace preserved for a trailing space
    assertXPath_HasWhiteSpaceInStyle(pHtmlDoc, "/html/body/p[3]");
    // Whitespace preserved for a double space
    assertXPath_HasWhiteSpaceInStyle(pHtmlDoc, "/html/body/p[4]");
    // No whitespace preservation for leading / trailing breaks
    assertXPath_NoWhiteSpaceInStyle(pHtmlDoc, "/html/body/p[5]");
    // Whitespace preserved for a leading break + space
    assertXPath_HasWhiteSpaceInStyle(pHtmlDoc, "/html/body/p[6]");
    // Whitespace preserved for a trailing space + break
    assertXPath_HasWhiteSpaceInStyle(pHtmlDoc, "/html/body/p[7]");
    // No whitespace preservation for a middle break
    assertXPath_NoWhiteSpaceInStyle(pHtmlDoc, "/html/body/p[8]");
    // Whitespace preserved for a middle space + break
    assertXPath_HasWhiteSpaceInStyle(pHtmlDoc, "/html/body/p[9]");
    // Whitespace preserved for a middle break + space
    assertXPath_HasWhiteSpaceInStyle(pHtmlDoc, "/html/body/p[10]");
    // No whitespace preservation for a trailing space and SVG
    assertXPath_NoWhiteSpaceInStyle(pHtmlDoc, "/html/body/p[11]");

    // Test import

    setImportFilterName(TestFilter::HTML_WRITER);
    loadFromURL(maTempFile.GetURL());

    CPPUNIT_ASSERT_EQUAL(u"No special spaces"_ustr, getParagraph(1)->getString());
    CPPUNIT_ASSERT_EQUAL(u" Leading space"_ustr, getParagraph(2)->getString());
    CPPUNIT_ASSERT_EQUAL(u"Trailing space "_ustr, getParagraph(3)->getString());
    CPPUNIT_ASSERT_EQUAL(u"Double  space"_ustr, getParagraph(4)->getString());
    // Trailing break is removed in SwHTMLParser::AppendTextNode, and replaced with para spacing
    CPPUNIT_ASSERT_EQUAL(u"\nLeading/trailing breaks"_ustr, getParagraph(5)->getString());
    CPPUNIT_ASSERT_EQUAL(u"\n Leading break + space"_ustr, getParagraph(6)->getString());
    // Trailing break is removed in SwHTMLParser::AppendTextNode, and replaced with para spacing
    CPPUNIT_ASSERT_EQUAL(u"Trailing space + break "_ustr, getParagraph(7)->getString());
    CPPUNIT_ASSERT_EQUAL(u"Middle\nbreak"_ustr, getParagraph(8)->getString());
    CPPUNIT_ASSERT_EQUAL(u"Middle space \n+ break"_ustr, getParagraph(9)->getString());
    CPPUNIT_ASSERT_EQUAL(u"Middle break\n + space"_ustr, getParagraph(10)->getString());
    // The SVG is replaced by a space in SwXParagraph::getString()
    CPPUNIT_ASSERT_EQUAL(u"Trailing space and SVG  "_ustr, getParagraph(11)->getString());
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_NoPreserveSpaces)
{
    // Test cases where "PreserveSpaces" should not introduce respective markup

    createSwDoc("test_no_space_preserve.fodt");

    // Export to ReqIF, using PreserveSpaces:
    saveWithParams({
        comphelper::makePropertyValue(u"FilterName"_ustr, u"HTML (StarWriter)"_ustr),
        comphelper::makePropertyValue(u"FilterOptions"_ustr, u"xhtmlns=reqif-xhtml"_ustr),
        comphelper::makePropertyValue(u"PreserveSpaces"_ustr, true),
    });

    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();

    // No whitespace preservation, where no leading / trailing / double whitespace
    assertXPathNoAttribute(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p[1]", "space");
    // Whitespace preserved for a leading space
    assertXPath(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p[2]", "space",
                u"preserve");
    // Whitespace preserved for a trailing space
    assertXPath(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p[3]", "space",
                u"preserve");
    // Whitespace preserved for a double space
    assertXPath(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p[4]", "space",
                u"preserve");
    // No whitespace preservation for leading / trailing breaks
    assertXPathNoAttribute(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p[5]", "space");
    // Whitespace preserved for a leading break + space
    assertXPath(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p[6]", "space",
                u"preserve");
    // No whitespace preservation for a trailing space + break
    assertXPathNoAttribute(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p[7]", "space");
    // No whitespace preservation for a middle break
    assertXPathNoAttribute(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p[8]", "space");
    // No whitespace preservation for a middle space + break
    assertXPathNoAttribute(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p[9]", "space");
    // Whitespace preserved for a middle break + space
    assertXPath(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p[10]", "space",
                u"preserve");
    // No whitespace preservation for a trailing space and SVG
    assertXPathNoAttribute(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p[11]", "space");

    // Test import

    setImportFilterOptions(u"xhtmlns=reqif-xhtml"_ustr);
    setImportFilterName(TestFilter::HTML_WRITER);
    loadFromURL(maTempFile.GetURL());

    CPPUNIT_ASSERT_EQUAL(u"No special spaces"_ustr, getParagraph(1)->getString());
    CPPUNIT_ASSERT_EQUAL(u" Leading space"_ustr, getParagraph(2)->getString());
    CPPUNIT_ASSERT_EQUAL(u"Trailing space "_ustr, getParagraph(3)->getString());
    CPPUNIT_ASSERT_EQUAL(u"Double  space"_ustr, getParagraph(4)->getString());
    CPPUNIT_ASSERT_EQUAL(u"\nLeading/trailing breaks\n"_ustr, getParagraph(5)->getString());
    CPPUNIT_ASSERT_EQUAL(u"\n Leading break + space"_ustr, getParagraph(6)->getString());
    CPPUNIT_ASSERT_EQUAL(u"Trailing space + break \n"_ustr, getParagraph(7)->getString());
    CPPUNIT_ASSERT_EQUAL(u"Middle\nbreak"_ustr, getParagraph(8)->getString());
    CPPUNIT_ASSERT_EQUAL(u"Middle space \n+ break"_ustr, getParagraph(9)->getString());
    CPPUNIT_ASSERT_EQUAL(u"Middle break\n + space"_ustr, getParagraph(10)->getString());
    // The SVG is replaced by a space in SwXParagraph::getString()
    CPPUNIT_ASSERT_EQUAL(u"Trailing space and SVG  "_ustr, getParagraph(11)->getString());
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_ExportFormulasAsPDF)
{
#if ENABLE_PDFIMPORT
    // Given a document with a formula:
    createSwDoc("embedded_formula.fodt");

    // When exporting to reqif with ExportFormulasAsPDF=true:
    uno::Sequence<beans::PropertyValue> aStoreProperties = {
        comphelper::makePropertyValue(u"FilterName"_ustr, u"HTML (StarWriter)"_ustr),
        comphelper::makePropertyValue(u"FilterOptions"_ustr, u"xhtmlns=reqif-xhtml"_ustr),
        comphelper::makePropertyValue(u"ExportFormulasAsPDF"_ustr, true),
    };
    saveWithParams(aStoreProperties);

    // Make sure that the formula is exported as PDF:
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    assertXPath(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p[2]/reqif-xhtml:object",
                "type", u"application/pdf");

    css::uno::Sequence<css::beans::PropertyValue> descr{
        comphelper::makePropertyValue(u"URL"_ustr, GetObjectPath(u".pdf"_ustr)),
    };

    uno::Reference<lang::XMultiServiceFactory> xFactory(
        comphelper::getProcessComponentContext()->getServiceManager(), uno::UNO_QUERY_THROW);
    uno::Reference<document::XTypeDetection> xTypeDetection(
        xFactory->createInstance(u"com.sun.star.document.TypeDetection"_ustr),
        uno::UNO_QUERY_THROW);

    CPPUNIT_ASSERT_EQUAL(u"pdf_Portable_Document_Format"_ustr,
                         xTypeDetection->queryTypeByDescriptor(descr, true));
#endif
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_NoBrClearForImageWrap)
{
    // Given a document with a paragraph-anchored image with "none" wrap:
    createSwDoc("image_anchored_to_paragraph_no_wrap.fodt");
    // When exporting to reqif:
    ExportToReqif();
    // Make sure that there's no 'br' elements in the 'object' (used to represent the wrapping
    // in HTML export, using 'clear' attribute):
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    assertXPath(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p/reqif-xhtml:object");
    assertXPath(pXmlDoc,
                "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p/reqif-xhtml:object/reqif-xhtml:br",
                0);
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_Tdf160017_spanClosingOrder)
{
    // Given a document with a paragraph having explicit font color and character border properties:
    createSwDoc("char_border_and_font_color.fodt");
    // When exporting to reqif:
    ExportToReqif();
    // Without the fix, this would fail, because there was an extra closing </reqif-xhtml:span>
    WrapReqifFromTempFile();
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testHTML_Tdf160017_spanClosingOrder)
{
    // Given a document with a paragraph having explicit font color and character border properties:
    createSwDoc("char_border_and_font_color.fodt");
    // When exporting to HTML:
    ExportToHTML();
    // Parse it as XML (strict!)
    // Without the fix, this would fail, because span and font elements closed in wrong order
    CPPUNIT_ASSERT(parseXml(maTempFile));
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testHTML_Tdf160390)
{
    // This document must not hang infinitely on HTML export
    createSwDoc("tdf160390.fodt");
    ExportToHTML();
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testHTML_160867)
{
    // Given a document with an image with hyperlink, and text with hyperlink, both in a frame:
    createSwDoc("tdf160867_image_with_link.fodt");
    // When exporting to HTML:
    ExportToHTML();
    // Parse it as XML (strict!)
    xmlDocUniquePtr pDoc = parseXml(maTempFile);
    CPPUNIT_ASSERT(pDoc);
    assertXPath(pDoc, "/html/body/p", 2);

    // Test export of image and text hyperlinks in the image map.
    // Without the fix, the test would fail with
    // - Expected: 1
    // - Actual  : 0
    // - In <>, XPath '/html/body/p[2]/map' number of nodes is incorrect
    const OUString mapName = getXPath(pDoc, "/html/body/p[2]/map", "name");
    assertXPath(pDoc, "/html/body/p[2]/map/area[1]", "shape", u"rect");
    CPPUNIT_ASSERT(getXPath(pDoc, "/html/body/p[2]/map/area[1]", "href").endsWith("foo/bar"));
    assertXPath(pDoc, "/html/body/p[2]/map/area[2]", "shape", u"rect");
    CPPUNIT_ASSERT(getXPath(pDoc, "/html/body/p[2]/map/area[2]", "href").endsWith("baz"));
    assertXPath(pDoc, "/html/body/p[2]/img", "usemap", Concat2View("#" + mapName));
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_160867)
{
    // Given a document with an image with hyperlink, and text with hyperlink, both in a frame:
    createSwDoc("tdf160867_image_with_link.fodt");
    // When exporting to reqif:
    ExportToReqif();
    // For now, we don't (yet) output the whole map in ReqIF case.
    // Make sure that the first hyperlink from the objects in the frame is output as an <a> element
    // around the whole image of the frame.
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    assertXPath(pXmlDoc, "//reqif-xhtml:p[2]/reqif-xhtml:a/reqif-xhtml:object");
    CPPUNIT_ASSERT(
        getXPath(pXmlDoc, "//reqif-xhtml:p[2]/reqif-xhtml:a", "href").endsWith("foo/bar"));
    // There must be no 'target' attribute
    assertXPathNoAttribute(pXmlDoc, "//reqif-xhtml:p[2]/reqif-xhtml:a", "target");
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testHTML_161979)
{
    // Given a document with two embedded metafiles:
    createSwDoc("tdf161979_metafile.fodt");
    ExportToHTML();
    xmlDocUniquePtr pDoc = parseXml(maTempFile);
    CPPUNIT_ASSERT(pDoc);
    // First image: it has no EMF+ actions, and didn't use canvas rendering before the fix;
    // yet, it didn't export correctly.
    OUString imgName = getXPath(pDoc, "/html/body/p[2]/img", "src");
    CPPUNIT_ASSERT(imgName.endsWith(".gif"));
    INetURLObject aUrl(maTempFile.GetURL());
    aUrl.setName(imgName);
    Graphic graphic;
    CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, GraphicFilter().ImportGraphic(graphic, aUrl));

    // Check that only ~4% of pixels are not transparent (before the fix, it was completely black)
    Bitmap bitmap = graphic.GetBitmap();
    Size size = bitmap.GetSizePixel();
    int numNonTransparent = 0;
    for (tools::Long y = 0; y < size.Height(); ++y)
        for (tools::Long x = 0; x < size.Width(); ++x)
            if (bitmap.GetPixelColor(x, y).GetAlpha() != 0)
                ++numNonTransparent;
    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.04, numNonTransparent / double(size.Height() * size.Width()),
                                 0.01);

    // Second image: it consists of EMF+ records (no EMF fallback). It used canvas rendering
    // before the fix; it also didn't export correctly.
    imgName = getXPath(pDoc, "/html/body/p[4]/img", "src");
    CPPUNIT_ASSERT(imgName.endsWith(".gif"));
    aUrl.SetURL(maTempFile.GetURL());
    aUrl.setName(imgName);
    graphic.Clear();
    CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, GraphicFilter().ImportGraphic(graphic, aUrl));

    // Check that some pixels are transparent (before the fix, it was completely black)
    bitmap = graphic.GetBitmap();
    size = bitmap.GetSizePixel();
    numNonTransparent = 0;
    for (tools::Long y = 0; y < size.Height(); ++y)
        for (tools::Long x = 0; x < size.Width(); ++x)
            if (bitmap.GetPixelColor(x, y).GetAlpha() != 0)
                ++numNonTransparent;
    CPPUNIT_ASSERT(numNonTransparent > 0);
    CPPUNIT_ASSERT(numNonTransparent < size.Height() * size.Width());
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_exportAbsoluteURLs_ownRelative)
{
    auto pBatch(comphelper::ConfigurationChanges::create());
    Resetter resetter([
        bInternetPreviousValue = officecfg::Office::Common::Save::URL::Internet::get(),
        bFileSystemPreviousValue = officecfg::Office::Common::Save::URL::FileSystem::get(), pBatch
    ]() {
        officecfg::Office::Common::Save::URL::Internet::set(bInternetPreviousValue, pBatch);
        officecfg::Office::Common::Save::URL::FileSystem::set(bFileSystemPreviousValue, pBatch);
        return pBatch->commit();
    });
    // Set saving absolute URLs
    officecfg::Office::Common::Save::URL::Internet::set(false, pBatch);
    officecfg::Office::Common::Save::URL::FileSystem::set(false, pBatch);
    pBatch->commit();

    createSwDoc("URLs.odt");
    // Export to ReqIF, using absolute URLs
    saveWithParams({
        comphelper::makePropertyValue(u"FilterName"_ustr, u"HTML (StarWriter)"_ustr),
        comphelper::makePropertyValue(u"FilterOptions"_ustr, u"xhtmlns=reqif-xhtml"_ustr),
        comphelper::makePropertyValue(u"ExportImagesAsOLE"_ustr, true),
        comphelper::makePropertyValue(u"RelativeOwnObjectURL"_ustr, true),
    });
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();

    // HTTP URL: must be absolute
    assertXPath(pXmlDoc, "//reqif-xhtml:p[1]/reqif-xhtml:a", "href", u"http://www.example.org/");
    // file URL: must be absolute
    assertXPath(pXmlDoc, "//reqif-xhtml:p[2]/reqif-xhtml:a", "href",
                createFileURL(u"NonExistingPath/NonExistingFile.html"));
    // form URL: must be absolute
    assertXPath(pXmlDoc, "//reqif-xhtml:form", "action", u"https://www.example.org/submit");
    // linked image exported as object: generated, must be relative
    OUString url = getXPath(pXmlDoc, "//reqif-xhtml:p[3]/reqif-xhtml:object", "data");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith(".ole"));
    // its original image URL: must be absolute
    assertXPath(pXmlDoc, "//reqif-xhtml:p[3]/reqif-xhtml:object/reqif-xhtml:object", "data",
                createFileURL(u"external.png"));
    // embedded image exported as object: generated, must be relative
    url = getXPath(pXmlDoc, "//reqif-xhtml:p[4]/reqif-xhtml:object", "data");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith(".ole"));
    // its image URL: generated, must be relative
    url = getXPath(pXmlDoc, "//reqif-xhtml:p[4]/reqif-xhtml:object/reqif-xhtml:object", "data");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith(".png"));
    // unordered list with image bullet - it gets embedded as base64 data
    OUString style = getXPath(pXmlDoc, "//reqif-xhtml:ul", "style");
    CPPUNIT_ASSERT(style.indexOf("list-style-image: url(data:image/png;base64,") != -1);
    // an as-char frame, exported as a whole to an object, must be relative
    url = getXPath(pXmlDoc, "//reqif-xhtml:p[5]/reqif-xhtml:object", "data");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith(".ole"));
    // its file hyperlink must be absolute
    assertXPath(pXmlDoc, "//reqif-xhtml:p[5]/reqif-xhtml:object/reqif-xhtml:a", "href",
                createFileURL(u"foo/bar"));
    // its image URL: generated, must be relative
    url = getXPath(
        pXmlDoc, "//reqif-xhtml:p[5]/reqif-xhtml:object/reqif-xhtml:a/reqif-xhtml:object", "data");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith(".png"));
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_exportRelativeURLs)
{
    CPPUNIT_ASSERT(officecfg::Office::Common::Save::URL::Internet::get());
    CPPUNIT_ASSERT(officecfg::Office::Common::Save::URL::FileSystem::get());

    createSwDoc("URLs.odt");
    // Export to ReqIF, using relative URLs (the default)
    saveWithParams({
        comphelper::makePropertyValue(u"FilterName"_ustr, u"HTML (StarWriter)"_ustr),
        comphelper::makePropertyValue(u"FilterOptions"_ustr, u"xhtmlns=reqif-xhtml"_ustr),
        comphelper::makePropertyValue(u"ExportImagesAsOLE"_ustr, true),
    });
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();

    // HTTP URL: must be absolute
    assertXPath(pXmlDoc, "//reqif-xhtml:p[1]/reqif-xhtml:a", "href", u"http://www.example.org/");
    // file URL: must be relative
    OUString url = getXPath(pXmlDoc, "//reqif-xhtml:p[2]/reqif-xhtml:a", "href");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith("NonExistingPath/NonExistingFile.html"));
    // form URL: must be absolute
    assertXPath(pXmlDoc, "//reqif-xhtml:form", "action", u"https://www.example.org/submit");
    // linked image exported as object: generated, must be relative
    url = getXPath(pXmlDoc, "//reqif-xhtml:p[3]/reqif-xhtml:object", "data");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith(".ole"));
    // its original image URL: must be relative
    url = getXPath(pXmlDoc, "//reqif-xhtml:p[3]/reqif-xhtml:object/reqif-xhtml:object", "data");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith("external.png"));
    // embedded image exported as object: generated, must be relative
    url = getXPath(pXmlDoc, "//reqif-xhtml:p[4]/reqif-xhtml:object", "data");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith(".ole"));
    // its image URL: generated, must be relative
    url = getXPath(pXmlDoc, "//reqif-xhtml:p[4]/reqif-xhtml:object/reqif-xhtml:object", "data");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith(".png"));
    // unordered list with image bullet - it gets embedded as base64 data
    OUString style = getXPath(pXmlDoc, "//reqif-xhtml:ul", "style");
    CPPUNIT_ASSERT(style.indexOf("list-style-image: url(data:image/png;base64,") != -1);
    // an as-char frame, exported as a whole to an object, must be relative
    url = getXPath(pXmlDoc, "//reqif-xhtml:p[5]/reqif-xhtml:object", "data");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith(".ole"));
    // its file hyperlink must be relative
    url = getXPath(pXmlDoc, "//reqif-xhtml:p[5]/reqif-xhtml:object/reqif-xhtml:a", "href");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith("foo/bar"));
    // its image URL: generated, must be relative
    url = getXPath(
        pXmlDoc, "//reqif-xhtml:p[5]/reqif-xhtml:object/reqif-xhtml:a/reqif-xhtml:object", "data");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith(".png"));
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testHTML_exportAbsoluteURLs_ownRelative)
{
    auto pBatch(comphelper::ConfigurationChanges::create());
    Resetter resetter([
        bInternetPreviousValue = officecfg::Office::Common::Save::URL::Internet::get(),
        bFileSystemPreviousValue = officecfg::Office::Common::Save::URL::FileSystem::get(), pBatch
    ]() {
        officecfg::Office::Common::Save::URL::Internet::set(bInternetPreviousValue, pBatch);
        officecfg::Office::Common::Save::URL::FileSystem::set(bFileSystemPreviousValue, pBatch);
        return pBatch->commit();
    });
    // Set saving absolute URLs
    officecfg::Office::Common::Save::URL::Internet::set(false, pBatch);
    officecfg::Office::Common::Save::URL::FileSystem::set(false, pBatch);
    pBatch->commit();

    createSwDoc("URLs.odt");
    // Export to HTML, using absolute URLs
    saveWithParams({
        comphelper::makePropertyValue(u"FilterName"_ustr, u"HTML (StarWriter)"_ustr),
        comphelper::makePropertyValue(u"RelativeOwnObjectURL"_ustr, true),
    });
    htmlDocUniquePtr pHtmlDoc = parseHtml(maTempFile);

    // HTTP URL: must be absolute
    assertXPath(pHtmlDoc, "//p[1]/a", "href", u"http://www.example.org/");
    // file URL: must be absolute
    assertXPath(pHtmlDoc, "//p[2]/a", "href",
                createFileURL(u"NonExistingPath/NonExistingFile.html"));
    // form URL: must be absolute
    assertXPath(pHtmlDoc, "//form", "action", u"https://www.example.org/submit");
    // linked image: must be absolute
    assertXPath(pHtmlDoc, "//p[3]/img", "src", createFileURL(u"external.png"));
    // embedded image: generated, must be relative
    OUString url = getXPath(pHtmlDoc, "//p[4]/img", "src");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith(".png"));
    // unordered list with image bullet - it gets embedded as base64 data
    OUString style = getXPath(pHtmlDoc, "//ul", "style");
    CPPUNIT_ASSERT(style.indexOf("list-style-image: url(data:image/png;base64,") != -1);
    // image-in-frame file hyperlink must be absolute; FIXME: HTMLOutFuncs::Out_ImageMap
    // assertXPath(pHtmlDoc, "//p[5]/map/area", "href", createFileURL(u"foo/bar"));
    // its image URL: generated, must be relative
    url = getXPath(pHtmlDoc, "//p[5]/img", "src");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith(".gif"));
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testHTML_exportRelativeURLs)
{
    CPPUNIT_ASSERT(officecfg::Office::Common::Save::URL::Internet::get());
    CPPUNIT_ASSERT(officecfg::Office::Common::Save::URL::FileSystem::get());

    createSwDoc("URLs.odt");
    // Export to HTML, using relative URLs (the default)
    ExportToHTML();
    htmlDocUniquePtr pHtmlDoc = parseHtml(maTempFile);

    // HTTP URL: must be absolute
    assertXPath(pHtmlDoc, "//p[1]/a", "href", u"http://www.example.org/");
    // file URL: must be relative
    OUString url = getXPath(pHtmlDoc, "//p[2]/a", "href");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith("NonExistingPath/NonExistingFile.html"));
    // form URL: must be absolute
    assertXPath(pHtmlDoc, "//form", "action", u"https://www.example.org/submit");
    // linked image: must be relative
    url = getXPath(pHtmlDoc, "//p[3]/img", "src");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith("external.png"));
    // embedded image: generated, must be relative
    url = getXPath(pHtmlDoc, "//p[4]/img", "src");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith(".png"));
    // unordered list with image bullet - it gets embedded as base64 data
    OUString style = getXPath(pHtmlDoc, "//ul", "style");
    CPPUNIT_ASSERT(style.indexOf("list-style-image: url(data:image/png;base64,") != -1);
    // image-in-frame file hyperlink must be relative
    url = getXPath(pHtmlDoc, "//p[5]/map/area", "href");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith("foo/bar"));
    // its image URL: generated, must be relative
    url = getXPath(pHtmlDoc, "//p[5]/img", "src");
    CPPUNIT_ASSERT(!url.startsWith("file:"));
    CPPUNIT_ASSERT(url.endsWith(".gif"));
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_162282)
{
    // Given a document with an embedded metafile:
    createSwDoc("tdf162282.odt");
    ExportToReqif();
    xmlDocUniquePtr pDoc = WrapReqifFromTempFile();

    // Check that the exported EMF is exactly the same as in the ODF package
    assertXPath(pDoc, "//reqif-xhtml:p/reqif-xhtml:object", "type", u"image/x-emf");
    OUString imgName = getXPath(pDoc, "//reqif-xhtml:p/reqif-xhtml:object", "data");
    CPPUNIT_ASSERT(imgName.endsWith(".emf"));
    INetURLObject aUrl(maTempFile.GetURL());
    aUrl.setName(imgName);
    SvFileStream aEmfStream(aUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE),
                            StreamMode::READ);

    // without the fix, this would fail with
    // - Expected: 220
    // - Actual  : 111260
    CPPUNIT_ASSERT_EQUAL(sal_uInt64(220), aEmfStream.TellEnd());

    css::uno::Sequence<sal_uInt8> emfData(220);
    aEmfStream.ReadBytes(emfData.getArray(), emfData.getLength());

    const css::uno::Sequence<sal_uInt8> correctData{
        0x01, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0xF4, 0x01, 0x00, 0x00, 0xF4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x88, 0x13, 0x00, 0x00, 0x88, 0x13, 0x00, 0x00, 0x20, 0x45, 0x4D, 0x46, 0x00,
        0x00, 0x01, 0x00, 0xDC, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00,
        0x00, 0x38, 0x04, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF4, 0x01, 0x00, 0x00, 0xF4,
        0x01, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
        0x2D, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00,
        0x00, 0xFA, 0x00, 0x00, 0x00, 0x2C, 0x01, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00,
        0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x28,
        0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x2C, 0x01, 0x00, 0x00,
        0x2C, 0x01, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x2C, 0x01, 0x00,
        0x00, 0xC8, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
    };
    CPPUNIT_ASSERT_EQUAL(correctData, emfData);
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testHTML_162426)
{
    // Given a document with an image with style:wrap="none":
    createSwDoc("tdf162426_image_with_wrap_none.fodt");
    // Before the fix, an assertion failed in HtmlWriter::attribute when exporting to HTML :
    ExportToHTML();

    xmlDocUniquePtr pDoc = parseXml(maTempFile);
    CPPUNIT_ASSERT(pDoc);

    // Before the fix, the 'border' attribute was written after the 'img' tag was already closed,
    // so without the assertion, this would fail with
    // - In <>, XPath '/html/body/p/img' no attribute 'border' exist
    assertXPath(pDoc, "/html/body/p/img", "border", u"0");
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testHTML_163873)
{
    // Given a document with an image with style:wrap="none":
    createSwDoc("tdf131728.docx");
    // Before the fix, an assertion failed in HtmlWriter::attribute when exporting to HTML :
    ExportToHTML();

    xmlDocUniquePtr pDoc = parseXml(maTempFile);
    CPPUNIT_ASSERT(pDoc);

    // Before the fix, inline headings weren't inline
    assertXPath(pDoc, "/html/body/p[5]/span/h2", "style", u"display:inline;");
    assertXPath(pDoc, "/html/body/p[6]/span/h2", "style", u"display:inline;");
    assertXPath(pDoc, "/html/body/p[7]/span/h2", "style", u"display:inline;");
    assertXPath(pDoc, "/html/body/p[11]/span/h2", "style", u"display:inline;");
    assertXPath(pDoc, "/html/body/p[14]/span/h2", "style", u"display:inline;");
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqifNoTargetInA)
{
    // Given a document with a link with target:
    createSwDoc("link_with_target.fodt");

    // When exporting to XHTML:
    ExportToReqif();

    // Check that 'a' element has no 'target' attribute
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    // Without the accompanying fix in place, this test would have failed
    assertXPathNoAttribute(pXmlDoc, "//reqif-xhtml:a", "target");
}

CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqifNoObjectBorderExport)
{
    // Given a document with an image with black border
    createSwDoc("img_with_border.fodt");

    // When exporting to XHTML:
    ExportToReqif();

    // Check that there's no 'font' element that used to be exported, containing the border color:
    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
    // Without the accompanying fix in place, this test would have failed
    assertXPath(pXmlDoc, "//reqif-xhtml:font", 0);
}

} // end of anonymous namespace
CPPUNIT_PLUGIN_IMPLEMENT();

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