sw/qa/extras/layout/data/tdf168858_nested_hidden_section.fodt |   16 +
 sw/qa/extras/layout/data/tdf168858_single_hidden_section.fodt |   14 +
 sw/qa/extras/layout/layout5.cxx                               |   99 ++++++++++
 sw/source/core/doc/DocumentFieldsManager.cxx                  |   47 ----
 4 files changed, 130 insertions(+), 46 deletions(-)

New commits:
commit 74384758e93792d16bf4af712ba39b36d1c78c70
Author:     Mike Kaganski <[email protected]>
AuthorDate: Thu Oct 23 14:52:35 2025 +0500
Commit:     Xisco Fauli <[email protected]>
CommitDate: Fri Oct 24 09:28:38 2025 +0200

    tdf#168858: drop workaround for hidden last section
    
    Commit bb6bd1ff9cd3eecec7eb2cd7bd0a4dcef584c903 (fdo#53210 
SwDoc::UpdateExpFlds
    don't crash when hiding all sections, 2012-08-10) added an exception for a 
case
    where the last section was hidden, leaving no content on a page. That 
happened
    because hidden sections, unlike e.g. hidden paragraphs, didn't generate 
frames
    for layout. The workaround set the section's condition to "0", which 
prevented
    it from hiding. It wasn't robust: the problem could happen for nested 
sections,
    where the outer section was hidden, so the inner section got hidden even 
with
    its condition set like that. Also, the workaround could apply to sections 
that
    ended up not the only content on the page (maybe that happened at a moment 
when
    layout hadn't finished yet).
    
    Commit 0c96119895b347f8eb5bb89f393351bd3c02b9f1 (tdf#159565 prerequisite: 
make
    hidden sections have zero-height frames, 2024-02-14) changed how sections 
are
    hidden, to follow what paragraphs do: they now create zero-height frames. 
That
    made the workaround unnecessary.
    
    This change reverts commit bb6bd1ff9cd3eecec7eb2cd7bd0a4dcef584c903.
    
    There is a (pre-existing) problem, that documents having only hidden 
content,
    like tdf168858_nested_hidden_section.fodt, open in read-only mode. It should
    be fixed in a separate change.
    
    Change-Id: Iac653baef0d17a2e7ec4e75acbf915f281daa9ed
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192902
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <[email protected]>
    (cherry picked from commit 0856a6a4dfcb00c63fcc13c116f570a2d6c4ca57)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192916
    Reviewed-by: Xisco Fauli <[email protected]>

diff --git a/sw/qa/extras/layout/data/tdf168858_nested_hidden_section.fodt 
b/sw/qa/extras/layout/data/tdf168858_nested_hidden_section.fodt
new file mode 100644
index 000000000000..b6b6bc453f0b
--- /dev/null
+++ b/sw/qa/extras/layout/data/tdf168858_nested_hidden_section.fodt
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" 
xmlns:ooow="http://openoffice.org/2004/writer"; office:version="1.4" 
office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:body>
+  <office:text>
+   <text:user-field-decls>
+    <text:user-field-decl office:value-type="float" office:value="0" 
text:name="foo"/>
+   </text:user-field-decls>
+   <text:section text:name="abc" text:condition="ooow:&quot;&quot; EQ 
&quot;&quot;" text:is-hidden="true" text:display="condition">
+    <text:section text:name="def" text:condition="ooow:&quot;&quot; EQ 
&quot;&quot;" text:display="condition">
+     <text:p>hidden text</text:p>
+    </text:section>
+   </text:section>
+  </office:text>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/sw/qa/extras/layout/data/tdf168858_single_hidden_section.fodt 
b/sw/qa/extras/layout/data/tdf168858_single_hidden_section.fodt
new file mode 100644
index 000000000000..99ad3e140768
--- /dev/null
+++ b/sw/qa/extras/layout/data/tdf168858_single_hidden_section.fodt
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" 
xmlns:ooow="http://openoffice.org/2004/writer"; office:version="1.4" 
office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:body>
+  <office:text>
+   <text:user-field-decls>
+    <text:user-field-decl office:value-type="float" office:value="0" 
text:name="foo"/>
+   </text:user-field-decls>
+   <text:section text:name="abc" text:condition="ooow:&quot;&quot; EQ 
&quot;&quot;" text:display="condition">
+    <text:p>hidden text</text:p>
+   </text:section>
+  </office:text>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/sw/qa/extras/layout/layout5.cxx b/sw/qa/extras/layout/layout5.cxx
index accfdb6d41b2..a1c5d6c3f79e 100644
--- a/sw/qa/extras/layout/layout5.cxx
+++ b/sw/qa/extras/layout/layout5.cxx
@@ -2001,6 +2001,105 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, 
testTdf72341GrowAllScripts)
     CPPUNIT_ASSERT_GREATER(nWidthAlephInitial, nWidthAlephResized);
 }
 
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf168858)
+{
+    // Both test documents have no content outside of hidden sections.
+    // Each test checks, that the document loads OK, sections in the document 
load with correct
+    // conditions, the layout has nothing except a section on a single page, 
and the section is
+    // hidden (height is 0). The test repeats after save-and-reload. FODT 
format is used for save,
+    // because for ODT, the schema check fails for unknown "text:is-hidden" 
attribute in section
+    // (used as cache; written in XMLSectionExport::ExportRegularSectionStart; 
handled on import
+    // in XMLSectionImportContext::ProcessAttributes).
+
+    // 1. A single section, with a condition that hides it
+    {
+        createSwDoc("tdf168858_single_hidden_section.fodt");
+        Scheduler::ProcessEventsToIdle(); // Recalculate conditions
+        auto xTextSections
+            = 
mxComponent.queryThrow<text::XTextSectionsSupplier>()->getTextSections();
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(1), 
xTextSections->getElementNames().getLength());
+
+        auto aSect = xTextSections->getByName(u"abc"_ustr);
+        // Before the fix, this was "0", because the condition was set to not 
hide the last section:
+        CPPUNIT_ASSERT_EQUAL(u"\"\" EQ \"\""_ustr, 
getProperty<OUString>(aSect, u"Condition"_ustr));
+
+        auto pXmlDoc = parseLayoutDump();
+        assertXPath(pXmlDoc, "//page['pass 1']", 1);
+        assertXPathChildren(pXmlDoc, "//page['pass 1']/body", 2);
+        assertXPathNodeName(pXmlDoc, "//page['pass 1']/body/*[1]", "infos");
+        assertXPathNodeName(pXmlDoc, "//page['pass 1']/body/*[2]", "section");
+        assertXPath(pXmlDoc, "//page['pass 1']/body/section", "formatName", 
u"abc");
+        // Before the fix, this was 276 - i.e., the frame wasn't hidden:
+        assertXPath(pXmlDoc, "//page['pass 1']/body/section/infos/bounds", 
"height", u"0");
+    }
+    {
+        saveAndReload(u"OpenDocument Text Flat XML"_ustr);
+        Scheduler::ProcessEventsToIdle(); // Recalculate conditions
+        auto xTextSections
+            = 
mxComponent.queryThrow<text::XTextSectionsSupplier>()->getTextSections();
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(1), 
xTextSections->getElementNames().getLength());
+
+        auto aSect = xTextSections->getByName(u"abc"_ustr);
+        CPPUNIT_ASSERT_EQUAL(u"\"\" EQ \"\""_ustr, 
getProperty<OUString>(aSect, u"Condition"_ustr));
+
+        auto pXmlDoc = parseLayoutDump();
+        assertXPath(pXmlDoc, "//page['pass 2']", 1);
+        assertXPathChildren(pXmlDoc, "//page['pass 2']/body", 2);
+        assertXPathNodeName(pXmlDoc, "//page['pass 2']/body/*[1]", "infos");
+        assertXPathNodeName(pXmlDoc, "//page['pass 2']/body/*[2]", "section");
+        assertXPath(pXmlDoc, "//page['pass 2']/body/section", "formatName", 
u"abc");
+        assertXPath(pXmlDoc, "//page['pass 2']/body/section/infos/bounds", 
"height", u"0");
+    }
+
+    // 2. A conditionally hidden section inside another hidden section
+    {
+        createSwDoc("tdf168858_nested_hidden_section.fodt");
+        Scheduler::ProcessEventsToIdle(); // Recalculate conditions
+        auto xTextSections
+            = 
mxComponent.queryThrow<text::XTextSectionsSupplier>()->getTextSections();
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(2), 
xTextSections->getElementNames().getLength());
+
+        auto aSect = xTextSections->getByName(u"abc"_ustr);
+        CPPUNIT_ASSERT_EQUAL(u"\"\" EQ \"\""_ustr, 
getProperty<OUString>(aSect, u"Condition"_ustr));
+        aSect = xTextSections->getByName(u"def"_ustr);
+        // Before the fix, this was "0", because the condition was set to not 
hide the last section:
+        CPPUNIT_ASSERT_EQUAL(u"\"\" EQ \"\""_ustr, 
getProperty<OUString>(aSect, u"Condition"_ustr));
+
+        auto pXmlDoc = parseLayoutDump();
+        assertXPath(pXmlDoc, "//page['pass 3']", 1);
+        assertXPathChildren(pXmlDoc, "//page['pass 3']/body", 2);
+        assertXPathNodeName(pXmlDoc, "//page['pass 3']/body/*[1]", "infos");
+        assertXPathNodeName(pXmlDoc, "//page['pass 3']/body/*[2]", "section");
+        // The inner section replaces the whole area of the outer section, so 
there's no frame for
+        // the outer section:
+        assertXPath(pXmlDoc, "//page['pass 3']/body/section", "formatName", 
u"def");
+        // Even before the fix, this was hidden:
+        assertXPath(pXmlDoc, "//page['pass 3']/body/section/infos/bounds", 
"height", u"0");
+    }
+    {
+        saveAndReload(u"OpenDocument Text Flat XML"_ustr);
+        Scheduler::ProcessEventsToIdle(); // Recalculate conditions
+        auto xTextSections
+            = 
mxComponent.queryThrow<text::XTextSectionsSupplier>()->getTextSections();
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(2), 
xTextSections->getElementNames().getLength());
+
+        auto aSect = xTextSections->getByName(u"abc"_ustr);
+        CPPUNIT_ASSERT_EQUAL(u"\"\" EQ \"\""_ustr, 
getProperty<OUString>(aSect, u"Condition"_ustr));
+        aSect = xTextSections->getByName(u"def"_ustr);
+        CPPUNIT_ASSERT_EQUAL(u"\"\" EQ \"\""_ustr, 
getProperty<OUString>(aSect, u"Condition"_ustr));
+
+        auto pXmlDoc = parseLayoutDump();
+        assertXPath(pXmlDoc, "//page['pass 4']", 1);
+        assertXPathChildren(pXmlDoc, "//page['pass 4']/body", 2);
+        assertXPathNodeName(pXmlDoc, "//page['pass 4']/body/*[1]", "infos");
+        assertXPathNodeName(pXmlDoc, "//page['pass 4']/body/*[2]", "section");
+        // The inner section replaces the whole area of the outer section, so 
there's no frame for
+        // the outer section:
+        assertXPath(pXmlDoc, "//page['pass 4']/body/section", "formatName", 
u"def");
+        assertXPath(pXmlDoc, "//page['pass 4']/body/section/infos/bounds", 
"height", u"0");
+    }
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/doc/DocumentFieldsManager.cxx 
b/sw/source/core/doc/DocumentFieldsManager.cxx
index a6bc65851b38..44247c63b3d7 100644
--- a/sw/source/core/doc/DocumentFieldsManager.cxx
+++ b/sw/source/core/doc/DocumentFieldsManager.cxx
@@ -902,38 +902,6 @@ void DocumentFieldsManager::UpdateExpFieldsImpl(
     bool bCanFill = pMgr->FillCalcWithMergeData( m_rDoc.GetNumberFormatter(), 
nLang, aCalc );
 #endif
 
-    // Make sure we don't hide all content, which would lead to a crash. 
First, count how many visible sections we have.
-    int nShownSections = 0;
-    SwNodeOffset nContentStart = 
m_rDoc.GetNodes().GetEndOfContent().StartOfSectionIndex() + 1;
-    SwNodeOffset nContentEnd = m_rDoc.GetNodes().GetEndOfContent().GetIndex();
-    SwSectionFormats& rSectFormats = m_rDoc.GetSections();
-    for( SwSectionFormats::size_type n = 0; n<rSectFormats.size(); ++n )
-    {
-        SwSectionFormat& rSectFormat = *rSectFormats[ n ];
-        SwSectionNode* pSectionNode = rSectFormat.GetSectionNode();
-        SwSection* pSect = rSectFormat.GetSection();
-
-        // Usually some of the content is not in a section: count that as a 
virtual section, so that all real sections can be hidden.
-        // Only look for section gaps at the lowest level, ignoring 
sub-sections.
-        if ( pSectionNode && !rSectFormat.GetParent() )
-        {
-            SwNodeIndex aNextIdx( *pSectionNode->EndOfSectionNode(), 1 );
-            if ( n == 0 && pSectionNode->GetIndex() != nContentStart )
-                nShownSections++;  //document does not start with a section
-            if ( n == rSectFormats.size() - 1 )
-            {
-                if ( aNextIdx.GetIndex() != nContentEnd )
-                    nShownSections++;  //document does not end in a section
-            }
-            else if ( !aNextIdx.GetNode().IsSectionNode() )
-                    nShownSections++; //section is not immediately followed by 
another section
-        }
-
-        // count only visible sections
-        if ( pSect && !pSect->CalcHiddenFlag())
-            nShownSections++;
-    }
-
     IDocumentRedlineAccess const& rIDRA(m_rDoc.getIDocumentRedlineAccess());
     std::unordered_map<SwSetExpFieldType const*, SwTextNode const*> 
SetExpOutlineNodeMap;
 
@@ -946,20 +914,7 @@ void DocumentFieldsManager::UpdateExpFieldsImpl(
                                         pSect->GetCondition() );
             if(!aValue.IsVoidValue())
             {
-                // Do we want to hide this one?
-                bool bHide = aValue.GetBool();
-                if (bHide && !pSect->IsCondHidden())
-                {
-                    // This section will be hidden, but it wasn't before
-                    if (nShownSections == 1)
-                    {
-                        // This would be the last section, so set its 
condition to false, and avoid hiding it.
-                        pSect->SetCondition(u"0"_ustr);
-                        bHide = false;
-                    }
-                    nShownSections--;
-                }
-                pSect->SetCondHidden( bHide );
+                pSect->SetCondHidden(aValue.GetBool());
             }
             continue;
         }

Reply via email to