include/xmloff/xmltoken.hxx                                 |    1 
 schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng |   10 +
 xmloff/qa/unit/data/content-control-dropdown.fodt           |    8 
 xmloff/qa/unit/text.cxx                                     |  107 ++++++++++++
 xmloff/source/core/xmltoken.cxx                             |    1 
 xmloff/source/text/txtparae.cxx                             |   26 ++
 xmloff/source/text/xmlcontentcontrolcontext.cxx             |   60 ++++++
 xmloff/source/text/xmlcontentcontrolcontext.hxx             |   19 ++
 xmloff/source/token/tokens.txt                              |    1 
 9 files changed, 232 insertions(+), 1 deletion(-)

New commits:
commit c3f4c43694f0f9aec35193ccf40f0e7c8476fdc3
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Mon May 9 10:14:29 2022 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Mon May 9 11:16:54 2022 +0200

    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 <vmik...@collabora.com>
    Tested-by: Jenkins

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
@@ -2844,6 +2844,16 @@ 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
         <rng:ref name="string"/>
         </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>
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

Reply via email to