/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* libodfgen * Version: MPL 2.0 / LGPLv2.1+ * * 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/. * * Major Contributor(s): * Copyright (C) 2002-2003 William Lachance (wrlach@gmail.com) * * For minor contributions see the git repository. * * Alternatively, the contents of this file may be used under the terms * of the GNU Lesser General Public License Version 2.1 or later * (LGPLv2.1+), in which case the provisions of the LGPLv2.1+ are * applicable instead of those above. * * For further information visit http://libwpd.sourceforge.net */ /* "This product is not manufactured, approved, or supported by * Corel Corporation or Corel Corporation Limited." */ #include "ListStyle.hxx" #include "FilterInternal.hxx" #include "DocumentElement.hxx" using libodfgen::make_unique; OrderedListLevelStyle::OrderedListLevelStyle(const librevenge::RVNGPropertyList &xPropList) : mPropList(xPropList) { } void OrderedListLevelStyle::write(OdfDocumentHandler *pHandler, int iLevel) const { librevenge::RVNGString sLevel; sLevel.sprintf("%i", (iLevel+1)); TagOpenElement listLevelStyleOpen("text:list-level-style-number"); listLevelStyleOpen.addAttribute("text:level", sLevel); /* TOADD if we allow redefining the main style if (mPropList["text:style-name"]) listLevelStyleOpen.addAttribute("text:style-name", mPropList["text:style-name"]->getStr()); else */ listLevelStyleOpen.addAttribute("text:style-name", "Numbering_Symbols"); if (mPropList["style:num-prefix"]) { librevenge::RVNGString sEscapedString; sEscapedString.appendEscapedXML(mPropList["style:num-prefix"]->getStr()); listLevelStyleOpen.addAttribute("style:num-prefix", sEscapedString); } if (mPropList["style:num-suffix"]) { librevenge::RVNGString sEscapedString; sEscapedString.appendEscapedXML(mPropList["style:num-suffix"]->getStr()); listLevelStyleOpen.addAttribute("style:num-suffix", sEscapedString); } if (mPropList["style:num-format"]) listLevelStyleOpen.addAttribute("style:num-format", mPropList["style:num-format"]->getStr()); if (mPropList["text:start-value"]) { // ODF as to the version 1.1 does require the text:start-value to be a positive integer, means > 0 if (mPropList["text:start-value"]->getInt() > 0) listLevelStyleOpen.addAttribute("text:start-value", mPropList["text:start-value"]->getStr()); else listLevelStyleOpen.addAttribute("text:start-value", "1"); } if (mPropList["text:display-levels"]) listLevelStyleOpen.addAttribute("text:display-levels", mPropList["text:display-levels"]->getStr()); listLevelStyleOpen.write(pHandler); TagOpenElement stylePropertiesOpen("style:list-level-properties"); if (mPropList["text:space-before"] && mPropList["text:space-before"]->getDouble() > 0.0) stylePropertiesOpen.addAttribute("text:space-before", mPropList["text:space-before"]->getStr()); if (mPropList["text:min-label-width"] && mPropList["text:min-label-width"]->getDouble() > 0.0) stylePropertiesOpen.addAttribute("text:min-label-width", mPropList["text:min-label-width"]->getStr()); if (mPropList["text:min-label-distance"] && mPropList["text:min-label-distance"]->getDouble() > 0.0) stylePropertiesOpen.addAttribute("text:min-label-distance", mPropList["text:min-label-distance"]->getStr()); if (mPropList["fo:text-align"]) stylePropertiesOpen.addAttribute("fo:text-align", mPropList["fo:text-align"]->getStr()); if (mPropList["style:font-name"]) stylePropertiesOpen.addAttribute("style:font-name", mPropList["style:font-name"]->getStr()); stylePropertiesOpen.write(pHandler); pHandler->endElement("style:list-level-properties"); TagOpenElement textPropertiesOpen("style:text-properties"); if (mPropList["fo:font-family"]) textPropertiesOpen.addAttribute("fo:font-family", mPropList["fo:font-family"]->getStr()); if (mPropList["fo:font-size"]) textPropertiesOpen.addAttribute("fo:font-size", mPropList["fo:font-size"]->getStr()); if (mPropList["fo:color"]) textPropertiesOpen.addAttribute("fo:color", mPropList["fo:color"]->getStr()); textPropertiesOpen.write(pHandler); pHandler->endElement("style:text-properties"); pHandler->endElement("text:list-level-style-number"); } UnorderedListLevelStyle::UnorderedListLevelStyle(const librevenge::RVNGPropertyList &xPropList) : mPropList(xPropList) { } void UnorderedListLevelStyle::write(OdfDocumentHandler *pHandler, int iLevel) const { librevenge::RVNGString sLevel; sLevel.sprintf("%i", (iLevel+1)); TagOpenElement listLevelStyleOpen("text:list-level-style-bullet"); listLevelStyleOpen.addAttribute("text:level", sLevel); /* TOADD if we allow redefining the main style if (mPropList["text:style-name"]) listLevelStyleOpen.addAttribute("text:style-name", mPropList["text:style-name"]->getStr()); else */ listLevelStyleOpen.addAttribute("text:style-name", "Bullet_Symbols"); if (mPropList["text:bullet-char"] && (mPropList["text:bullet-char"]->getStr().len())) { librevenge::RVNGString sEscapedString; sEscapedString.appendEscapedXML((mPropList["text:bullet-char"]->getStr())); listLevelStyleOpen.addAttribute("text:bullet-char", sEscapedString); } else listLevelStyleOpen.addAttribute("text:bullet-char", "."); if (mPropList["text:display-levels"]) listLevelStyleOpen.addAttribute("text:display-levels", mPropList["text:display-levels"]->getStr()); listLevelStyleOpen.write(pHandler); TagOpenElement stylePropertiesOpen("style:list-level-properties"); if (mPropList["text:space-before"] && mPropList["text:space-before"]->getDouble() > 0.0) stylePropertiesOpen.addAttribute("text:space-before", mPropList["text:space-before"]->getStr()); if (mPropList["text:min-label-width"] && mPropList["text:min-label-width"]->getDouble() > 0.0) stylePropertiesOpen.addAttribute("text:min-label-width", mPropList["text:min-label-width"]->getStr()); if (mPropList["text:min-label-distance"] && mPropList["text:min-label-distance"]->getDouble() > 0.0) stylePropertiesOpen.addAttribute("text:min-label-distance", mPropList["text:min-label-distance"]->getStr()); if (mPropList["fo:text-align"]) stylePropertiesOpen.addAttribute("fo:text-align", mPropList["fo:text-align"]->getStr()); if (mPropList["style:font-name"]) stylePropertiesOpen.addAttribute("style:font-name", mPropList["style:font-name"]->getStr()); else stylePropertiesOpen.addAttribute("style:font-name", "OpenSymbol"); stylePropertiesOpen.write(pHandler); pHandler->endElement("style:list-level-properties"); TagOpenElement textPropertiesOpen("style:text-properties"); if (mPropList["fo:font-family"]) textPropertiesOpen.addAttribute("fo:font-family", mPropList["fo:font-family"]->getStr()); if (mPropList["fo:font-size"]) textPropertiesOpen.addAttribute("fo:font-size", mPropList["fo:font-size"]->getStr()); if (mPropList["fo:color"]) textPropertiesOpen.addAttribute("fo:color", mPropList["fo:color"]->getStr()); textPropertiesOpen.write(pHandler); pHandler->endElement("style:text-properties"); pHandler->endElement("text:list-level-style-bullet"); } ListStyle::ListStyle(const char *psName, const int iListID, Style::Zone zone) : Style(psName, zone), mDisplayName(""), miListID(iListID), mxListLevels() { } ListStyle::~ListStyle() { } bool ListStyle::isListLevelDefined(int iLevel) const { auto iter = mxListLevels.find(iLevel); return iter != mxListLevels.end() && iter->second; } void ListStyle::setListLevel(int iLevel, std::unique_ptr iListLevelStyle) { // can't uncomment this next line without adding some extra logic. // figure out which is best: use the initial message, or constantly // update? if (!isListLevelDefined(iLevel)) mxListLevels[iLevel] = std::move(iListLevelStyle); } void ListStyle::updateListLevel(const int iLevel, const librevenge::RVNGPropertyList &xPropList, bool ordered) { if (iLevel < 0) return; if (!isListLevelDefined(iLevel)) { if (ordered) setListLevel(iLevel, make_unique(xPropList)); else setListLevel(iLevel, make_unique(xPropList)); } } void ListStyle::write(OdfDocumentHandler *pHandler) const { TagOpenElement listStyleOpenElement("text:list-style"); listStyleOpenElement.addAttribute("style:name", getName()); if (!mDisplayName.empty()) listStyleOpenElement.addAttribute("style:display-name", mDisplayName); listStyleOpenElement.write(pHandler); for (const auto &xListLevel : mxListLevels) { if (xListLevel.second) xListLevel.second->write(pHandler, xListLevel.first); } pHandler->endElement("text:list-style"); } // // list manager // ListManager::State::State() : mpCurrentListStyle(), miCurrentListLevel(0), miLastListLevel(0), miLastListNumber(0), mbListContinueNumbering(false), mbListElementParagraphOpened(false), mbListElementOpened() { } ListManager::State::State(const ListManager::State &state) : mpCurrentListStyle(state.mpCurrentListStyle), miCurrentListLevel(state.miCurrentListLevel), miLastListLevel(state.miCurrentListLevel), miLastListNumber(state.miLastListNumber), mbListContinueNumbering(state.mbListContinueNumbering), mbListElementParagraphOpened(state.mbListElementParagraphOpened), mbListElementOpened(state.mbListElementOpened) { } ListManager::State &ListManager::getState() { if (!mStatesStack.empty()) return mStatesStack.top(); ODFGEN_DEBUG_MSG(("ListManager::getState: call with no state\n")); static ListManager::State bad; return bad; } void ListManager::popState() { if (mStatesStack.size()>1) mStatesStack.pop(); } void ListManager::pushState() { mStatesStack.push(State()); } // ListManager::ListManager() : miNumListStyles(0), mListStylesVector(), mIdListStyleMap(), mStatesStack() { mStatesStack.push(State()); } ListManager::~ListManager() { } void ListManager::write(OdfDocumentHandler *pHandler, Style::Zone zone) const { for (auto listStyle : mListStylesVector) { if (listStyle->getZone() == zone) listStyle->write(pHandler); } } void ListManager::defineLevel(const librevenge::RVNGPropertyList &propList, bool ordered, Style::Zone zone) { int id = -1; if (propList["librevenge:list-id"]) id = propList["librevenge:list-id"]->getInt(); std::shared_ptr pListStyle; State &state=getState(); // as all direct list have the same id:-1, we reused the last list // excepted at level 0 where we force the redefinition of a new list if ((id!=-1 || !state.mbListElementOpened.empty()) && state.mpCurrentListStyle && state.mpCurrentListStyle->getListID() == id) pListStyle = state.mpCurrentListStyle; // this rather appalling conditional makes sure we only start a // new list (rather than continue an old one) if: (1) we have no // prior list or the prior list has another listId OR (2) we can // tell that the user actually is starting a new list at level 1 // (and only level 1) if (pListStyle == nullptr || (ordered && propList["librevenge:level"] && propList["librevenge:level"]->getInt()==1 && (propList["text:start-value"] && propList["text:start-value"]->getInt() != int(state.miLastListNumber+1)))) { // first retrieve the displayname librevenge::RVNGString displayName(""); if (propList["style:display-name"]) displayName=propList["style:display-name"]->getStr(); else if (pListStyle) displayName=pListStyle->getDisplayName(); ODFGEN_DEBUG_MSG(("ListManager:defineLevel Attempting to create a new list style (listid: %i)\n", id)); // first check if we need to store the style as style or as automatic style if (propList["style:display-name"] && !propList["style:master-page-name"]) zone=Style::Z_Style; else if (zone==Style::Z_Unknown) zone=Style::Z_ContentAutomatic; librevenge::RVNGString sName; if (zone==Style::Z_Style) sName.sprintf(ordered ? "OL_N%i" : "UL_N%i", miNumListStyles); else if (zone==Style::Z_StyleAutomatic) sName.sprintf(ordered ? "OL_M%i" : "UL_M%i", miNumListStyles); else sName.sprintf(ordered ? "OL%i" : "UL%i", miNumListStyles); miNumListStyles++; pListStyle = std::make_shared(sName.cstr(), id, zone); if (!displayName.empty()) pListStyle->setDisplayName(displayName.cstr()); mListStylesVector.push_back(pListStyle); state.mpCurrentListStyle = pListStyle; mIdListStyleMap[pListStyle->getListID()]=pListStyle; if (ordered) { state.mbListContinueNumbering = false; state.miLastListNumber = 0; } } else if (ordered) state.mbListContinueNumbering = true; if (!propList["librevenge:level"]) return; // Iterate through ALL list styles with the same WordPerfect list id and define a level if it is not already defined // This solves certain problems with lists that start and finish without reaching certain levels and then begin again // and reach those levels. See gradguide0405_PC.wpd in the regression suite for (auto &iterListStyles : mListStylesVector) { if (iterListStyles && iterListStyles->getListID() == id) iterListStyles->updateListLevel((propList["librevenge:level"]->getInt() - 1), propList, ordered); } } /* vim:set shiftwidth=4 softtabstop=4 noexpandtab: */