summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiklos Vajna <vmiklos@collabora.com>2022-05-09 10:14:29 +0200
committerMiklos Vajna <vmiklos@collabora.com>2022-05-09 11:16:54 +0200
commitc3f4c43694f0f9aec35193ccf40f0e7c8476fdc3 (patch)
tree92c3beaa8e50e2173eea3edd1f8a77eebf4e1359
parent32cb4e4fe55e662d2e515e9a6facabffe3684754 (diff)
sw content controls, drop-down: add ODT filter
Map each list item to a dedicated XML element: <loext:list-item loext:display-text="..." loext:value="..."> And do the opposite on import. Change-Id: I59a536a8317a3bb24919107b4449f858d5f6de96 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/134034 Reviewed-by: Miklos Vajna <vmiklos@collabora.com> Tested-by: Jenkins
-rw-r--r--include/xmloff/xmltoken.hxx1
-rw-r--r--schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng10
-rw-r--r--xmloff/qa/unit/data/content-control-dropdown.fodt8
-rw-r--r--xmloff/qa/unit/text.cxx107
-rw-r--r--xmloff/source/core/xmltoken.cxx1
-rw-r--r--xmloff/source/text/txtparae.cxx26
-rw-r--r--xmloff/source/text/xmlcontentcontrolcontext.cxx60
-rw-r--r--xmloff/source/text/xmlcontentcontrolcontext.hxx19
-rw-r--r--xmloff/source/token/tokens.txt1
9 files changed, 232 insertions, 1 deletions
diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx
index 5d1dc09e9937..cb088d6cb966 100644
--- a/include/xmloff/xmltoken.hxx
+++ b/include/xmloff/xmltoken.hxx
@@ -3493,6 +3493,7 @@ namespace xmloff::token {
XML_SHOWING_PLACE_HOLDER,
XML_CHECKED_STATE,
XML_UNCHECKED_STATE,
+ XML_DISPLAY_TEXT,
XML_TOKEN_END
};
diff --git a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
index cc9db62a04a3..f46fca27ffb8 100644
--- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
+++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
@@ -2845,6 +2845,16 @@ xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
</rng:attribute>
</rng:optional>
<rng:zeroOrMore>
+ <rng:element name="loext:list-item">
+ <rng:attribute name="loext:display-text">
+ <rng:ref name="string"/>
+ </rng:attribute>
+ <rng:attribute name="loext:value">
+ <rng:ref name="string"/>
+ </rng:attribute>
+ </rng:element>
+ </rng:zeroOrMore>
+ <rng:zeroOrMore>
<rng:ref name="paragraph-content-or-hyperlink"/>
</rng:zeroOrMore>
</rng:element>
diff --git a/xmloff/qa/unit/data/content-control-dropdown.fodt b/xmloff/qa/unit/data/content-control-dropdown.fodt
new file mode 100644
index 000000000000..97344d1e8bec
--- /dev/null
+++ b/xmloff/qa/unit/data/content-control-dropdown.fodt
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<office:document xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:body>
+ <office:text>
+ <text:p><loext:content-control><loext:list-item loext:display-text="red" loext:value="R"/><loext:list-item loext:display-text="green" loext:value="G"/><loext:list-item loext:display-text="blue" loext:value="B"/>choose a color</loext:content-control></text:p>
+ </office:text>
+ </office:body>
+</office:document>
diff --git a/xmloff/qa/unit/text.cxx b/xmloff/qa/unit/text.cxx
index ba932d77b069..861a9230d609 100644
--- a/xmloff/qa/unit/text.cxx
+++ b/xmloff/qa/unit/text.cxx
@@ -12,6 +12,7 @@
#include <test/xmltesttools.hxx>
#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/PropertyValues.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/frame/XStorable.hpp>
#include <com/sun/star/text/XTextDocument.hpp>
@@ -537,6 +538,112 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testCheckboxContentControlImport)
CPPUNIT_ASSERT_EQUAL(OUString(u"☒"), xContent->getString());
}
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testDropdownContentControlExport)
+{
+ // Given a document with a dropdown content control around a text portion:
+ getComponent() = loadFromDesktop("private:factory/swriter");
+ uno::Reference<lang::XMultiServiceFactory> xMSF(getComponent(), uno::UNO_QUERY);
+ uno::Reference<text::XTextDocument> xTextDocument(getComponent(), uno::UNO_QUERY);
+ uno::Reference<text::XText> xText = xTextDocument->getText();
+ uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
+ xText->insertString(xCursor, "choose an item", /*bAbsorb=*/false);
+ xCursor->gotoStart(/*bExpand=*/false);
+ xCursor->gotoEnd(/*bExpand=*/true);
+ uno::Reference<text::XTextContent> xContentControl(
+ xMSF->createInstance("com.sun.star.text.ContentControl"), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
+ {
+ uno::Sequence<beans::PropertyValues> aListItems = {
+ {
+ comphelper::makePropertyValue("DisplayText", uno::Any(OUString("red"))),
+ comphelper::makePropertyValue("Value", uno::Any(OUString("R"))),
+ },
+ {
+ comphelper::makePropertyValue("DisplayText", uno::Any(OUString("green"))),
+ comphelper::makePropertyValue("Value", uno::Any(OUString("G"))),
+ },
+ {
+ comphelper::makePropertyValue("DisplayText", uno::Any(OUString("blue"))),
+ comphelper::makePropertyValue("Value", uno::Any(OUString("B"))),
+ },
+ };
+ xContentControlProps->setPropertyValue("ListItems", uno::Any(aListItems));
+ }
+ xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true);
+
+ // When exporting to ODT:
+ uno::Reference<frame::XStorable> xStorable(getComponent(), uno::UNO_QUERY);
+ uno::Sequence<beans::PropertyValue> aStoreProps = comphelper::InitPropertySequence({
+ { "FilterName", uno::Any(OUString("writer8")) },
+ });
+ utl::TempFile aTempFile;
+ aTempFile.EnableKillingFile();
+ xStorable->storeToURL(aTempFile.GetURL(), aStoreProps);
+ validate(aTempFile.GetFileName(), test::ODF);
+
+ // Then make sure the expected markup is used:
+ std::unique_ptr<SvStream> pStream = parseExportStream(aTempFile, "content.xml");
+ xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get());
+ // Without the accompanying fix in place, this failed with:
+ // - Expected: 1
+ // - Actual : 0
+ // - XPath '//loext:content-control/loext:list-item[1]' number of nodes is incorrect
+ // i.e. the list items were lost on export.
+ assertXPath(pXmlDoc, "//loext:content-control/loext:list-item[1]", "display-text", "red");
+ assertXPath(pXmlDoc, "//loext:content-control/loext:list-item[1]", "value", "R");
+ assertXPath(pXmlDoc, "//loext:content-control/loext:list-item[2]", "display-text", "green");
+ assertXPath(pXmlDoc, "//loext:content-control/loext:list-item[2]", "value", "G");
+ assertXPath(pXmlDoc, "//loext:content-control/loext:list-item[3]", "display-text", "blue");
+ assertXPath(pXmlDoc, "//loext:content-control/loext:list-item[3]", "value", "B");
+}
+
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testDropdownContentControlImport)
+{
+ // Given an ODF document with a dropdown content control:
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "content-control-dropdown.fodt";
+
+ // When loading that document:
+ getComponent() = loadFromDesktop(aURL);
+
+ // Then make sure that the content control is not lost on import:
+ uno::Reference<text::XTextDocument> xTextDocument(getComponent(), uno::UNO_QUERY);
+ uno::Reference<container::XEnumerationAccess> xParagraphsAccess(xTextDocument->getText(),
+ uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xParagraphs = xParagraphsAccess->createEnumeration();
+ uno::Reference<container::XEnumerationAccess> xParagraph(xParagraphs->nextElement(),
+ uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xPortions = xParagraph->createEnumeration();
+ uno::Reference<beans::XPropertySet> xTextPortion(xPortions->nextElement(), uno::UNO_QUERY);
+ OUString aPortionType;
+ xTextPortion->getPropertyValue("TextPortionType") >>= aPortionType;
+ CPPUNIT_ASSERT_EQUAL(OUString("ContentControl"), aPortionType);
+ uno::Reference<text::XTextContent> xContentControl;
+ xTextPortion->getPropertyValue("ContentControl") >>= xContentControl;
+ uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
+ uno::Sequence<beans::PropertyValues> aListItems;
+ xContentControlProps->getPropertyValue("ListItems") >>= aListItems;
+ // Without the accompanying fix in place, this failed with:
+ // - Expected: 3
+ // - Actual : 0
+ // i.e. the list items were lost on import.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(3), aListItems.getLength());
+ comphelper::SequenceAsHashMap aMap0(aListItems[0]);
+ CPPUNIT_ASSERT_EQUAL(OUString("red"), aMap0["DisplayText"].get<OUString>());
+ CPPUNIT_ASSERT_EQUAL(OUString("R"), aMap0["Value"].get<OUString>());
+ comphelper::SequenceAsHashMap aMap1(aListItems[1]);
+ CPPUNIT_ASSERT_EQUAL(OUString("green"), aMap1["DisplayText"].get<OUString>());
+ CPPUNIT_ASSERT_EQUAL(OUString("G"), aMap1["Value"].get<OUString>());
+ comphelper::SequenceAsHashMap aMap2(aListItems[2]);
+ CPPUNIT_ASSERT_EQUAL(OUString("blue"), aMap2["DisplayText"].get<OUString>());
+ CPPUNIT_ASSERT_EQUAL(OUString("B"), aMap2["Value"].get<OUString>());
+ uno::Reference<text::XTextRange> xContentControlRange(xContentControl, uno::UNO_QUERY);
+ uno::Reference<text::XText> xText = xContentControlRange->getText();
+ uno::Reference<container::XEnumerationAccess> xContentEnumAccess(xText, uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xContentEnum = xContentEnumAccess->createEnumeration();
+ uno::Reference<text::XTextRange> xContent(xContentEnum->nextElement(), uno::UNO_QUERY);
+ CPPUNIT_ASSERT_EQUAL(OUString("choose a color"), xContent->getString());
+}
+
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx
index d1485460f83a..b8ffa8f72ca9 100644
--- a/xmloff/source/core/xmltoken.cxx
+++ b/xmloff/source/core/xmltoken.cxx
@@ -3496,6 +3496,7 @@ namespace xmloff::token {
TOKEN("showing-place-holder", XML_SHOWING_PLACE_HOLDER ),
TOKEN("checked-state", XML_CHECKED_STATE),
TOKEN("unchecked-state", XML_UNCHECKED_STATE),
+ TOKEN("display-text", XML_DISPLAY_TEXT),
#if OSL_DEBUG_LEVEL > 0
diff --git a/xmloff/source/text/txtparae.cxx b/xmloff/source/text/txtparae.cxx
index e1d98201834a..ac566ace391d 100644
--- a/xmloff/source/text/txtparae.cxx
+++ b/xmloff/source/text/txtparae.cxx
@@ -110,6 +110,7 @@
#include <iterator>
#include <officecfg/Office/Common.hxx>
#include <o3tl/safeint.hxx>
+#include <comphelper/sequenceashashmap.hxx>
using namespace ::std;
using namespace ::com::sun::star;
@@ -3888,9 +3889,9 @@ void XMLTextParagraphExport::ExportContentControl(
uno::Reference<container::XEnumerationAccess> xEA(xTextContent, uno::UNO_QUERY_THROW);
uno::Reference<container::XEnumeration> xTextEnum = xEA->createEnumeration();
+ uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY_THROW);
if (bExport)
{
- uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY_THROW);
bool bShowingPlaceHolder = false;
xPropertySet->getPropertyValue("ShowingPlaceHolder") >>= bShowingPlaceHolder;
if (bShowingPlaceHolder)
@@ -3937,6 +3938,29 @@ void XMLTextParagraphExport::ExportContentControl(
SvXMLElementExport aElem(GetExport(), bExport, XML_NAMESPACE_LO_EXT, XML_CONTENT_CONTROL, false,
false);
+ // Export list items of dropdowns.
+ uno::Sequence<beans::PropertyValues> aListItems;
+ xPropertySet->getPropertyValue("ListItems") >>= aListItems;
+ for (const auto& rListItem : aListItems)
+ {
+ comphelper::SequenceAsHashMap aMap(rListItem);
+ auto it = aMap.find("DisplayText");
+ OUString aValue;
+ if (it != aMap.end() && (it->second >>= aValue))
+ {
+ GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_DISPLAY_TEXT, aValue);
+ }
+
+ it = aMap.find("Value");
+ if (it != aMap.end() && (it->second >>= aValue))
+ {
+ GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_VALUE, aValue);
+ }
+
+ SvXMLElementExport aItem(GetExport(), bExport, XML_NAMESPACE_LO_EXT, XML_LIST_ITEM, false,
+ false);
+ }
+
// Recurse to export content.
exportTextRangeEnumeration(xTextEnum, bAutoStyles, isProgress, rPrevCharIsSpace);
}
diff --git a/xmloff/source/text/xmlcontentcontrolcontext.cxx b/xmloff/source/text/xmlcontentcontrolcontext.cxx
index 885090da5330..ebe034835278 100644
--- a/xmloff/source/text/xmlcontentcontrolcontext.cxx
+++ b/xmloff/source/text/xmlcontentcontrolcontext.cxx
@@ -25,6 +25,8 @@
#include <xmloff/xmlimp.hxx>
#include <xmloff/xmlnamespace.hxx>
#include <xmloff/xmltoken.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/sequence.hxx>
#include "XMLTextMarkImportContext.hxx"
#include "txtparai.hxx"
@@ -142,12 +144,26 @@ void XMLContentControlContext::endFastElement(sal_Int32)
{
xPropertySet->setPropertyValue("UncheckedState", uno::Any(m_aUncheckedState));
}
+ if (!m_aListItems.empty())
+ {
+ xPropertySet->setPropertyValue("ListItems",
+ uno::Any(comphelper::containerToSequence(m_aListItems)));
+ }
}
css::uno::Reference<css::xml::sax::XFastContextHandler>
XMLContentControlContext::createFastChildContext(
sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList)
{
+ switch (nElement)
+ {
+ case XML_ELEMENT(LO_EXT, XML_LIST_ITEM):
+ return new XMLListItemContext(GetImport(), *this);
+ break;
+ default:
+ break;
+ }
+
return XMLImpSpanContext_Impl::CreateSpanContext(GetImport(), nElement, xAttrList, m_rHints,
m_rIgnoreLeadingSpace);
}
@@ -157,4 +173,48 @@ void XMLContentControlContext::characters(const OUString& rChars)
GetImport().GetTextImport()->InsertString(rChars, m_rIgnoreLeadingSpace);
}
+void XMLContentControlContext::AppendListItem(const css::beans::PropertyValues& rListItem)
+{
+ m_aListItems.push_back(rListItem);
+}
+
+XMLListItemContext::XMLListItemContext(SvXMLImport& rImport,
+ XMLContentControlContext& rContentControl)
+ : SvXMLImportContext(rImport)
+ , m_rContentControl(rContentControl)
+{
+}
+
+void XMLListItemContext::startFastElement(
+ sal_Int32 /*nElement*/, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList)
+{
+ OUString aDisplayText;
+ OUString aValue;
+
+ for (auto& rIter : sax_fastparser::castToFastAttributeList(xAttrList))
+ {
+ switch (rIter.getToken())
+ {
+ case XML_ELEMENT(LO_EXT, XML_DISPLAY_TEXT):
+ {
+ aDisplayText = rIter.toString();
+ break;
+ }
+ case XML_ELEMENT(LO_EXT, XML_VALUE):
+ {
+ aValue = rIter.toString();
+ break;
+ }
+ default:
+ XMLOFF_WARN_UNKNOWN("xmloff", rIter);
+ }
+ }
+
+ uno::Sequence<beans::PropertyValue> aListItem = {
+ comphelper::makePropertyValue("DisplayText", uno::Any(aDisplayText)),
+ comphelper::makePropertyValue("Value", uno::Any(aValue)),
+ };
+ m_rContentControl.AppendListItem(aListItem);
+}
+
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/xmloff/source/text/xmlcontentcontrolcontext.hxx b/xmloff/source/text/xmlcontentcontrolcontext.hxx
index 3d3e44d76445..20c39ad44d2c 100644
--- a/xmloff/source/text/xmlcontentcontrolcontext.hxx
+++ b/xmloff/source/text/xmlcontentcontrolcontext.hxx
@@ -20,7 +20,10 @@
#include <xmloff/xmlictxt.hxx>
+#include <vector>
+
#include <com/sun/star/text/XTextContent.hpp>
+#include <com/sun/star/beans/PropertyValues.hpp>
class XMLHints_Impl;
@@ -39,6 +42,7 @@ class XMLContentControlContext : public SvXMLImportContext
bool m_bChecked = false;
OUString m_aCheckedState;
OUString m_aUncheckedState;
+ std::vector<css::beans::PropertyValues> m_aListItems;
public:
XMLContentControlContext(SvXMLImport& rImport, sal_Int32 nElement, XMLHints_Impl& rHints,
@@ -55,6 +59,21 @@ public:
const css::uno::Reference<css::xml::sax::XFastAttributeList>& rAttrList) override;
void SAL_CALL characters(const OUString& rChars) override;
+
+ void AppendListItem(const css::beans::PropertyValues& rListItem);
+};
+
+/// Imports <loext:list-item> inside <loext:content-control>.
+class XMLListItemContext : public SvXMLImportContext
+{
+ XMLContentControlContext& m_rContentControl;
+
+public:
+ XMLListItemContext(SvXMLImport& rImport, XMLContentControlContext& rContentControl);
+
+ void SAL_CALL startFastElement(
+ sal_Int32 nElement,
+ const css::uno::Reference<css::xml::sax::XFastAttributeList>& xAttrList) override;
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt
index 2acc44abf3f7..1bad73bf0255 100644
--- a/xmloff/source/token/tokens.txt
+++ b/xmloff/source/token/tokens.txt
@@ -3239,4 +3239,5 @@ content-control
showing-place-holder
checked-state
unchecked-state
+display-text
TOKEN_END_DUMMY