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 0856a6a4dfcb00c63fcc13c116f570a2d6c4ca57 Author: Mike Kaganski <[email protected]> AuthorDate: Thu Oct 23 14:52:35 2025 +0500 Commit: Mike Kaganski <[email protected]> CommitDate: Thu Oct 23 16:30:04 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]> 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 15563999ed79..abd603974845 100644 --- a/sw/qa/extras/layout/layout5.cxx +++ b/sw/qa/extras/layout/layout5.cxx @@ -2115,6 +2115,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 8782c293628d..4367a90458eb 100644 --- a/sw/source/core/doc/DocumentFieldsManager.cxx +++ b/sw/source/core/doc/DocumentFieldsManager.cxx @@ -903,38 +903,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; @@ -947,20 +915,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; }
