include/xmloff/txtparae.hxx                     |    9 
 sw/qa/uitest/data/hyperlink_multiple_spans.fodt |   44 +++
 sw/qa/uitest/navigator/tdf148198.py             |   61 +++++
 xmloff/source/text/txtflde.cxx                  |   35 --
 xmloff/source/text/txtftne.cxx                  |   30 --
 xmloff/source/text/txtparae.cxx                 |  288 +++++++++++++-----------
 6 files changed, 276 insertions(+), 191 deletions(-)

New commits:
commit 9f4af852c4050d45bb5ab314480fc83639bea90a
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Wed Jul 13 11:37:30 2022 +0300
Commit:     Mike Kaganski <mike.kagan...@collabora.com>
CommitDate: Thu Jul 14 07:43:14 2022 +0200

    tdf#148198: merge identical hyperlinks of adjacent text ranges on ODF export
    
    The true hyperlink boundaries are available as SwpHints starts/ends, which 
are used
    in DOC(X) export (see SwWW8AttrIter::OutAttrWithRange).
    
    However, I don't see a reasonable way to expose this information to xmloff, 
so I
    decided instead to just merge the identical hyperlink properties of 
adjacent ranges
    into a single hyperlink. This will allow to fix already split hyperlinks 
saved in
    previous versions. The downside is that this disallows to have separate 
adjacent
    identical hyperlinks - I hope that this would not be a real issue.
    
    Change-Id: I901e6035a5e89bc515b5742c6a5f564c77faf05b
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/137013
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>

diff --git a/include/xmloff/txtparae.hxx b/include/xmloff/txtparae.hxx
index 546bf2adca3c..e474a9c5b763 100644
--- a/include/xmloff/txtparae.hxx
+++ b/include/xmloff/txtparae.hxx
@@ -50,7 +50,7 @@ namespace com::sun::star
 {
     namespace beans { class XPropertySet; class XPropertyState;
                       class XPropertySetInfo; }
-    namespace container { class XEnumeration; class XIndexAccess; }
+    namespace container { class XEnumeration; class XIndexAccess; class 
XNameReplace; }
     namespace text { class XTextContent; class XTextRange; class XText;
                      class XFootnote; class XTextFrame; class XTextSection;
                      class XTextField; }
@@ -182,16 +182,11 @@ public:
         return xRubyPropMapper;
     }
 
-    OUString FindTextStyleAndHyperlink(
+    OUString FindTextStyle(
             const css::uno::Reference< css::beans::XPropertySet > & rPropSet,
-            bool& rbHyperlink,
             bool& rbHasCharStyle,
             bool& rbHasAutoStyle,
             const XMLPropertyState** pAddState = nullptr) const;
-    bool addHyperlinkAttributes(
-        const css::uno::Reference< css::beans::XPropertySet > & rPropSet,
-        const css::uno::Reference< css::beans::XPropertyState > & rPropState,
-        const css::uno::Reference< css::beans::XPropertySetInfo > & 
rPropSetInfo );
 
     void exportTextRangeEnumeration(
         const css::uno::Reference< css::container::XEnumeration > & rRangeEnum,
diff --git a/sw/qa/uitest/data/hyperlink_multiple_spans.fodt 
b/sw/qa/uitest/data/hyperlink_multiple_spans.fodt
new file mode 100644
index 000000000000..bdf405ffd39d
--- /dev/null
+++ b/sw/qa/uitest/data/hyperlink_multiple_spans.fodt
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" 
xmlns:xlink="http://www.w3.org/1999/xlink"; 
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" 
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" 
xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" 
xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" 
xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"
 xmlns:officeooo="http://openoffice.org/2009/office"; office:version="1.3" 
office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:styles>
+  <style:style style:name="Emphasis" style:family="text">
+   <style:text-properties fo:font-style="italic" 
style:font-style-asian="italic" style:font-style-complex="italic"/>
+  </style:style>
+  <style:style style:name="Strong_20_Emphasis" style:display-name="Strong 
Emphasis" style:family="text">
+   <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" 
style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="Internet_20_link" style:display-name="Internet 
link" style:family="text">
+   <style:text-properties fo:color="#000080" loext:opacity="100%" 
fo:language="zxx" fo:country="none" style:text-underline-style="solid" 
style:text-underline-width="auto" style:text-underline-color="font-color" 
style:language-asian="zxx" style:country-asian="none" 
style:language-complex="zxx" style:country-complex="none"/>
+  </style:style>
+  <style:style style:name="Visited_20_Internet_20_Link" 
style:display-name="Visited Internet Link" style:family="text">
+   <style:text-properties fo:color="#800000" loext:opacity="100%" 
fo:language="zxx" fo:country="none" style:text-underline-style="solid" 
style:text-underline-width="auto" style:text-underline-color="font-color" 
style:language-asian="zxx" style:country-asian="none" 
style:language-complex="zxx" style:country-complex="none"/>
+  </style:style>
+  <style:style style:name="Footnote_20_Symbol" style:display-name="Footnote 
Symbol" style:family="text"/>
+  <style:style style:name="Footnote_20_anchor" style:display-name="Footnote 
anchor" style:family="text">
+   <style:text-properties style:text-position="super 58%"/>
+  </style:style>
+  <text:notes-configuration text:note-class="footnote" 
text:citation-style-name="Footnote_20_Symbol" 
text:citation-body-style-name="Footnote_20_anchor" style:num-format="1" 
text:start-value="0" text:footnotes-position="page" 
text:start-numbering-at="document"/>
+ </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="T1" style:family="text">
+   <style:text-properties officeooo:rsid="00083fb4"/>
+  </style:style>
+  <style:style style:name="gr1" style:family="graphic">
+   <style:graphic-properties draw:stroke="none" svg:stroke-color="#000000" 
draw:fill="solid" draw:fill-color="#ffff80" fo:min-height="2cm" 
style:run-through="foreground" style:wrap="run-through" 
style:number-wrapped-paragraphs="no-limit" style:vertical-pos="from-top" 
style:vertical-rel="paragraph" style:horizontal-pos="from-left" 
style:horizontal-rel="paragraph" 
draw:wrap-influence-on-position="once-concurrent" loext:allow-overlap="true" 
style:flow-with-text="false"/>
+   <style:paragraph-properties style:writing-mode="lr-tb"/>
+  </style:style>
+ </office:automatic-styles>
+ <office:body>
+  <office:text>
+   <text:p><text:bookmark-start text:name="Bookmark"/>This is a 
bookmark<text:bookmark-end text:name="Bookmark"/></text:p>
+   <text:p/>
+   <text:p>No hyperlink; <text:a xlink:type="simple" xlink:href="#Bookmark" 
office:target-frame-name="_top" xlink:show="replace" 
text:style-name="Internet_20_link" 
text:visited-style-name="Visited_20_Internet_20_Link"><text:span 
text:style-name="Strong_20_Emphasis">a hyperlink with an 
anchor</text:span><draw:frame text:anchor-type="char" draw:z-index="0" 
draw:name="Text Frame 1" draw:style-name="gr1" draw:text-style-name="P4" 
svg:width="2cm" svg:height="2cm" svg:x="6cm" svg:y="2cm">
+      <draw:text-box>
+       <text:p>text</text:p>
+      </draw:text-box>
+     </draw:frame>, <text:span text:style-name="Emphasis">different character 
styles</text:span>, a footnote<text:span text:style-name="Emphasis"><text:note 
text:id="ftn1" 
text:note-class="footnote"><text:note-citation>1</text:note-citation><text:note-body>
+        <text:p>A footnote</text:p></text:note-body></text:note></text:span>, 
a field <text:bookmark-ref text:reference-format="text" 
text:ref-name="Bookmark">This is a bookmark</text:bookmark-ref>, and <text:span 
text:style-name="T1">a rsid</text:span></text:a>; no hyperlink; <text:a 
xlink:type="simple" xlink:href="#Bookmark" office:target-frame-name="_top" 
xlink:show="replace" text:style-name="Internet_20_link" 
text:visited-style-name="Visited_20_Internet_20_Link">another 
hyperlink</text:a>; no hyperlink</text:p>
+  </office:text>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/sw/qa/uitest/navigator/tdf148198.py 
b/sw/qa/uitest/navigator/tdf148198.py
new file mode 100644
index 000000000000..d273bb6b5e2b
--- /dev/null
+++ b/sw/qa/uitest/navigator/tdf148198.py
@@ -0,0 +1,61 @@
+# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-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/.
+#
+
+from uitest.framework import UITestCase
+from libreoffice.uno.propertyvalue import mkPropertyValues
+from uitest.uihelper.common import get_state_as_dict, get_url_for_data_file
+
+class tdf148198(UITestCase):
+
+    def test_tdf148198(self):
+        sHyperlink0 = 'a hyperlink with an anchor, different character styles, 
a footnote1, a field This is a bookmark, and a rsid'
+        sHyperlink1 = 'another hyperlink'
+
+        with 
self.ui_test.load_file(get_url_for_data_file('hyperlink_multiple_spans.fodt')):
+            xWriterDoc = self.xUITest.getTopFocusWindow()
+            xWriterEdit = xWriterDoc.getChild('writer_edit')
+
+            self.xUITest.executeCommand('.uno:Sidebar')
+            xWriterEdit.executeAction('SIDEBAR', mkPropertyValues({'PANEL': 
'SwNavigatorPanel'}))
+
+            # wait until the navigator panel is available
+            xNavigatorPanel = 
self.ui_test.wait_until_child_is_available('NavigatorPanel')
+
+            xContentTree = xNavigatorPanel.getChild('contenttree')
+            xHyperlinks = xContentTree.getChild('7')
+            self.assertEqual('Hyperlinks', 
get_state_as_dict(xHyperlinks)['Text'])
+
+            xHyperlinks.executeAction('EXPAND', tuple())
+
+            self.assertEqual('2', get_state_as_dict(xHyperlinks)['Children'])
+            self.assertEqual(sHyperlink0, 
get_state_as_dict(xHyperlinks.getChild('0'))['Text'])
+            self.assertEqual(sHyperlink1, 
get_state_as_dict(xHyperlinks.getChild('1'))['Text'])
+
+            # save and reload: the first hyperlink must be kept in one piece
+            self.xUITest.executeCommand('.uno:Save')
+            self.xUITest.executeCommand('.uno:Reload')
+
+            # wait until the navigator panel is available
+            xNavigatorPanel = 
self.ui_test.wait_until_child_is_available('NavigatorPanel')
+
+            xContentTree = xNavigatorPanel.getChild('contenttree')
+            xHyperlinks = xContentTree.getChild('7')
+            self.assertEqual('Hyperlinks', 
get_state_as_dict(xHyperlinks)['Text'])
+
+            xHyperlinks.executeAction('EXPAND', tuple())
+
+            # without the fix in place, this would fail:
+            #   AssertionError: '2' != '11'
+            self.assertEqual('2', get_state_as_dict(xHyperlinks)['Children'])
+            self.assertEqual(sHyperlink0, 
get_state_as_dict(xHyperlinks.getChild('0'))['Text'])
+            self.assertEqual(sHyperlink1, 
get_state_as_dict(xHyperlinks.getChild('1'))['Text'])
+
+            self.xUITest.executeCommand('.uno:Sidebar')
+
+# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/xmloff/source/text/txtflde.cxx b/xmloff/source/text/txtflde.cxx
index 242b641369c8..54341aab0b01 100644
--- a/xmloff/source/text/txtflde.cxx
+++ b/xmloff/source/text/txtflde.cxx
@@ -991,44 +991,15 @@ void XMLTextFieldExport::ExportField(
                     ? aStates
                     : nullptr;
 
-    // find out whether we need to set the style or hyperlink
-    bool bHasHyperlink;
+    // find out whether we need to set the style
     bool bIsUICharStyle;
     bool bHasAutoStyle;
     OUString sStyle = GetExport().GetTextParagraphExport()->
-        FindTextStyleAndHyperlink( xRangePropSet, bHasHyperlink, 
bIsUICharStyle,
-                                   bHasAutoStyle, pStates );
+        FindTextStyle( xRangePropSet, bIsUICharStyle, bHasAutoStyle, pStates );
     bool bHasStyle = !sStyle.isEmpty();
 
-    // export hyperlink (if we have one)
-    Reference < XPropertySetInfo > xRangePropSetInfo;
-    if( bHasHyperlink )
-    {
-        Reference<XPropertyState> xRangePropState( xRangePropSet, UNO_QUERY );
-        xRangePropSetInfo = xRangePropSet->getPropertySetInfo();
-        bHasHyperlink =
-            GetExport().GetTextParagraphExport()->addHyperlinkAttributes(
-                xRangePropSet, xRangePropState,
-                xRangePropSetInfo );
-    }
-    SvXMLElementExport aHyperlink( GetExport(), bHasHyperlink,
-                                   XML_NAMESPACE_TEXT, XML_A,
-                                   false, false );
-
-    if( bHasHyperlink )
-    {
-        // export events (if supported)
-        OUString sHyperLinkEvents("HyperLinkEvents");
-        if (xRangePropSetInfo->hasPropertyByName(sHyperLinkEvents))
-        {
-            Any aAny = xRangePropSet->getPropertyValue(sHyperLinkEvents);
-            Reference<XNameReplace> xName;
-            aAny >>= xName;
-            GetExport().GetEventExport().Export(xName, false);
-        }
-    }
-
     {
+        Reference<XPropertySetInfo> xRangePropSetInfo;
         XMLTextCharStyleNamesElementExport aCharStylesExport(
             GetExport(), bIsUICharStyle &&
                          GetExport().GetTextParagraphExport()
diff --git a/xmloff/source/text/txtftne.cxx b/xmloff/source/text/txtftne.cxx
index ff37cd990b32..333ec7c6f783 100644
--- a/xmloff/source/text/txtftne.cxx
+++ b/xmloff/source/text/txtftne.cxx
@@ -89,38 +89,10 @@ void XMLTextParagraphExport::exportTextFootnote(
     {
         // create span (for citation mark) if necessary; footnote content
         // will be handled via exportTextFootnoteHelper, exportText
-        bool bHasHyperlink;
         bool bIsUICharStyle = false;
         bool bHasAutoStyle = false;
 
-        OUString sStyle = FindTextStyleAndHyperlink( rPropSet, bHasHyperlink,
-                                                     bIsUICharStyle, 
bHasAutoStyle );
-
-        // export hyperlink (if we have one)
-        Reference < XPropertySetInfo > xPropSetInfo;
-        if( bHasHyperlink )
-        {
-            Reference<XPropertyState> xPropState( rPropSet, UNO_QUERY );
-            xPropSetInfo = rPropSet->getPropertySetInfo();
-            bHasHyperlink =
-                addHyperlinkAttributes( rPropSet, xPropState, xPropSetInfo );
-        }
-        SvXMLElementExport aHyperlink( GetExport(), bHasHyperlink,
-                                       XML_NAMESPACE_TEXT, XML_A,
-                                       false, false );
-
-        if( bHasHyperlink )
-        {
-            // export events (if supported)
-            OUString sHyperLinkEvents("HyperLinkEvents");
-            if (xPropSetInfo->hasPropertyByName(sHyperLinkEvents))
-            {
-                Any a = rPropSet->getPropertyValue(sHyperLinkEvents);
-                Reference<XNameReplace> xName;
-                a >>= xName;
-                GetExport().GetEventExport().Export(xName, false);
-            }
-        }
+        OUString sStyle = FindTextStyle( rPropSet, bIsUICharStyle, 
bHasAutoStyle );
 
         {
             XMLTextCharStyleNamesElementExport aCharStylesExport(
diff --git a/xmloff/source/text/txtparae.cxx b/xmloff/source/text/txtparae.cxx
index 5b2cb233dd38..13378e3902e2 100644
--- a/xmloff/source/text/txtparae.cxx
+++ b/xmloff/source/text/txtparae.cxx
@@ -280,6 +280,148 @@ namespace
 
             void ExportParameter(const OUString& sKey, const OUString& sValue);
     };
+
+    struct HyperlinkData
+    {
+        OUString href, name, targetFrame, ustyleName, vstyleName;
+        bool serverMap = false;
+        css::uno::Reference<css::container::XNameReplace> events;
+
+        HyperlinkData() = default;
+        HyperlinkData(const css::uno::Reference<css::beans::XPropertySet>& 
rPropSet);
+
+        bool operator==(const HyperlinkData&);
+        bool operator!=(const HyperlinkData& rOther) { return 
!operator==(rOther); }
+
+        bool addHyperlinkAttributes(SvXMLExport& rExport);
+        void exportEvents(SvXMLExport& rExport);
+    };
+
+    HyperlinkData::HyperlinkData(const 
css::uno::Reference<css::beans::XPropertySet>& rPropSet)
+    {
+        const css::uno::Reference<css::beans::XPropertyState> 
xPropState(rPropSet, UNO_QUERY);
+        const auto xPropSetInfo(rPropSet->getPropertySetInfo());
+        if (xPropSetInfo->hasPropertyByName(gsHyperLinkURL)
+            && (!xPropState.is()
+                || PropertyState_DIRECT_VALUE == 
xPropState->getPropertyState(gsHyperLinkURL)))
+        {
+            rPropSet->getPropertyValue(gsHyperLinkURL) >>= href;
+        }
+
+        if (href.isEmpty())
+            return;
+
+        if (xPropSetInfo->hasPropertyByName(gsHyperLinkName)
+            && (!xPropState.is()
+                || PropertyState_DIRECT_VALUE == 
xPropState->getPropertyState(gsHyperLinkName)))
+        {
+            rPropSet->getPropertyValue(gsHyperLinkName) >>= name;
+        }
+
+        if (xPropSetInfo->hasPropertyByName(gsHyperLinkTarget)
+            && (!xPropState.is()
+                || PropertyState_DIRECT_VALUE == 
xPropState->getPropertyState(gsHyperLinkTarget)))
+        {
+            rPropSet->getPropertyValue(gsHyperLinkTarget) >>= targetFrame;
+        }
+
+        if (xPropSetInfo->hasPropertyByName(gsServerMap)
+            && (!xPropState.is()
+                || PropertyState_DIRECT_VALUE == 
xPropState->getPropertyState(gsServerMap)))
+        {
+            serverMap = 
*o3tl::doAccess<bool>(rPropSet->getPropertyValue(gsServerMap));
+        }
+
+        if (xPropSetInfo->hasPropertyByName(gsUnvisitedCharStyleName)
+            && (!xPropState.is()
+                || PropertyState_DIRECT_VALUE
+                       == 
xPropState->getPropertyState(gsUnvisitedCharStyleName)))
+        {
+            rPropSet->getPropertyValue(gsUnvisitedCharStyleName) >>= 
ustyleName;
+        }
+
+        if (xPropSetInfo->hasPropertyByName(gsVisitedCharStyleName)
+            && (!xPropState.is()
+                || PropertyState_DIRECT_VALUE
+                       == 
xPropState->getPropertyState(gsVisitedCharStyleName)))
+        {
+            rPropSet->getPropertyValue(gsVisitedCharStyleName) >>= vstyleName;
+        }
+
+        static constexpr OUStringLiteral sHyperLinkEvents(u"HyperLinkEvents");
+        if (xPropSetInfo->hasPropertyByName(sHyperLinkEvents))
+        {
+            events.set(rPropSet->getPropertyValue(sHyperLinkEvents), 
uno::UNO_QUERY);
+        }
+    }
+
+    bool HyperlinkData::operator==(const HyperlinkData& rOther)
+    {
+        if (href != rOther.href || name != rOther.name || targetFrame != 
rOther.targetFrame
+            || ustyleName != rOther.ustyleName || vstyleName != 
rOther.vstyleName
+            || serverMap != rOther.serverMap)
+            return false;
+
+        if (events == rOther.events)
+            return true;
+        if (!events || !rOther.events)
+            return false;
+
+        const css::uno::Sequence<OUString> aNames = events->getElementNames();
+        if (aNames != rOther.events->getElementNames())
+            return false;
+        for (const auto& rName : aNames)
+        {
+            const css::uno::Any aAny = events->getByName(rName);
+            const css::uno::Any aOtherAny = rOther.events->getByName(rName);
+            if (aAny != aOtherAny)
+                return false;
+        }
+        return true;
+    }
+
+    bool HyperlinkData::addHyperlinkAttributes(SvXMLExport& rExport)
+    {
+        if (href.isEmpty())
+        {
+            // hyperlink without a URL does not make sense
+            OSL_ENSURE(false, "hyperlink without a URL --> no export to ODF");
+            return false;
+        }
+
+        rExport.AddAttribute(XML_NAMESPACE_XLINK, XML_TYPE, XML_SIMPLE);
+        rExport.AddAttribute(XML_NAMESPACE_XLINK, XML_HREF, 
rExport.GetRelativeReference(href));
+
+        if (!name.isEmpty())
+            rExport.AddAttribute(XML_NAMESPACE_OFFICE, XML_NAME, name);
+
+        if (!targetFrame.isEmpty())
+        {
+            rExport.AddAttribute(XML_NAMESPACE_OFFICE, XML_TARGET_FRAME_NAME, 
targetFrame);
+            enum XMLTokenEnum eTok = targetFrame == "_blank" ? XML_NEW : 
XML_REPLACE;
+            rExport.AddAttribute(XML_NAMESPACE_XLINK, XML_SHOW, eTok);
+        }
+
+        if (serverMap)
+            rExport.AddAttribute(XML_NAMESPACE_OFFICE, XML_SERVER_MAP, 
XML_TRUE);
+
+        if (!ustyleName.isEmpty())
+            rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_STYLE_NAME,
+                                 rExport.EncodeStyleName(ustyleName));
+
+        if (!vstyleName.isEmpty())
+            rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_VISITED_STYLE_NAME,
+                                 rExport.EncodeStyleName(vstyleName));
+
+        return true;
+    }
+
+    void HyperlinkData::exportEvents(SvXMLExport& rExport)
+    {
+        // export events (if supported)
+        if (events)
+            rExport.GetEventExport().Export(events, false);
+    }
 }
 
 namespace xmloff
@@ -769,9 +911,8 @@ OUString XMLTextParagraphExport::Find(
     return sName;
 }
 
-OUString XMLTextParagraphExport::FindTextStyleAndHyperlink(
+OUString XMLTextParagraphExport::FindTextStyle(
            const Reference < XPropertySet > & rPropSet,
-        bool& rbHyperlink,
         bool& rbHasCharStyle,
         bool& rbHasAutoStyle,
         const XMLPropertyState** ppAddStates ) const
@@ -781,7 +922,7 @@ OUString XMLTextParagraphExport::FindTextStyleAndHyperlink(
 
     // Get parent and remove hyperlinks (they aren't of interest)
     OUString sName;
-    rbHyperlink = rbHasCharStyle = rbHasAutoStyle = false;
+    rbHasCharStyle = rbHasAutoStyle = false;
     sal_uInt16 nIgnoreProps = 0;
     rtl::Reference< XMLPropertySetMapper > 
xPM(xPropMapper->getPropertySetMapper());
     ::std::vector< XMLPropertyState >::iterator aFirstDel = aPropStates.end();
@@ -808,7 +949,6 @@ OUString XMLTextParagraphExport::FindTextStyleAndHyperlink(
             nIgnoreProps++;
             break;
         case CTF_HYPERLINK_URL:
-            rbHyperlink = true;
             i->mnIndex = -1;
             if( nIgnoreProps )
                 aSecondDel = i;
@@ -2123,12 +2263,29 @@ void XMLTextParagraphExport::exportTextRangeEnumeration(
      * bookmarks are used instead of fieldmarks. */
     FieldmarkType openFieldMark = NONE;
 
+    std::optional<SvXMLElementExport> oTextA;
+    HyperlinkData aHyperlinkData;
+
     while( rTextEnum->hasMoreElements() )
     {
         Reference<XPropertySet> xPropSet(rTextEnum->nextElement(), UNO_QUERY);
         Reference < XTextRange > xTxtRange(xPropSet, uno::UNO_QUERY);
         Reference<XPropertySetInfo> xPropInfo(xPropSet->getPropertySetInfo());
 
+        if (!bAutoStyles)
+        {
+            if (HyperlinkData aNewHyperlinkData(xPropSet); aNewHyperlinkData 
!= aHyperlinkData)
+            {
+                aHyperlinkData = aNewHyperlinkData;
+                oTextA.reset();
+                if (aHyperlinkData.addHyperlinkAttributes(GetExport()))
+                {
+                    oTextA.emplace(GetExport(), true, XML_NAMESPACE_TEXT, 
XML_A, false, false);
+                    aHyperlinkData.exportEvents(GetExport());
+                }
+            }
+        }
+
         if (xPropInfo->hasPropertyByName(gsTextPortionType))
         {
             OUString sType;
@@ -2890,7 +3047,6 @@ void XMLTextParagraphExport::exportAnyTextFrame(
     else
     {
         Reference< XPropertySetInfo > 
xPropSetInfo(xPropSet->getPropertySetInfo());
-        Reference< XPropertyState > xPropState( xPropSet, UNO_QUERY );
         {
             bool bAddCharStyles = pRangePropSet &&
                 lcl_txtpara_isBoundAsChar( xPropSet, xPropSetInfo );
@@ -2901,10 +3057,7 @@ void XMLTextParagraphExport::exportAnyTextFrame(
             OUString sStyle;
 
             if( bAddCharStyles )
-            {
-                bool bDummy;
-                sStyle = FindTextStyleAndHyperlink( *pRangePropSet, bDummy, 
bIsUICharStyle, bHasAutoStyle );
-            }
+                sStyle = FindTextStyle( *pRangePropSet, bIsUICharStyle, 
bHasAutoStyle );
             else
                 bIsUICharStyle = false;
 
@@ -2924,8 +3077,7 @@ void XMLTextParagraphExport::exportAnyTextFrame(
                 {
                     SvXMLElementExport aElement( GetExport(),
                         FrameType::Shape != eType &&
-                        addHyperlinkAttributes( xPropSet,
-                                                xPropState,xPropSetInfo ),
+                            
HyperlinkData(xPropSet).addHyperlinkAttributes(GetExport()),
                         XML_NAMESPACE_DRAW, XML_A, false, false );
                     switch( eType )
                     {
@@ -3364,89 +3516,6 @@ void XMLTextParagraphExport::exportTitleAndDescription(
     }
 }
 
-bool XMLTextParagraphExport::addHyperlinkAttributes(
-    const Reference< XPropertySet > & rPropSet,
-    const Reference< XPropertyState > & rPropState,
-    const Reference< XPropertySetInfo > & rPropSetInfo )
-{
-    OUString sHRef;
-
-    if( rPropSetInfo->hasPropertyByName( gsHyperLinkURL ) &&
-        ( !rPropState.is() || PropertyState_DIRECT_VALUE ==
-                    rPropState->getPropertyState( gsHyperLinkURL ) ) )
-    {
-        rPropSet->getPropertyValue( gsHyperLinkURL ) >>= sHRef;
-    }
-
-    if ( sHRef.isEmpty() )
-    {
-        // hyperlink without a URL does not make sense
-        OSL_ENSURE( false, "hyperlink without a URL --> no export to ODF" );
-        return false;
-    }
-
-    GetExport().AddAttribute(XML_NAMESPACE_XLINK, XML_TYPE, XML_SIMPLE);
-    GetExport().AddAttribute(XML_NAMESPACE_XLINK, XML_HREF,
-                             GetExport().GetRelativeReference(sHRef));
-
-    if ( rPropSetInfo->hasPropertyByName( gsHyperLinkName )
-         && ( !rPropState.is()
-              || PropertyState_DIRECT_VALUE == rPropState->getPropertyState( 
gsHyperLinkName ) ) )
-    {
-        OUString sName;
-        rPropSet->getPropertyValue( gsHyperLinkName ) >>= sName;
-        if( !sName.isEmpty() )
-            GetExport().AddAttribute(XML_NAMESPACE_OFFICE, XML_NAME, sName);
-    }
-
-    if ( rPropSetInfo->hasPropertyByName( gsHyperLinkTarget )
-         && ( !rPropState.is()
-              || PropertyState_DIRECT_VALUE == rPropState->getPropertyState( 
gsHyperLinkTarget ) ) )
-    {
-        OUString sTargetFrame;
-        rPropSet->getPropertyValue( gsHyperLinkTarget ) >>= sTargetFrame;
-        if( !sTargetFrame.isEmpty() )
-        {
-            GetExport().AddAttribute(XML_NAMESPACE_OFFICE, 
XML_TARGET_FRAME_NAME, sTargetFrame);
-            enum XMLTokenEnum eTok = sTargetFrame == "_blank" ? XML_NEW : 
XML_REPLACE;
-            GetExport().AddAttribute(XML_NAMESPACE_XLINK, XML_SHOW, eTok);
-        }
-    }
-
-    if ( rPropSetInfo->hasPropertyByName( gsServerMap )
-         && ( !rPropState.is()
-              || PropertyState_DIRECT_VALUE == rPropState->getPropertyState( 
gsServerMap ) ) )
-    {
-        bool bServerMap = *o3tl::doAccess<bool>(rPropSet->getPropertyValue( 
gsServerMap ));
-        if ( bServerMap )
-            GetExport().AddAttribute(XML_NAMESPACE_OFFICE, XML_SERVER_MAP, 
XML_TRUE);
-    }
-
-    if ( rPropSetInfo->hasPropertyByName( gsUnvisitedCharStyleName )
-         && ( !rPropState.is()
-              || PropertyState_DIRECT_VALUE == rPropState->getPropertyState( 
gsUnvisitedCharStyleName ) ) )
-    {
-        OUString sUStyleName;
-        rPropSet->getPropertyValue( gsUnvisitedCharStyleName ) >>= sUStyleName;
-        if( !sUStyleName.isEmpty() )
-            GetExport().AddAttribute(XML_NAMESPACE_TEXT, XML_STYLE_NAME,
-                                     GetExport().EncodeStyleName(sUStyleName));
-    }
-
-    if ( rPropSetInfo->hasPropertyByName( gsVisitedCharStyleName )
-         && ( !rPropState.is()
-              || PropertyState_DIRECT_VALUE == rPropState->getPropertyState( 
gsVisitedCharStyleName ) ) )
-    {
-        OUString sVStyleName;
-        rPropSet->getPropertyValue( gsVisitedCharStyleName ) >>= sVStyleName;
-        if( !sVStyleName.isEmpty() )
-            GetExport().AddAttribute(XML_NAMESPACE_TEXT, 
XML_VISITED_STYLE_NAME,
-                                     GetExport().EncodeStyleName(sVStyleName));
-    }
-
-    return true;
-}
-
 void XMLTextParagraphExport::exportTextRangeSpan(
     const css::uno::Reference< css::text::XTextRange > & rTextRange,
     Reference< XPropertySet > const & xPropSet,
@@ -3492,40 +3561,13 @@ void XMLTextParagraphExport::exportTextRange(
     }
     else
     {
-        bool bHyperlink = false;
         bool bIsUICharStyle = false;
         bool bHasAutoStyle = false;
         const OUString sStyle(
-            FindTextStyleAndHyperlink( xPropSet, bHyperlink, bIsUICharStyle, 
bHasAutoStyle ) );
+            FindTextStyle( xPropSet, bIsUICharStyle, bHasAutoStyle ) );
 
         Reference < XPropertySetInfo > xPropSetInfo;
-        bool bHyperlinkAttrsAdded = false;
-        if ( bHyperlink )
-        {
-            Reference< XPropertyState > xPropState( xPropSet, UNO_QUERY );
-            xPropSetInfo.set( xPropSet->getPropertySetInfo() );
-            bHyperlinkAttrsAdded = addHyperlinkAttributes( xPropSet, 
xPropState, xPropSetInfo );
-        }
-
-        if ( bHyperlink && bHyperlinkAttrsAdded )
-        {
-            SvXMLElementExport aElem( GetExport(), true, XML_NAMESPACE_TEXT, 
XML_A, false, false );
-
-            // export events (if supported)
-            OUString sHyperLinkEvents(
-                "HyperLinkEvents");
-            if (xPropSetInfo->hasPropertyByName(sHyperLinkEvents))
-            {
-                Reference< XNameReplace > xName( xPropSet->getPropertyValue( 
sHyperLinkEvents ), uno::UNO_QUERY );
-                GetExport().GetEventExport().Export( xName, false );
-            }
-
-            exportTextRangeSpan( rTextRange, xPropSet, xPropSetInfo, 
bIsUICharStyle, bHasAutoStyle, sStyle, rPrevCharIsSpace, openFieldMark );
-        }
-        else
-        {
-            exportTextRangeSpan( rTextRange, xPropSet, xPropSetInfo, 
bIsUICharStyle, bHasAutoStyle, sStyle, rPrevCharIsSpace, openFieldMark );
-        }
+        exportTextRangeSpan( rTextRange, xPropSet, xPropSetInfo, 
bIsUICharStyle, bHasAutoStyle, sStyle, rPrevCharIsSpace, openFieldMark );
     }
 }
 

Reply via email to