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