1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* libodfgen
3 * Version: MPL 2.0 / LGPLv2.1+
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * Major Contributor(s):
10 * Copyright (C) 2002-2003 William Lachance (wrlach@gmail.com)
11 *
12 * For minor contributions see the git repository.
13 *
14 * Alternatively, the contents of this file may be used under the terms
15 * of the GNU Lesser General Public License Version 2.1 or later
16 * (LGPLv2.1+), in which case the provisions of the LGPLv2.1+ are
17 * applicable instead of those above.
18 *
19 * For further information visit http://libwpd.sourceforge.net
20 */
21
22 /* "This product is not manufactured, approved, or supported by
23 * Corel Corporation or Corel Corporation Limited."
24 */
25 #include "ListStyle.hxx"
26
27 #include "FilterInternal.hxx"
28 #include "DocumentElement.hxx"
29
30 using libodfgen::make_unique;
31
OrderedListLevelStyle(const librevenge::RVNGPropertyList & xPropList)32 OrderedListLevelStyle::OrderedListLevelStyle(const librevenge::RVNGPropertyList &xPropList) :
33 mPropList(xPropList)
34 {
35 }
36
write(OdfDocumentHandler * pHandler,int iLevel) const37 void OrderedListLevelStyle::write(OdfDocumentHandler *pHandler, int iLevel) const
38 {
39 librevenge::RVNGString sLevel;
40 sLevel.sprintf("%i", (iLevel+1));
41
42 TagOpenElement listLevelStyleOpen("text:list-level-style-number");
43 listLevelStyleOpen.addAttribute("text:level", sLevel);
44 /* TOADD if we allow redefining the main style
45 if (mPropList["text:style-name"])
46 listLevelStyleOpen.addAttribute("text:style-name", mPropList["text:style-name"]->getStr());
47 else
48 */
49 listLevelStyleOpen.addAttribute("text:style-name", "Numbering_Symbols");
50 if (mPropList["style:num-prefix"])
51 {
52 librevenge::RVNGString sEscapedString;
53 sEscapedString.appendEscapedXML(mPropList["style:num-prefix"]->getStr());
54 listLevelStyleOpen.addAttribute("style:num-prefix", sEscapedString);
55 }
56 if (mPropList["style:num-suffix"])
57 {
58 librevenge::RVNGString sEscapedString;
59 sEscapedString.appendEscapedXML(mPropList["style:num-suffix"]->getStr());
60 listLevelStyleOpen.addAttribute("style:num-suffix", sEscapedString);
61 }
62 if (mPropList["style:num-format"])
63 listLevelStyleOpen.addAttribute("style:num-format", mPropList["style:num-format"]->getStr());
64 if (mPropList["text:start-value"])
65 {
66 // ODF as to the version 1.1 does require the text:start-value to be a positive integer, means > 0
67 if (mPropList["text:start-value"]->getInt() > 0)
68 listLevelStyleOpen.addAttribute("text:start-value", mPropList["text:start-value"]->getStr());
69 else
70 listLevelStyleOpen.addAttribute("text:start-value", "1");
71 }
72 if (mPropList["text:display-levels"])
73 listLevelStyleOpen.addAttribute("text:display-levels", mPropList["text:display-levels"]->getStr());
74 listLevelStyleOpen.write(pHandler);
75
76 TagOpenElement stylePropertiesOpen("style:list-level-properties");
77 if (mPropList["text:space-before"] && mPropList["text:space-before"]->getDouble() > 0.0)
78 stylePropertiesOpen.addAttribute("text:space-before", mPropList["text:space-before"]->getStr());
79 if (mPropList["text:min-label-width"] && mPropList["text:min-label-width"]->getDouble() > 0.0)
80 stylePropertiesOpen.addAttribute("text:min-label-width", mPropList["text:min-label-width"]->getStr());
81 if (mPropList["text:min-label-distance"] && mPropList["text:min-label-distance"]->getDouble() > 0.0)
82 stylePropertiesOpen.addAttribute("text:min-label-distance", mPropList["text:min-label-distance"]->getStr());
83 if (mPropList["fo:text-align"])
84 stylePropertiesOpen.addAttribute("fo:text-align", mPropList["fo:text-align"]->getStr());
85 if (mPropList["style:font-name"])
86 stylePropertiesOpen.addAttribute("style:font-name", mPropList["style:font-name"]->getStr());
87 stylePropertiesOpen.write(pHandler);
88
89 pHandler->endElement("style:list-level-properties");
90
91 TagOpenElement textPropertiesOpen("style:text-properties");
92 if (mPropList["fo:font-family"])
93 textPropertiesOpen.addAttribute("fo:font-family", mPropList["fo:font-family"]->getStr());
94 if (mPropList["fo:font-size"])
95 textPropertiesOpen.addAttribute("fo:font-size", mPropList["fo:font-size"]->getStr());
96 if (mPropList["fo:color"])
97 textPropertiesOpen.addAttribute("fo:color", mPropList["fo:color"]->getStr());
98 textPropertiesOpen.write(pHandler);
99
100 pHandler->endElement("style:text-properties");
101
102 pHandler->endElement("text:list-level-style-number");
103 }
104
UnorderedListLevelStyle(const librevenge::RVNGPropertyList & xPropList)105 UnorderedListLevelStyle::UnorderedListLevelStyle(const librevenge::RVNGPropertyList &xPropList)
106 : mPropList(xPropList)
107 {
108 }
109
write(OdfDocumentHandler * pHandler,int iLevel) const110 void UnorderedListLevelStyle::write(OdfDocumentHandler *pHandler, int iLevel) const
111 {
112 librevenge::RVNGString sLevel;
113 sLevel.sprintf("%i", (iLevel+1));
114 TagOpenElement listLevelStyleOpen("text:list-level-style-bullet");
115 listLevelStyleOpen.addAttribute("text:level", sLevel);
116 /* TOADD if we allow redefining the main style
117 if (mPropList["text:style-name"])
118 listLevelStyleOpen.addAttribute("text:style-name", mPropList["text:style-name"]->getStr());
119 else
120 */
121 listLevelStyleOpen.addAttribute("text:style-name", "Bullet_Symbols");
122 if (mPropList["text:bullet-char"] && (mPropList["text:bullet-char"]->getStr().len()))
123 {
124 librevenge::RVNGString sEscapedString;
125 sEscapedString.appendEscapedXML((mPropList["text:bullet-char"]->getStr()));
126 listLevelStyleOpen.addAttribute("text:bullet-char", sEscapedString);
127
128 }
129 else
130 listLevelStyleOpen.addAttribute("text:bullet-char", ".");
131 if (mPropList["text:display-levels"])
132 listLevelStyleOpen.addAttribute("text:display-levels", mPropList["text:display-levels"]->getStr());
133 listLevelStyleOpen.write(pHandler);
134
135 TagOpenElement stylePropertiesOpen("style:list-level-properties");
136 if (mPropList["text:space-before"] && mPropList["text:space-before"]->getDouble() > 0.0)
137 stylePropertiesOpen.addAttribute("text:space-before", mPropList["text:space-before"]->getStr());
138 if (mPropList["text:min-label-width"] && mPropList["text:min-label-width"]->getDouble() > 0.0)
139 stylePropertiesOpen.addAttribute("text:min-label-width", mPropList["text:min-label-width"]->getStr());
140 if (mPropList["text:min-label-distance"] && mPropList["text:min-label-distance"]->getDouble() > 0.0)
141 stylePropertiesOpen.addAttribute("text:min-label-distance", mPropList["text:min-label-distance"]->getStr());
142 if (mPropList["fo:text-align"])
143 stylePropertiesOpen.addAttribute("fo:text-align", mPropList["fo:text-align"]->getStr());
144 if (mPropList["style:font-name"])
145 stylePropertiesOpen.addAttribute("style:font-name", mPropList["style:font-name"]->getStr());
146 else
147 stylePropertiesOpen.addAttribute("style:font-name", "OpenSymbol");
148 stylePropertiesOpen.write(pHandler);
149
150 pHandler->endElement("style:list-level-properties");
151
152 TagOpenElement textPropertiesOpen("style:text-properties");
153 if (mPropList["fo:font-family"])
154 textPropertiesOpen.addAttribute("fo:font-family", mPropList["fo:font-family"]->getStr());
155 if (mPropList["fo:font-size"])
156 textPropertiesOpen.addAttribute("fo:font-size", mPropList["fo:font-size"]->getStr());
157 if (mPropList["fo:color"])
158 textPropertiesOpen.addAttribute("fo:color", mPropList["fo:color"]->getStr());
159 textPropertiesOpen.write(pHandler);
160
161 pHandler->endElement("style:text-properties");
162
163 pHandler->endElement("text:list-level-style-bullet");
164 }
165
ListStyle(const char * psName,const int iListID,Style::Zone zone)166 ListStyle::ListStyle(const char *psName, const int iListID, Style::Zone zone) :
167 Style(psName, zone),
168 mDisplayName(""),
169 miListID(iListID),
170 mxListLevels()
171 {
172 }
173
~ListStyle()174 ListStyle::~ListStyle()
175 {
176 }
177
isListLevelDefined(int iLevel) const178 bool ListStyle::isListLevelDefined(int iLevel) const
179 {
180 auto iter = mxListLevels.find(iLevel);
181 return iter != mxListLevels.end() && iter->second;
182 }
183
setListLevel(int iLevel,std::unique_ptr<ListLevelStyle> iListLevelStyle)184 void ListStyle::setListLevel(int iLevel, std::unique_ptr<ListLevelStyle> iListLevelStyle)
185 {
186 // can't uncomment this next line without adding some extra logic.
187 // figure out which is best: use the initial message, or constantly
188 // update?
189 if (!isListLevelDefined(iLevel))
190 mxListLevels[iLevel] = std::move(iListLevelStyle);
191 }
192
updateListLevel(const int iLevel,const librevenge::RVNGPropertyList & xPropList,bool ordered)193 void ListStyle::updateListLevel(const int iLevel, const librevenge::RVNGPropertyList &xPropList, bool ordered)
194 {
195 if (iLevel < 0)
196 return;
197 if (!isListLevelDefined(iLevel))
198 {
199 if (ordered)
200 setListLevel(iLevel, make_unique<OrderedListLevelStyle>(xPropList));
201 else
202 setListLevel(iLevel, make_unique<UnorderedListLevelStyle>(xPropList));
203 }
204 }
205
write(OdfDocumentHandler * pHandler) const206 void ListStyle::write(OdfDocumentHandler *pHandler) const
207 {
208 TagOpenElement listStyleOpenElement("text:list-style");
209 listStyleOpenElement.addAttribute("style:name", getName());
210 if (!mDisplayName.empty())
211 listStyleOpenElement.addAttribute("style:display-name", mDisplayName);
212 listStyleOpenElement.write(pHandler);
213
214 for (const auto &xListLevel : mxListLevels)
215 {
216 if (xListLevel.second)
217 xListLevel.second->write(pHandler, xListLevel.first);
218 }
219
220 pHandler->endElement("text:list-style");
221 }
222
223 //
224 // list manager
225 //
226
State()227 ListManager::State::State() :
228 mpCurrentListStyle(),
229 miCurrentListLevel(0),
230 miLastListLevel(0),
231 miLastListNumber(0),
232 mbListContinueNumbering(false),
233 mbListElementParagraphOpened(false),
234 mbListElementOpened()
235 {
236 }
237
State(const ListManager::State & state)238 ListManager::State::State(const ListManager::State &state) :
239 mpCurrentListStyle(state.mpCurrentListStyle),
240 miCurrentListLevel(state.miCurrentListLevel),
241 miLastListLevel(state.miCurrentListLevel),
242 miLastListNumber(state.miLastListNumber),
243 mbListContinueNumbering(state.mbListContinueNumbering),
244 mbListElementParagraphOpened(state.mbListElementParagraphOpened),
245 mbListElementOpened(state.mbListElementOpened)
246 {
247 }
getState()248 ListManager::State &ListManager::getState()
249 {
250 if (!mStatesStack.empty()) return mStatesStack.top();
251 ODFGEN_DEBUG_MSG(("ListManager::getState: call with no state\n"));
252 static ListManager::State bad;
253 return bad;
254 }
255
popState()256 void ListManager::popState()
257 {
258 if (mStatesStack.size()>1)
259 mStatesStack.pop();
260 }
261
pushState()262 void ListManager::pushState()
263 {
264 mStatesStack.push(State());
265 }
266
267 //
268
ListManager()269 ListManager::ListManager() : miNumListStyles(0), mListStylesVector(), mIdListStyleMap(), mStatesStack()
270 {
271 mStatesStack.push(State());
272 }
273
~ListManager()274 ListManager::~ListManager()
275 {
276 }
277
write(OdfDocumentHandler * pHandler,Style::Zone zone) const278 void ListManager::write(OdfDocumentHandler *pHandler, Style::Zone zone) const
279 {
280 for (auto listStyle : mListStylesVector)
281 {
282 if (listStyle->getZone() == zone)
283 listStyle->write(pHandler);
284 }
285
286 }
287
defineLevel(const librevenge::RVNGPropertyList & propList,bool ordered,Style::Zone zone)288 void ListManager::defineLevel(const librevenge::RVNGPropertyList &propList, bool ordered, Style::Zone zone)
289 {
290 int id = -1;
291 if (propList["librevenge:list-id"])
292 id = propList["librevenge:list-id"]->getInt();
293
294 std::shared_ptr<ListStyle> pListStyle;
295 State &state=getState();
296 // as all direct list have the same id:-1, we reused the last list
297 // excepted at level 0 where we force the redefinition of a new list
298 if ((id!=-1 || !state.mbListElementOpened.empty()) &&
299 state.mpCurrentListStyle && state.mpCurrentListStyle->getListID() == id)
300 pListStyle = state.mpCurrentListStyle;
301
302 // this rather appalling conditional makes sure we only start a
303 // new list (rather than continue an old one) if: (1) we have no
304 // prior list or the prior list has another listId OR (2) we can
305 // tell that the user actually is starting a new list at level 1
306 // (and only level 1)
307 if (pListStyle == nullptr ||
308 (ordered && propList["librevenge:level"] && propList["librevenge:level"]->getInt()==1 &&
309 (propList["text:start-value"] && propList["text:start-value"]->getInt() != int(state.miLastListNumber+1))))
310 {
311 // first retrieve the displayname
312 librevenge::RVNGString displayName("");
313 if (propList["style:display-name"])
314 displayName=propList["style:display-name"]->getStr();
315 else if (pListStyle)
316 displayName=pListStyle->getDisplayName();
317
318 ODFGEN_DEBUG_MSG(("ListManager:defineLevel Attempting to create a new list style (listid: %i)\n", id));
319 // first check if we need to store the style as style or as automatic style
320 if (propList["style:display-name"] && !propList["style:master-page-name"])
321 zone=Style::Z_Style;
322 else if (zone==Style::Z_Unknown)
323 zone=Style::Z_ContentAutomatic;
324 librevenge::RVNGString sName;
325 if (zone==Style::Z_Style)
326 sName.sprintf(ordered ? "OL_N%i" : "UL_N%i", miNumListStyles);
327 else if (zone==Style::Z_StyleAutomatic)
328 sName.sprintf(ordered ? "OL_M%i" : "UL_M%i", miNumListStyles);
329 else
330 sName.sprintf(ordered ? "OL%i" : "UL%i", miNumListStyles);
331 miNumListStyles++;
332
333 pListStyle = std::make_shared<ListStyle>(sName.cstr(), id, zone);
334 if (!displayName.empty())
335 pListStyle->setDisplayName(displayName.cstr());
336
337 mListStylesVector.push_back(pListStyle);
338 state.mpCurrentListStyle = pListStyle;
339 mIdListStyleMap[pListStyle->getListID()]=pListStyle;
340
341 if (ordered)
342 {
343 state.mbListContinueNumbering = false;
344 state.miLastListNumber = 0;
345 }
346 }
347 else if (ordered)
348 state.mbListContinueNumbering = true;
349
350 if (!propList["librevenge:level"])
351 return;
352 // Iterate through ALL list styles with the same WordPerfect list id and define a level if it is not already defined
353 // This solves certain problems with lists that start and finish without reaching certain levels and then begin again
354 // and reach those levels. See gradguide0405_PC.wpd in the regression suite
355 for (auto &iterListStyles : mListStylesVector)
356 {
357 if (iterListStyles && iterListStyles->getListID() == id)
358 iterListStyles->updateListLevel((propList["librevenge:level"]->getInt() - 1), propList, ordered);
359 }
360 }
361
362
363 /* vim:set shiftwidth=4 softtabstop=4 noexpandtab: */
364