Remove debug statements from KCal backends.
[beagle.git] / Util / StringFu.cs
blob1c7714ae3766ae56db83fa07e207e6ee8f9df6bf
1 //
2 // StringFu.cs
3 //
4 // Copyright (C) 2004 Novell, Inc.
5 //
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
27 using System;
28 using System.Collections;
29 using System.Globalization;
30 using System.IO;
31 using System.Text;
32 using System.Xml;
34 using Mono.Unix;
36 namespace Beagle.Util {
38 public class StringFu {
40 private StringFu () { } // class is static
42 public const string UnindexedNamespace = "_unindexed:";
44 private const String timeFormat = "yyyyMMddHHmmss";
46 static public string DateTimeToString (DateTime dt)
48 return dt.ToString (timeFormat);
51 static public string DateTimeToYearMonthString (DateTime dt)
53 return dt.ToString ("yyyyMM");
56 static public string DateTimeToDayString (DateTime dt)
58 return dt.ToString ("dd");
61 static public DateTime StringToDateTime (string str)
63 if (str == null || str == "")
64 return new DateTime ();
66 return DateTime.ParseExact (str, timeFormat, CultureInfo.CurrentCulture);
69 static public string DateTimeToFuzzy (DateTime dt)
71 DateTime today = DateTime.Today;
72 TimeSpan sinceToday = today - dt;
74 string date = null, time = null;
76 if (sinceToday.TotalDays <= 0)
77 date = Catalog.GetString ("Today");
78 else if (sinceToday.TotalDays < 1)
79 date = Catalog.GetString ("Yesterday");
80 else if (today.Year == dt.Year)
81 /* Translators: Example output: Aug 9 */
82 date = dt.ToString (Catalog.GetString ("MMM d"));
83 else
84 /* Translators: Example output: Aug 9, 2000 */
85 date = dt.ToString (Catalog.GetString ("MMM d, yyyy"));
87 /* Translators: Example output: 11:05 AM (note h = 12-hour time) */
88 time = dt.ToString (Catalog.GetString ("h:mm tt"));
90 string fuzzy;
92 if (date != null && time != null)
93 /* Translators: {0} is a date (e.g. 'Today' or 'Apr 23'), {1} is the time */
94 fuzzy = String.Format (Catalog.GetString ("{0}, {1}"), date, time);
95 else if (date != null)
96 fuzzy = date;
97 else
98 fuzzy = time;
100 return fuzzy;
103 public static string DateTimeToPrettyString (DateTime date)
105 DateTime now = DateTime.Now;
106 string short_time = date.ToShortTimeString ();
108 if (date.Year == now.Year) {
109 if (date.DayOfYear == now.DayOfYear) {
110 /* To translators: {0} is the time of the day, eg. 13:45 */
111 return String.Format (Catalog.GetString ("Today, {0}"), short_time);
112 } else if (date.DayOfYear == now.DayOfYear - 1) {
113 /* To translators: {0} is the time of the day, eg. 13:45 */
114 return String.Format (Catalog.GetString ("Yesterday, {0}"), short_time);
115 } else if (date.DayOfYear > now.DayOfYear - 6 && date.DayOfYear < now.DayOfYear) {
116 /* To translators: {0} is the number of days that have passed, {1} is the time of the day, eg. 13:45 */
117 return String.Format (Catalog.GetString ("{0} days ago, {1}"),
118 now.DayOfYear - date.DayOfYear,
119 short_time);
120 } else {
121 /* Translators: Example output: January 3, 3:45 PM */
122 return date.ToString (Catalog.GetString ("MMMM d, h:mm tt"));
126 /* Translators: Example output: March 23 2001, 10:04 AM */
127 return date.ToString (Catalog.GetString ("MMMM d yyyy, h:mm tt"));
130 public static string DurationToPrettyString (DateTime end_time, DateTime start_time)
132 TimeSpan span = end_time - start_time;
134 string span_str = "";
136 if (span.Hours > 0) {
137 span_str = String.Format (Catalog.GetPluralString ("{0} hour", "{0} hours", span.Hours), span.Hours);
139 if (span.Minutes > 0)
140 span_str += ", ";
143 if (span.Minutes > 0) {
144 span_str += String.Format (Catalog.GetPluralString ("{0} minute", "{0} minutes", span.Minutes), span.Minutes);
148 return span_str;
151 static public string FileLengthToString (long len)
153 const long oneMb = 1024*1024;
155 if (len < 0)
156 return "*BadLength*";
158 if (len < 1024)
159 /* Translators: {0} is a file size in bytes */
160 return String.Format (Catalog.GetString ("{0} bytes"), len);
162 if (len < oneMb)
163 /* Translators: {0} is a file size in kilobytes */
164 return String.Format (Catalog.GetString ("{0:0.0} KB"), len/(double)1024);
166 /* Translators: {0} is a file size in megabytes */
167 return String.Format (Catalog.GetString ("{0:0.0} MB"), len/(double)oneMb);
170 // Here we:
171 // (1) Replace non-alphanumeric characters with spaces
172 // (2) Inject whitespace between lowercase-to-uppercase
173 // transitions (so "FooBar" becomes "Foo Bar")
174 // and transitions between letters and numbers
175 // (so "cvs2svn" becomes "cvs 2 svn")
176 static public string FuzzyDivide (string line)
178 // Allocate a space slightly bigger than the
179 // original string.
180 StringBuilder builder;
181 builder = new StringBuilder (line.Length + 4);
183 int prev_case = 0;
184 bool last_was_space = true; // don't start w/ a space
185 for (int i = 0; i < line.Length; ++i) {
186 char c = line [i];
187 int this_case = 0;
188 if (Char.IsLetterOrDigit (c)) {
189 if (Char.IsUpper (c))
190 this_case = +1;
191 else if (Char.IsLower (c))
192 this_case = -1;
193 if (this_case != prev_case
194 && !(this_case == -1 && prev_case == +1)) {
195 if (! last_was_space) {
196 builder.Append (' ');
197 last_was_space = true;
201 if (c != ' ' || !last_was_space) {
202 builder.Append (c);
203 last_was_space = (c == ' ');
206 prev_case = this_case;
207 } else {
208 if (! last_was_space) {
209 builder.Append (' ');
210 last_was_space = true;
212 prev_case = 0;
216 return builder.ToString ();
219 public static string UrlFuzzyDivide (string url)
221 int protocol_index = url.IndexOf ("://");
222 return FuzzyDivide (url.Substring (protocol_index + 3));
225 // Match strings against patterns that are allowed to contain
226 // glob-style * wildcards.
227 // This recursive implementation is not particularly efficient,
228 // and probably will fail for weird corner cases.
229 static public bool GlobMatch (string pattern, string str)
231 if (pattern == null || str == null)
232 return false;
234 if (pattern == "*")
235 return true;
236 else if (pattern.StartsWith ("**"))
237 return GlobMatch (pattern.Substring (1), str);
238 else if (str == "" && pattern != "")
239 return false;
241 int i = pattern.IndexOf ('*');
242 if (i == -1)
243 return pattern == str;
244 else if (i > 0 && i < str.Length)
245 return pattern.Substring (0, i) == str.Substring (0, i)
246 && GlobMatch (pattern.Substring (i), str.Substring (i));
247 else if (i == 0)
248 return GlobMatch (pattern.Substring (1), str.Substring (1))
249 || GlobMatch (pattern.Substring (1), str)
250 || GlobMatch (pattern, str.Substring (1));
252 return false;
255 // FIXME: how do we do this operation in a culture-neutral way?
256 static public string[] SplitQuoted (string str)
258 char[] specialChars = new char [2] { ' ', '"' };
260 ArrayList array = new ArrayList ();
262 int i;
263 while ((i = str.IndexOfAny (specialChars)) != -1) {
264 if (str [i] == ' ') {
265 if (i > 0)
266 array.Add (str.Substring (0, i));
267 str = str.Substring (i+1);
268 } else if (str [i] == '"') {
269 int j = str.IndexOf ('"', i+1);
270 if (i > 0)
271 array.Add (str.Substring (0, i));
272 if (j == -1) {
273 if (i+1 < str.Length)
274 array.Add (str.Substring (i+1));
275 str = "";
276 } else {
277 if (j-i-1 > 0)
278 array.Add (str.Substring (i+1, j-i-1));
279 str = str.Substring (j+1);
283 if (str != "")
284 array.Add (str);
286 string [] retval = new string [array.Count];
287 for (i = 0; i < array.Count; ++i)
288 retval [i] = (string) array [i];
289 return retval;
292 static public bool ContainsWhiteSpace (string str)
294 foreach (char c in str)
295 if (char.IsWhiteSpace (c))
296 return true;
297 return false;
300 static char[] CharsToQuote = { ';', '?', ':', '@', '&', '=', '$', ',', '#', '%', '"', ' ' };
302 static public string HexEscape (string str)
304 StringBuilder builder = new StringBuilder ();
306 foreach (char c in str) {
308 if (Array.IndexOf (CharsToQuote, c) != -1)
309 builder.Append (Uri.HexEscape (c));
310 else if (c < 128)
311 builder.Append (c);
312 else {
313 byte[] utf8_bytes;
315 utf8_bytes = Encoding.UTF8.GetBytes (new char [] { c });
317 foreach (byte b in utf8_bytes)
318 builder.AppendFormat ("%{0:X}", b);
322 return builder.ToString ();
325 // Translate all %xx codes into real characters
326 static public string HexUnescape (string str)
328 ArrayList bytes = new ArrayList ();
329 byte[] sub_bytes;
330 int i, pos = 0;
332 while ((i = str.IndexOf ('%', pos)) != -1) {
333 sub_bytes = Encoding.UTF8.GetBytes (str.Substring (pos, i - pos));
334 bytes.AddRange (sub_bytes);
336 pos = i;
337 char unescaped = Uri.HexUnescape (str, ref pos);
338 bytes.Add ((byte) unescaped);
341 sub_bytes = Encoding.UTF8.GetBytes (str.Substring (pos, str.Length - pos));
342 bytes.AddRange (sub_bytes);
344 return Encoding.UTF8.GetString ((byte[]) bytes.ToArray (typeof (byte)));
347 // These strings should never be exposed to the user.
348 static int uid = 0;
349 static object uidLock = new object ();
350 static public string GetUniqueId ()
352 lock (uidLock) {
353 if (uid == 0) {
354 Random r = new Random ();
355 uid = r.Next ();
357 ++uid;
359 return string.Format ("{0}-{1}-{2}-{3}",
360 Environment.GetEnvironmentVariable ("USER"),
361 Environment.GetEnvironmentVariable ("HOST"),
362 DateTime.Now.Ticks,
363 uid);
367 static string [] replacements = new string [] {
368 "&amp;", "&lt;", "&gt;", "&quot;", "&apos;",
369 "&#xD;", "&#xA;"};
371 static private StringBuilder cachedStringBuilder;
372 static private char QuoteChar = '\"';
374 private static bool IsInvalid (int ch)
376 switch (ch) {
377 case 9:
378 case 10:
379 case 13:
380 return false;
382 if (ch < 32)
383 return true;
384 if (ch < 0xD800)
385 return false;
386 if (ch < 0xE000)
387 return true;
388 if (ch < 0xFFFE)
389 return false;
390 if (ch < 0x10000)
391 return true;
392 if (ch < 0x110000)
393 return false;
394 else
395 return true;
398 static public string EscapeStringForHtml (string source, bool skipQuotations)
400 int start = 0;
401 int pos = 0;
402 int count = source.Length;
403 char invalid = ' ';
404 for (int i = 0; i < count; i++) {
405 switch (source [i]) {
406 case '&': pos = 0; break;
407 case '<': pos = 1; break;
408 case '>': pos = 2; break;
409 case '\"':
410 if (skipQuotations) continue;
411 if (QuoteChar == '\'') continue;
412 pos = 3; break;
413 case '\'':
414 if (skipQuotations) continue;
415 if (QuoteChar == '\"') continue;
416 pos = 4; break;
417 case '\r':
418 if (skipQuotations) continue;
419 pos = 5; break;
420 case '\n':
421 if (skipQuotations) continue;
422 pos = 6; break;
423 default:
424 if (IsInvalid (source [i])) {
425 invalid = source [i];
426 pos = -1;
427 break;
429 else
430 continue;
432 if (cachedStringBuilder == null)
433 cachedStringBuilder = new StringBuilder
435 cachedStringBuilder.Append (source.Substring (start, i - start));
436 if (pos < 0) {
437 cachedStringBuilder.Append ("&#x");
438 if (invalid < (char) 255)
439 cachedStringBuilder.Append (((int) invalid).ToString ("X02", CultureInfo.InvariantCulture));
440 else
441 cachedStringBuilder.Append (((int) invalid).ToString ("X04", CultureInfo.InvariantCulture));
442 cachedStringBuilder.Append (";");
444 else
445 cachedStringBuilder.Append (replacements [pos]);
446 start = i + 1;
448 if (start == 0)
449 return source;
450 else if (start < count)
451 cachedStringBuilder.Append (source.Substring (start, count - start));
452 string s = cachedStringBuilder.ToString ();
453 cachedStringBuilder.Length = 0;
454 return s;
457 static public string CleanupInvalidXmlCharacters (string str)
459 if (str == null)
460 return null;
462 int len = str.Length;
464 // Find the first invalid character in the string
465 int i = 0;
466 while (i < len && ! IsInvalid (str [i]))
467 ++i;
469 // If the string doesn't contain invalid characters,
470 // just return it.
471 if (i >= len)
472 return str;
474 // Otherwise copy the first chunk, then go through
475 // character by character looking for more invalid stuff.
477 char [] char_array = new char[len];
479 for (int j = 0; j < i; ++j)
480 char_array [j] = str [j];
481 char_array [i] = ' ';
483 for (int j = i+1; j < len; ++j) {
484 char c = str [j];
485 if (IsInvalid (c))
486 char_array [j] = ' ';
487 else
488 char_array [j] = c;
491 return new string (char_array);
494 // Words of less than min_word_length characters are not counted
495 static public int CountWords (string str, int max_words, int min_word_length)
497 if (str == null)
498 return 0;
500 bool last_was_white = true;
501 int words = 0;
502 int word_start_pos = -1;
504 for (int i = 0; i < str.Length; ++i) {
505 if (Char.IsWhiteSpace (str [i])) {
506 // if just seen word is too short, ignore it
507 if (! last_was_white && (i - word_start_pos < min_word_length))
508 --words;
509 last_was_white = true;
510 } else {
511 if (last_was_white) {
512 ++words;
513 word_start_pos = i;
514 if (max_words > 0 && words >= max_words)
515 break;
517 last_was_white = false;
521 return words;
524 static public int CountWords (string str, int max_words)
526 return CountWords (str, max_words, -1);
529 static public int CountWords (string str)
531 return CountWords (str, -1);
534 // Strip trailing slashes and make sure we only have 1 leading slash
535 static public string SanitizePath (string path)
537 if (path.StartsWith ("//")) {
538 int pos;
539 for (pos = 2; pos < path.Length; pos++)
540 if (path [pos] != '/')
541 break;
543 path = path.Substring (pos - 1);
545 if (!(path.Length == 1 && path [0] == '/'))
546 path = path.TrimEnd ('/');
548 return path;
551 // This method will translate an email address like
552 // "john.doe+spamtrap@foo.com" to "john doe spamtrap foo"
554 // FIXME: Maybe we should only do the username part? Ie,
555 // "john doe spamtrap"? That way searching for "foo" won't
556 // turn up *everything*
557 static public string SanitizeEmail (string email)
559 char[] replace_array = { '@', '.', '-', '_', '+' };
560 string[] tlds = { "com", "net", "org", "edu", "gov", "mil" }; // Just the Big Six
562 if (email == null)
563 return null;
565 email = email.ToLower ();
567 string[] tmp = email.Split (replace_array);
568 email = String.Join (" ", tmp);
570 foreach (string tld in tlds) {
571 if (email.EndsWith (" " + tld)) {
572 email = email.Substring (0, email.Length - 4);
573 break;
577 return email;
581 * expands environment variables in a string e.g.
582 * folders=$HOME/.kde/share/...
584 public static string ExpandEnvVariables (string path)
586 int dollar_pos = path.IndexOf ('$');
587 if (dollar_pos == -1)
588 return path;
590 System.Text.StringBuilder sb =
591 new System.Text.StringBuilder ( (dollar_pos == 0 ? "" : path.Substring (0, dollar_pos)));
593 while (dollar_pos != -1 && dollar_pos + 1 < path.Length) {
594 // FIXME: kconfigbase.cpp contains an additional case, $(expression)/.kde/...
595 // Ignoring such complicated expressions for now. Volunteers ;) ?
596 int end_pos = dollar_pos;
597 if (path [dollar_pos + 1] != '$') {
598 string var_name;
599 end_pos ++;
600 if (path [end_pos] == '{') {
601 while ((end_pos < path.Length) &&
602 (path [end_pos] != '}'))
603 end_pos ++;
604 end_pos ++;
605 var_name = path.Substring (dollar_pos + 2, end_pos - dollar_pos - 3);
606 } else {
607 while ((end_pos < path.Length) &&
608 (Char.IsNumber (path [end_pos]) ||
609 Char.IsLetter (path [end_pos]) ||
610 path [end_pos] == '_'))
611 end_pos ++;
612 var_name = path.Substring (dollar_pos + 1, end_pos - dollar_pos - 1);
614 string value_env = null;
615 if (var_name != String.Empty)
616 value_env = Environment.GetEnvironmentVariable (var_name);
617 if (value_env != null) {
618 sb.Append (value_env);
620 // else, no environment variable with that name exists. ignore
621 }else // else, ignore the first '$', second one will be expanded
622 end_pos ++;
623 if (end_pos >= path.Length)
624 break;
625 dollar_pos = path.IndexOf ('$', end_pos);
626 if (dollar_pos == -1) {
627 sb.Append (path.Substring (end_pos));
628 } else {
629 sb.Append (path.Substring (end_pos, dollar_pos - end_pos));
633 return sb.ToString ();
636 public static string StripTags (string line, StringBuilder builder)
638 int first = line.IndexOf ('<');
639 if (first == -1)
640 return line;
642 builder.Length = 0;
644 int i = 0;
645 while (i < line.Length) {
647 int j;
648 if (first == -1) {
649 j = line.IndexOf ('<', i);
650 } else {
651 j = first;
652 first = -1;
655 int k = -1;
656 if (j != -1) {
657 k = line.IndexOf ('>', j);
659 // If a "<" is unmatched, preserve it, and the
660 // rest of the line
661 if (k == -1)
662 j = -1;
665 if (j == -1) {
666 builder.Append (line, i, line.Length - i);
667 break;
670 builder.Append (line, i, j-i);
672 i = k+1;
675 return builder.ToString ();
678 public static string StripTags (string line)
680 StringBuilder sb = new StringBuilder ();
681 return StripTags (line, sb);
684 public static string ConvertSpecialEntities (string line)
686 line.Replace ("&lt;", "<");
687 line.Replace ("&gt;", ">");
688 line.Replace ("&quot;", "\"");
689 line.Replace ("&amp;", "&");
690 line.Replace ("&nbsp", " ");
692 return line;
696 public class HtmlRemovingReader : TextReader {
698 private TextReader reader;
699 private StringBuilder sb;
701 public HtmlRemovingReader (TextReader reader)
703 this.reader = reader;
704 this.sb = new StringBuilder ();
707 public override string ReadLine ()
709 string line = reader.ReadLine ();
711 if (line == null)
712 return null;
714 sb.Length = 0;
715 line = StringFu.StripTags (line, sb);
716 line = StringFu.ConvertSpecialEntities (line);
718 return line;
721 public override void Close ()
723 reader.Close ();