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:"" EQ """ text:is-hidden="true" text:display="condition"> + <text:section text:name="def" text:condition="ooow:"" EQ """ 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:"" EQ """ 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; }
