1/*
2 * Copyright 2008-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT license.
4 */
5
6#include "Utilities.h"
7
8#include <ctype.h>
9#include <stdlib.h>
10
11#if __GNUC__ < 4
12#define va_copy		__va_copy
13#endif
14
15#include <Catalog.h>
16#include <Locale.h>
17#include <Message.h>
18
19
20#undef B_TRANSLATION_CONTEXT
21#define B_TRANSLATION_CONTEXT "Utilities"
22
23
24BString
25trim_string(const char* string, size_t len)
26{
27	BString trimmed;
28	size_t i = 0;
29	bool addSpace = false;
30
31	while (i < len) {
32		// skip space block
33		while (i < len && isspace(string[i]))
34			i++;
35
36		// write non-spaced (if any)
37		if (i < len && !isspace(string[i])) {
38			// pad with a single space, for all but the first block
39			if (addSpace)
40				trimmed << ' ';
41			else
42				addSpace = true;
43
44			// append chars
45			while (i < len && !isspace(string[i]))
46				trimmed << string[i++];
47		}
48	}
49
50	return trimmed;
51}
52
53
54void
55parse_named_url(const BString& namedURL, BString& name, BString& url)
56{
57	int32 urlStart = namedURL.FindFirst('<');
58	int32 urlEnd = namedURL.FindLast('>');
59	if (urlStart < 0 || urlEnd < 0 || urlStart + 1 >= urlEnd) {
60		name = namedURL;
61		url = namedURL;
62		return;
63	}
64
65	url.SetTo(namedURL.String() + urlStart + 1, urlEnd - urlStart - 1);
66
67	if (urlStart > 0)
68		name = trim_string(namedURL, urlStart);
69	else
70		name = url;
71}
72
73
74// #pragma mark - StringVector
75
76
77StringVector::StringVector()
78	:
79	fStrings(NULL),
80	fCount(0)
81{
82}
83
84
85StringVector::StringVector(const char* string,...)
86	:
87	fStrings(NULL),
88	fCount(0)
89{
90	if (string == NULL)
91		return;
92
93	va_list list;
94	va_start(list, string);
95	SetTo(string, list);
96	va_end(list);
97}
98
99
100StringVector::StringVector(const BMessage& strings, const char* fieldName)
101	:
102	fStrings(NULL),
103	fCount(0)
104{
105	SetTo(strings, fieldName);
106}
107
108
109StringVector::StringVector(const StringVector& other)
110	:
111	fStrings(NULL),
112	fCount(0)
113{
114	if (other.fCount == 0)
115		return;
116
117	fStrings = new BString[other.fCount];
118	fCount = other.fCount;
119
120	for (int32 i = 0; i < fCount; i++)
121		fStrings[i] = other.fStrings[i];
122}
123
124
125StringVector::~StringVector()
126{
127	Unset();
128}
129
130
131void
132StringVector::SetTo(const char* string,...)
133{
134	va_list list;
135	va_start(list, string);
136	SetTo(string, list);
137	va_end(list);
138}
139
140
141void
142StringVector::SetTo(const char* string, va_list _list)
143{
144	// free old strings
145	Unset();
146
147	if (string == NULL)
148		return;
149
150	// count strings
151	va_list list;
152	va_copy(list, _list);
153	fCount = 1;
154	while (va_arg(list, const char*) != NULL)
155		fCount++;
156	va_end(list);
157
158	// create array and copy them
159	fStrings = new BString[fCount];
160	fStrings[0] = string;
161
162	va_copy(list, _list);
163	for (int32 i = 1; i < fCount; i++)
164		fStrings[i] = va_arg(list, const char*);
165	va_end(list);
166
167}
168
169
170void
171StringVector::SetTo(const BMessage& strings, const char* fieldName,
172	const char* prefix)
173{
174	Unset();
175
176	type_code type;
177	int32 count;
178	if (strings.GetInfo(fieldName, &type, &count) != B_OK
179		|| type != B_STRING_TYPE) {
180		return;
181	}
182
183	fStrings = new BString[count];
184
185	for (int32 i = 0; i < count; i++) {
186		if (strings.FindString(fieldName, i, &fStrings[i]) != B_OK)
187			return;
188
189		if (prefix != NULL)
190			fStrings[i].Prepend(prefix);
191
192		fCount++;
193	}
194}
195
196
197void
198StringVector::Unset()
199{
200	delete[] fStrings;
201	fStrings = NULL;
202	fCount = 0;
203}
204
205
206const char*
207StringVector::StringAt(int32 index) const
208{
209	return (index >= 0 && index < fCount ? fStrings[index].String() : NULL);
210}
211
212
213// #pragma mark - PackageCredit
214
215
216PackageCredit::PackageCredit(const char* packageName)
217	:
218	fPackageName(packageName)
219{
220}
221
222
223PackageCredit::PackageCredit(const BMessage& packageDescription)
224{
225	const char* package;
226	const char* copyright;
227	const char* url;
228
229	// package and copyright are mandatory
230	if (packageDescription.FindString("Package", &package) != B_OK
231		|| packageDescription.FindString("Copyright", &copyright) != B_OK) {
232		return;
233	}
234
235	// URL is optional
236	if (packageDescription.FindString("URL", &url) != B_OK)
237		url = NULL;
238
239	fPackageName = package;
240	fCopyrights.SetTo(packageDescription, "Copyright", COPYRIGHT_STRING);
241	fLicenses.SetTo(packageDescription, "License");
242	fSources.SetTo(packageDescription, "SourceURL");
243	fURL = url;
244}
245
246
247PackageCredit::PackageCredit(const PackageCredit& other)
248	:
249	fPackageName(other.fPackageName),
250	fCopyrights(other.fCopyrights),
251	fLicenses(other.fLicenses),
252	fSources(other.fSources),
253	fURL(other.fURL)
254{
255}
256
257
258PackageCredit::~PackageCredit()
259{
260}
261
262
263bool
264PackageCredit::IsValid() const
265{
266	// should have at least a package name and a copyright
267	return fPackageName.Length() > 0 && !fCopyrights.IsEmpty();
268}
269
270
271bool
272PackageCredit::IsBetterThan(const PackageCredit& other) const
273{
274	// We prefer credits with licenses.
275	if (CountLicenses() > 0 && other.CountLicenses() == 0)
276		return true;
277
278	// Scan the copyrights for year numbers and let the greater one win.
279	return _MaxCopyrightYear() > other._MaxCopyrightYear();
280}
281
282
283PackageCredit&
284PackageCredit::SetCopyrights(const char* copyright,...)
285{
286	va_list list;
287	va_start(list, copyright);
288	fCopyrights.SetTo(copyright, list);
289	va_end(list);
290
291	return *this;
292}
293
294
295PackageCredit&
296PackageCredit::SetCopyright(const char* copyright)
297{
298	return SetCopyrights(copyright, NULL);
299}
300
301
302PackageCredit&
303PackageCredit::SetLicenses(const char* license,...)
304{
305	va_list list;
306	va_start(list, license);
307	fLicenses.SetTo(license, list);
308	va_end(list);
309
310	return *this;
311}
312
313
314PackageCredit&
315PackageCredit::SetLicense(const char* license)
316{
317	return SetLicenses(license, NULL);
318}
319
320
321PackageCredit&
322PackageCredit::SetSources(const char* source,...)
323{
324	va_list list;
325	va_start(list, source);
326	fSources.SetTo(source, list);
327	va_end(list);
328
329	return *this;
330}
331
332
333PackageCredit&
334PackageCredit::SetSource(const char* source)
335{
336	return SetSources(source, NULL);
337}
338
339
340PackageCredit&
341PackageCredit::SetURL(const char* url)
342{
343	fURL = url;
344
345	return *this;
346}
347
348
349const char*
350PackageCredit::PackageName() const
351{
352	return fPackageName.String();
353}
354
355
356const StringVector&
357PackageCredit::Copyrights() const
358{
359	return fCopyrights;
360}
361
362
363int32
364PackageCredit::CountCopyrights() const
365{
366	return fCopyrights.CountStrings();
367}
368
369
370const char*
371PackageCredit::CopyrightAt(int32 index) const
372{
373	return fCopyrights.StringAt(index);
374}
375
376
377const StringVector&
378PackageCredit::Licenses() const
379{
380	return fLicenses;
381}
382
383
384int32
385PackageCredit::CountLicenses() const
386{
387	return fLicenses.CountStrings();
388}
389
390
391const char*
392PackageCredit::LicenseAt(int32 index) const
393{
394	return fLicenses.StringAt(index);
395}
396
397
398const StringVector&
399PackageCredit::Sources() const
400{
401	return fSources;
402}
403
404
405int32
406PackageCredit::CountSources() const
407{
408	return fSources.CountStrings();
409}
410
411
412const char*
413PackageCredit::SourceAt(int32 index) const
414{
415	return fSources.StringAt(index);
416}
417
418
419const char*
420PackageCredit::URL() const
421{
422	return fURL.Length() > 0 ? fURL.String() : NULL;
423}
424
425
426/*static*/ bool
427PackageCredit::NameLessInsensitive(const PackageCredit* a,
428	const PackageCredit* b)
429{
430	return a->fPackageName.ICompare(b->fPackageName) < 0;
431}
432
433
434int
435PackageCredit::_MaxCopyrightYear() const
436{
437	int maxYear = 0;
438
439	for (int32 i = 0; const char* string = CopyrightAt(i); i++) {
440		// iterate through the numbers
441		int32 start = 0;
442		while (true) {
443			// find the next number start
444			while (string[start] != '\0' && !isdigit(string[start]))
445				start++;
446
447			if (string[start] == '\0')
448				break;
449
450			// find the end
451			int32 end = start + 1;
452			while (string[end] != '\0' && isdigit(string[end]))
453				end++;
454
455			if (end - start == 4) {
456				int year = atoi(string + start);
457				if (year > 1900 && year < 2200 && year > maxYear)
458					maxYear = year;
459			}
460
461			start = end;
462		}
463	}
464
465	return maxYear;
466}
467