Fixing an issue with output parameters that are of type IntPtr
[castle.git] / MonoRail / Castle.MonoRail.Framework / Helpers / TextHelper.cs
blob2d99a3d57fac0eefea2d3203f5d865466fc122ca
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle.MonoRail.Framework.Helpers
17 using System;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.Globalization;
21 using System.Text;
22 using System.Text.RegularExpressions;
24 /// <summary>
25 /// Provides methods for working with strings and grammar. At the moment,
26 /// it contains the ToSentence overloads.
27 /// </summary>
28 public class TextHelper : AbstractHelper
30 #region Constructors
32 /// <summary>
33 /// Initializes a new instance of the <see cref="TextHelper"/> class.
34 /// </summary>
35 public TextHelper()
39 /// <summary>
40 /// Initializes a new instance of the <see cref="TextHelper"/> class.
41 /// setting the Controller, Context and ControllerContext.
42 /// </summary>
43 /// <param name="engineContext">The engine context.</param>
44 public TextHelper(IEngineContext engineContext) : base(engineContext)
48 #endregion
50 /// <summary>
51 /// Default word connector
52 /// </summary>
53 public const string DefaultConnector = "and";
55 /// <summary>
56 /// Converts the special characters to HTML.
57 /// </summary>
58 /// <param name="content">The content.</param>
59 /// <returns></returns>
60 public string ConvertSpecialCharsToHtml(string content)
62 if (string.IsNullOrEmpty(content))
64 return string.Empty;
67 return HtmlEncode(content).
68 Replace("\r\n", "<br />").
69 Replace("\r", "<br />").
70 Replace("\n", "<br />").
71 Replace("\t", "&nbsp;");
74 /// <summary>
75 /// Formats the specified string as a phone number, varying according to the culture.
76 /// </summary>
77 /// <param name="phone">The phone number to format.</param>
78 /// <returns></returns>
79 public string FormatPhone(string phone)
81 if (string.IsNullOrEmpty(phone))
83 return string.Empty;
85 if (phone.Length <= 3)
87 return phone;
89 if (phone.IndexOfAny(new char[] {'(', '-', '.'}) != -1)
91 return phone;
94 PhoneFormatter formatter = new PhoneFormatter();
95 return formatter.Format(phone);
98 /// <summary>
99 /// Converts a camelized text to words. For instance:
100 /// <c>FileWriter</c> is converted to <c>File Writer</c>
101 /// </summary>
102 /// <param name="pascalText">Content in pascal case</param>
103 /// <returns></returns>
104 public static string PascalCaseToWord(string pascalText)
106 if (pascalText == null)
108 throw new ArgumentNullException("pascalText");
110 if (pascalText == string.Empty)
112 return string.Empty;
115 StringBuilder sbText = new StringBuilder(pascalText.Length + 4);
117 char[] chars = pascalText.ToCharArray();
119 sbText.Append(chars[0]);
121 for(int i = 1; i < chars.Length; i++)
123 char c = chars[i];
125 if (Char.IsUpper(c))
127 sbText.Append(' ');
130 sbText.Append(c);
133 return sbText.ToString();
136 /// <summary>
137 /// Builds a phrase listing a series of strings with with proper sentence semantics,
138 /// i.e. separating elements with &quot;, &quot; and prefacing the last element with
139 /// the specified <paramref name="connector"/>.
140 /// </summary>
141 /// <param name="elements">Collection with items to use in the sentence.</param>
142 /// <param name="connector">String to preface the last element.</param>
143 /// <returns>String suitable for use in a sentence.</returns>
144 /// <remarks>Calling <c>ToSentence( elements, "y" )</c> results in:
145 /// <code>
146 /// element1, element2 y element3
147 /// </code>
148 /// <para>If <paramref name="elements"/> is not an array of strings, each element will be
149 /// converted to string through <see cref="object.ToString"/>.</para>
150 /// </remarks>
151 /// <example>This example shows how to use <b>ToSentence</b>:
152 /// <code>
153 /// $TextHelper.ToSentence( elements, "y" )
154 /// </code>
155 /// </example>
156 public static string ToSentence(ICollection elements, string connector)
158 return ToSentence(elements, connector, true);
161 /// <summary>
162 /// Builds a phrase listing a series of strings with with proper sentence semantics,
163 /// i.e. separating elements with &quot;, &quot; and prefacing the last element with
164 /// &quot; and &quot;.
165 /// </summary>
166 /// <param name="elements">Collection with items to use in the sentence.</param>
167 /// <param name="skipLastComma">True to skip the comma before the connector, false to include it.</param>
168 /// <returns>String suitable for use in a sentence.</returns>
169 /// <remarks>Calling <c>ToSentence( elements, false )</c> results in:
170 /// <code>
171 /// element1, element2, and element3
172 /// </code>
173 /// <para>If <paramref name="elements"/> is not an array of strings, each element will be
174 /// converted to string through <see cref="object.ToString"/>.</para>
175 /// </remarks>
176 /// <example>This example shows how to use <b>ToSentence</b>:
177 /// <code>
178 /// $TextHelper.ToSentence( elements, false )
179 /// </code>
180 /// </example>
181 public static string ToSentence(ICollection elements, bool skipLastComma)
183 return ToSentence(elements, DefaultConnector, skipLastComma);
186 /// <summary>
187 /// Builds a phrase listing a series of strings with with proper sentence semantics,
188 /// i.e. separating elements with &quot;, &quot; and prefacing the last element with
189 /// &quot; and &quot;.
190 /// </summary>
191 /// <param name="elements">Collection with items to use in the sentence.</param>
192 /// <returns>String suitable for use in a sentence.</returns>
193 /// <remarks>Calling <c>ToSentence( elements )</c> results in:
194 /// <code>
195 /// element1, element2 and element3
196 /// </code>
197 /// <para>If <paramref name="elements"/> is not an array of strings, each element will be
198 /// converted to string through <see cref="object.ToString"/>.</para>
199 /// </remarks>
200 /// <example>This example shows how to use <b>ToSentence</b>:
201 /// <code>
202 /// $TextHelper.ToSentence( elements )
203 /// </code>
204 /// </example>
205 public static string ToSentence(ICollection elements)
207 if (elements == null)
209 throw new ArgumentNullException("elements");
212 return ToSentence(elements, DefaultConnector, true);
215 /// <summary>
216 /// Builds a phrase listing a series of strings with with proper sentence semantics,
217 /// i.e. separating elements with &quot;, &quot; and prefacing the last element with
218 /// &quot; and &quot;.
219 /// </summary>
220 /// <param name="elements">Collection with items to use in the sentence.</param>
221 /// <returns>String suitable for use in a sentence.</returns>
222 /// <remarks>Calling <c>ToSentence( elements )</c> results in:
223 /// <code>
224 /// element1, element2 and element3
225 /// </code>
226 /// <para>If <paramref name="elements"/> is not an array of strings, each element will be
227 /// converted to string through <see cref="object.ToString"/>.</para>
228 /// </remarks>
229 /// <example>This example shows how to use <b>ToSentence</b>:
230 /// <code>
231 /// $TextHelper.ToSentence( elements )
232 /// </code>
233 /// </example>
234 public static string ToSentence(IList elements)
236 if (elements == null)
238 throw new ArgumentNullException("elements");
241 return ToSentence(elements, DefaultConnector, true);
244 /// <summary>
245 /// Builds a phrase listing a series of strings with with proper sentence semantics,
246 /// i.e. separating elements with &quot;, &quot; and prefacing the last element with
247 /// the specified <paramref name="connector"/>.
248 /// </summary>
249 /// <param name="elements">Collection with items to use in the sentence.</param>
250 /// <param name="connector">String to preface the last element.</param>
251 /// <param name="skipLastComma">True to skip the comma before the <paramref name="connector"/>, false to include it.</param>
252 /// <returns>String suitable for use in a sentence.</returns>
253 /// <remarks>Calling <c>ToSentence( elements, "y", false )</c> results in:
254 /// <code>
255 /// element1, element2, y element3
256 /// </code>
257 /// <para>If <paramref name="elements"/> is not an array of strings, each element will be
258 /// converted to string through <see cref="Object.ToString"/>.</para>
259 /// </remarks>
260 /// <example>This example shows how to use <b>ToSentence</b>:
261 /// <code>
262 /// $TextHelper.ToSentence( elements, "y", false )
263 /// </code>
264 /// </example>
265 public static string ToSentence(ICollection elements, string connector, bool skipLastComma)
267 string[] array = elements as string[];
268 if (array == null)
270 array = new string[elements.Count];
271 IEnumerator enumerator = elements.GetEnumerator();
272 for(int i = 0; i < elements.Count; i++)
274 enumerator.MoveNext();
275 array[i] = enumerator.Current.ToString();
278 return ToSentence(array, connector, skipLastComma);
281 /// <summary>
282 /// Builds a phrase listing a series of strings with with proper sentence semantics,
283 /// i.e. separating elements with &quot;, &quot; and prefacing the last element with
284 /// the specified <paramref name="connector"/>.
285 /// </summary>
286 /// <param name="elements">Array of strings with items to use in the sentence.</param>
287 /// <param name="connector">String to preface the last element.</param>
288 /// <param name="skipLastComma">True to skip the comma before the <paramref name="connector"/>, false to include it.</param>
289 /// <returns>String suitable for use in a sentence.</returns>
290 /// <remarks>Calling <c>ToSentence( elements, "y", false )</c> results in:
291 /// <code>
292 /// element1, element2, y element3
293 /// </code>
294 /// </remarks>
295 /// <example>This example shows how to use <b>ToSentence</b>:
296 /// <code>
297 /// $TextHelper.ToSentence( elements, "y", false )
298 /// </code>
299 /// </example>
300 public static string ToSentence(string[] elements, string connector, bool skipLastComma)
302 switch(elements.Length)
304 case 0:
306 return String.Empty;
308 case 1:
310 return elements[0];
312 case 2:
314 return elements[0] + " " + connector + " " + elements[1];
316 default:
318 String[] allButLast = new String[elements.Length - 1];
320 Array.Copy(elements, allButLast, elements.Length - 1);
322 return
323 String.Join(", ", allButLast) + (skipLastComma ? "" : ",") + " " +
324 connector + " " +
325 elements[elements.Length - 1];
330 /// <summary>
331 /// Shortens a text to the specified length and wraps it into a span-
332 /// element that has the title-property with the full text associated.
333 /// This is convenient for displaying properties in tables that might
334 /// have very much content (desription fields etc.) without destroying
335 /// the table's layout.
336 /// Due to the title-property of the surrounding span-element, the full
337 /// text is displayed in the browser while hovering over the shortened
338 /// text.
339 /// </summary>
340 /// <param name="text">The text to display</param>
341 /// <param name="maxLength">The maximum number of character to display</param>
342 /// <returns>The generated HTML</returns>
343 public string Fold(string text, int maxLength)
345 // Empty text
346 if (text == null)
348 return "";
351 // maxLenght <= 0 switches off folding
352 // Determine whether text must be cut
353 if (maxLength <= 0 || text.Length < maxLength)
355 return text;
358 StringBuilder caption = new StringBuilder();
359 foreach(string word in text.Split())
361 if (caption.Length + word.Length + 1 > maxLength - 1)
363 break;
365 if (caption.Length > 0)
367 caption.Append(" "); // Adding space
369 caption.Append(word);
372 caption.Append("&hellip;");
373 return string.Format("<span title=\"{1}\">{0}</span>", caption, text.Replace("\"", "&quot;"));
376 /// <summary>
377 /// Provides methods for formatting telephone numbers based on region.
378 /// </summary>
379 /// <remarks>At present, formats for USA and Brazil are defined. These need to be expanded.
380 /// </remarks>
381 /// <example>
382 /// In C# code:
383 /// <code>
384 /// PhoneFormatter formatter = new PhoneFormatter();
385 /// string officePhone = formatter.Format(contact.OfficePhone);
386 /// string homePhone = formatter.Format(contact.HomePhone);
387 /// </code>
388 /// Using in a view:
389 /// <code>
390 ///PropertyBag["phoneformatter"] = new PhoneFormatter();
391 ///
392 ///#if ($OfficePhone)
393 /// &lt;h3&gt;Office Phone: $phoneformatter.Format($OfficePhone)&lt;/h3&gt;
394 ///#end
395 /// </code>
396 /// </example>
397 public class PhoneFormatter
399 private readonly IEnumerable<FormatPair> formats;
401 /// <summary>
402 /// Initializes a new instance of the <see cref="PhoneFormatter"/> class for the specified region.
403 /// </summary>
404 /// <param name="name">The name or ISO 3166 code for region.</param>
405 /// <exception cref="ArgumentException"><paramref name="name"/> is not a valid country/region name or specific culture name.</exception>
406 /// <exception cref="ArgumentNullException"><paramref name="name"/> is null reference (<b>Nothing</b> in Visual Basic).</exception>
407 public PhoneFormatter(string name) : this(new RegionInfo(name))
411 /// <summary>
412 /// Initializes a new instance of the PhoneFormatter class for the region used by the current thread.
413 /// </summary>
414 public PhoneFormatter() : this((RegionInfo) null)
418 /// <summary>
419 /// Initializes a new instance of the PhoneFormatter class for the specified region
420 /// </summary>
421 /// <param name="rinfo">The Region </param>
422 public PhoneFormatter(RegionInfo rinfo)
424 if (rinfo == null)
426 rinfo = RegionInfo.CurrentRegion;
429 formats = PhoneFormatterFactory.GetFormatPairs(rinfo);
432 /// <summary>
433 /// Formats the specified string as a phone number, varying according to the culture.
434 /// </summary>
435 /// <param name="orig">The string to format</param>
436 /// <returns>string, formatted phone number.</returns>
437 public string Format(string orig)
439 // Remove all characters besides numbers and letters.
440 Regex strip = new Regex(@"[^\w]", RegexOptions.IgnoreCase);
441 string stripped = strip.Replace(orig, "");
443 foreach(FormatPair testcase in formats)
445 Regex convert = new Regex(testcase.Pattern);
446 Match match = convert.Match(stripped);
447 if (match.Success)
449 return match.Result(testcase.Formatted);
452 return orig;
456 internal class FormatPair
458 public string Pattern;
459 public string Formatted;
461 /// <summary>
462 /// Initializes a new instance of the FormatPair class.
463 /// </summary>
464 /// <param name="pattern"></param>
465 /// <param name="formatted"></param>
466 public FormatPair(string pattern, string formatted)
468 Pattern = pattern;
469 Formatted = formatted;
473 internal static class PhoneFormatterFactory
475 public static IEnumerable<FormatPair> GetFormatPairs(RegionInfo rinfo)
477 List<FormatPair> pairs = new List<FormatPair>();
479 // This section
480 // a) Needs to be expanded to other regions.
481 // b) should be cached.
482 // c) Should be handled in a more robust fashion. (resource files? Xml file?)
484 switch(rinfo.TwoLetterISORegionName)
486 case "US": // United States
487 pairs.Add(new FormatPair(@"^(\w\w\d)(\d\d\d\d)$", @"$1-$2"));
488 pairs.Add(new FormatPair(@"^(\d\d\d)(\w\w\d)(\d\d\d\d)$", @"($1) $2-$3"));
489 pairs.Add(new FormatPair(@"^1(\d\d\d)(\w\w\d)(\d\d\d\d)$", @"+1 ($1) $2-$3"));
490 pairs.Add(new FormatPair(@"^(\d\d\d)(\w\w\d)(\d\d\d\d)x?(\d+)$", @"($1) $2-$3 ext. $4"));
491 pairs.Add(new FormatPair(@"^1(\d\d\d)(\w\w\d)(\d\d\d\d)x?(\d+)$", @"+1 ($1) $2-$3 ext. $4"));
492 break;
494 case "BR": // Brazil
495 pairs.Add(new FormatPair(@"^(\d\d\d\d)(\d\d\d\d)$", @"$1-$2"));
496 pairs.Add(new FormatPair(@"^(\d\d)(\d\d\d\d)(\d\d\d\d)$", @"($1) $2-$3"));
497 pairs.Add(new FormatPair(@"^(0\d\d)(\d\d\d\d)(\d\d\d\d)$", @"($1) $2-$3"));
498 pairs.Add(new FormatPair(@"^(xx\d\d)(\d\d\d\d)(\d\d\d\d)$", @"($1) $2-$3"));
499 pairs.Add(new FormatPair(@"^(0xx\d\d)(\d\d\d\d)(\d\d\d\d)$", @"($1) $2-$3"));
500 pairs.Add(new FormatPair(@"^55(\d\d)(\d\d\d\d)(\d\d\d\d)$", @"+55 ($1) $2-$3"));
501 pairs.Add(new FormatPair(@"^55(0\d\d)(\d\d\d\d)(\d\d\d\d)$", @"+55 ($1) $2-$3"));
502 pairs.Add(new FormatPair(@"^55(xx\d\d)(\d\d\d\d)(\d\d\d\d)$", @"+55 ($1) $2-$3"));
503 pairs.Add(new FormatPair(@"^55(0xx\d\d)(\d\d\d\d)(\d\d\d\d)$", @"+55 ($1) $2-$3"));
505 pairs.Add(new FormatPair(@"^(\d\d\d\d)(\d\d\d\d)r?(\d+)$", @"$1-$2 Reul. $3"));
506 pairs.Add(new FormatPair(@"^(\d\d)(\d\d\d\d)(\d\d\d\d)r?(\d+)$", @"($1) $2-$3 Reul. $4"));
507 pairs.Add(new FormatPair(@"^(0\d\d)(\d\d\d\d)(\d\d\d\d)r?(\d+)$", @"($1) $2-$3 Reul. $4"));
508 pairs.Add(new FormatPair(@"^(xx\d\d)(\d\d\d\d)(\d\d\d\d)r?(\d+)$", @"($1) $2-$3 Reul. $4"));
509 pairs.Add(new FormatPair(@"^(0xx\d\d)(\d\d\d\d)(\d\d\d\d)r?(\d+)$", @"($1) $2-$3 Reul. $4"));
510 pairs.Add(new FormatPair(@"^55(\d\d)(\d\d\d\d)(\d\d\d\d)r?(\d+)$", @"+55 ($1) $2-$3 Reul. $4"));
511 pairs.Add(new FormatPair(@"^55(0\d\d)(\d\d\d\d)(\d\d\d\d)r?(\d+)$", @"+55 ($1) $2-$3 Reul. $4"));
512 pairs.Add(new FormatPair(@"^55(xx\d\d)(\d\d\d\d)(\d\d\d\d)r?(\d+)$", @"+55 ($1) $2-$3 Reul. $4"));
513 pairs.Add(new FormatPair(@"^55(0xx\d\d)(\d\d\d\d)(\d\d\d\d)r?(\d+)$", @"+55 ($1) $2-$3 Reul. $4"));
514 break;
516 default:
517 throw new ArgumentOutOfRangeException("rinfo", "Telephone formats for given RegionInfo have not yet been defined.");
520 return pairs;