PageRenderTime 395ms CodeModel.GetById 30ms RepoModel.GetById 2ms app.codeStats 2ms

/3.0/Source/ClassLibrary/Strings.cs

#
C# | 6704 lines | 4222 code | 895 blank | 1587 comment | 1044 complexity | 972973919d58ab7a7e102243ed5350a1 MD5 | raw file
Possible License(s): CPL-1.0, GPL-2.0, CC-BY-SA-3.0, MPL-2.0-no-copyleft-exception, Apache-2.0
  1. /*
  2. Copyright (c) 2004-2006 Tomas Matousek and Ladislav Prosek.
  3. The use and distribution terms for this software are contained in the file named License.txt,
  4. which can be found in the root of the Phalanger distribution. By using this software
  5. in any fashion, you are agreeing to be bound by the terms of this license.
  6. You must not remove this notice from this software.
  7. */
  8. /*
  9. GENERICS:
  10. Generic char map and hashtable will allow to handle all Unicode characters and get rid of the errors:
  11. <exception cref="PhpException"><paramref name="whiteSpaceCharacters"/> contains Unicode characters greater than '\u0800'.</exception>
  12. TODO:
  13. - PHP6 - new functions hash($alg,...) hash_file($alg, ...)
  14. - Added overflow checks to wordwrap() function. (5.1.3)
  15. - Fixed offset/length parameter validation in substr_compare() function. (5.1.3)
  16. - (strncmp & strncasecmp do not return false on negative string length). (5.1.3)
  17. */
  18. using System;
  19. using PHP.Core;
  20. using System.IO;
  21. using System.Text;
  22. using System.Collections;
  23. using System.Collections.Generic;
  24. using System.Security.Cryptography;
  25. using System.Text.RegularExpressions;
  26. using System.ComponentModel;
  27. #if SILVERLIGHT
  28. using PHP.CoreCLR;
  29. using System.Windows.Browser;
  30. #else
  31. using System.Web;
  32. #endif
  33. namespace PHP.Library
  34. {
  35. #region Enumerations
  36. /// <summary>Quote conversion options.</summary>
  37. [Flags]
  38. public enum QuoteStyle
  39. {
  40. /// <summary>
  41. /// Default quote style for <c>htmlentities</c>.
  42. /// </summary>
  43. HtmlEntitiesDefault = QuoteStyle.Compatible | QuoteStyle.Html401,
  44. /// <summary>Single quotes.</summary>
  45. SingleQuotes = 1,
  46. /// <summary>Double quotes.</summary>
  47. DoubleQuotes = 2,
  48. /// <summary>
  49. /// No quotes.
  50. /// Will leave both double and single quotes unconverted.
  51. /// </summary>
  52. [ImplementsConstant("ENT_NOQUOTES")]
  53. NoQuotes = 0,
  54. /// <summary>
  55. /// Will convert double-quotes and leave single-quotes alone.
  56. /// </summary>
  57. [ImplementsConstant("ENT_COMPAT")]
  58. Compatible = DoubleQuotes,
  59. /// <summary>
  60. /// Both single and double quotes.
  61. /// Will convert both double and single quotes.
  62. /// </summary>
  63. [ImplementsConstant("ENT_QUOTES")]
  64. BothQuotes = DoubleQuotes | SingleQuotes,
  65. /// <summary>
  66. /// Silently discard invalid code unit sequences instead of
  67. /// returning an empty string. Using this flag is discouraged
  68. /// as it may have security implications.
  69. /// </summary>
  70. [ImplementsConstant("ENT_IGNORE")]
  71. Ignore = 4,
  72. /// <summary>
  73. /// Replace invalid code unit sequences with a Unicode
  74. /// Replacement Character U+FFFD (UTF-8) or &amp;#FFFD;
  75. /// (otherwise) instead of returning an empty string.
  76. /// </summary>
  77. [ImplementsConstant("ENT_SUBSTITUTE")] // 8
  78. Substitute = 8,
  79. /// <summary>
  80. /// Handle code as HTML 4.01.
  81. /// </summary>
  82. [ImplementsConstant("ENT_HTML401")] // 0
  83. Html401 = NoQuotes,
  84. /// <summary>
  85. /// Handle code as XML 1.
  86. /// </summary>
  87. [ImplementsConstant("ENT_XML1")] // 16
  88. XML1 = 16,
  89. /// <summary>
  90. /// Handle code as XHTML.
  91. /// </summary>
  92. [ImplementsConstant("ENT_XHTML")] // 32
  93. XHTML = 32,
  94. /// <summary>
  95. /// Handle code as HTML 5.
  96. /// </summary>
  97. [ImplementsConstant("ENT_HTML5")] // (16|32)
  98. HTML5 = XML1 | XHTML,
  99. /// <summary>
  100. /// Replace invalid code points for the given document type
  101. /// with a Unicode Replacement Character U+FFFD (UTF-8) or &amp;#FFFD;
  102. /// (otherwise) instead of leaving them as is.
  103. /// This may be useful, for instance, to ensure the well-formedness
  104. /// of XML documents with embedded external content.
  105. /// </summary>
  106. [ImplementsConstant("ENT_DISALLOWED")] // 128
  107. Disallowed = 128,
  108. };
  109. /// <summary>Types of HTML entities tables.</summary>
  110. public enum HtmlEntitiesTable
  111. {
  112. /// <summary>Table containing special characters only.</summary>
  113. [ImplementsConstant("HTML_SPECIALCHARS")]
  114. SpecialChars = 0,
  115. /// <summary>Table containing all entities.</summary>
  116. [ImplementsConstant("HTML_ENTITIES")]
  117. AllEntities = 1
  118. };
  119. /// <summary>
  120. /// Type of padding.
  121. /// </summary>
  122. public enum PaddingType
  123. {
  124. /// <summary>Pad a string from the left.</summary>
  125. [ImplementsConstant("STR_PAD_LEFT")]
  126. Left = 0,
  127. /// <summary>Pad a string from the right.</summary>
  128. [ImplementsConstant("STR_PAD_RIGHT")]
  129. Right = 1,
  130. /// <summary>Pad a string from both sides.</summary>
  131. [ImplementsConstant("STR_PAD_BOTH")]
  132. Both = 2
  133. }
  134. /// <summary>
  135. /// Format of a return value of <see cref="PhpStrings.CountWords"/> method. Constants are not named in PHP.
  136. /// </summary>
  137. public enum WordCountResult
  138. {
  139. /// <summary>
  140. /// Return number of words in string.
  141. /// </summary>
  142. WordCount = 0,
  143. /// <summary>
  144. /// Return array of words.
  145. /// </summary>
  146. WordsArray = 1,
  147. /// <summary>
  148. /// Return positions to words mapping.
  149. /// </summary>
  150. PositionsToWordsMapping = 2
  151. }
  152. #endregion
  153. /// <summary>
  154. /// Manipulates strings.
  155. /// </summary>
  156. /// <threadsafety static="true"/>
  157. public static class PhpStrings
  158. {
  159. #region Character map
  160. #if !SILVERLIGHT
  161. [ThreadStatic]
  162. #endif
  163. private static CharMap _charmap;
  164. /// <summary>
  165. /// Get clear <see cref="CharMap"/> to be used by current thread. <see cref="_charmap"/>.
  166. /// </summary>
  167. internal static CharMap InitializeCharMap()
  168. {
  169. CharMap result = _charmap;
  170. if (result == null)
  171. _charmap = result = new CharMap(0x0800);
  172. else
  173. result.ClearAll();
  174. return result;
  175. }
  176. #endregion
  177. #region Binary Data Functions
  178. #region ord, chr, bin2hex, ord_unicode, chr_unicode, bin2hex_unicode, to_binary
  179. /// <summary>
  180. /// Returns ASCII code of the first character of a string of bytes.
  181. /// </summary>
  182. /// <param name="bytes">The string of bytes which the first byte will be returned.</param>
  183. /// <returns>The ASCII code of <paramref name="bytes"/>[0] or zero if null or empty.</returns>
  184. [ImplementsFunction("ord")]
  185. [PureFunction]
  186. public static int Ord(PhpBytes bytes)
  187. {
  188. return (bytes == null || bytes.Length == 0) ? 0 : (int)bytes[0];
  189. }
  190. /// <summary>
  191. /// Returns Unicode ordinal number of the first character of a string.
  192. /// </summary>
  193. /// <param name="str">The string which the first character's ordinal number is returned.</param>
  194. /// <returns>The ordinal number of <paramref name="str"/>[0].</returns>
  195. [ImplementsFunction("ord_unicode")]
  196. [PureFunction]
  197. public static int OrdUnicode(string str)
  198. {
  199. return (str == null || str == String.Empty) ? 0 : (int)str[0];
  200. }
  201. /// <summary>
  202. /// Converts ordinal number of character to a binary string containing that character.
  203. /// </summary>
  204. /// <param name="charCode">The ASCII code.</param>
  205. /// <returns>The character with <paramref name="charCode"/> ASCIT code.</returns>
  206. /// <remarks>Current code-page is determined by the <see cref="ApplicationConfiguration.GlobalizationSection.PageEncoding"/> property.</remarks>
  207. [ImplementsFunction("chr")]
  208. [PureFunction]
  209. public static PhpBytes Chr(int charCode)
  210. {
  211. return new PhpBytes(unchecked((byte)charCode));
  212. }
  213. /// <summary>
  214. /// Converts ordinal number of Unicode character to a string containing that character.
  215. /// </summary>
  216. /// <param name="charCode">The ordinal number of character.</param>
  217. /// <returns>The character with <paramref name="charCode"/> ordnial number.</returns>
  218. [ImplementsFunction("chr_unicode")]
  219. [PureFunction]
  220. public static string ChrUnicode(int charCode)
  221. {
  222. return unchecked((char)charCode).ToString();
  223. }
  224. /// <summary>
  225. /// Converts a string of bytes into hexadecimal representation.
  226. /// </summary>
  227. /// <param name="bytes">The string of bytes.</param>
  228. /// <returns>Concatenation of hexadecimal values of bytes of <paramref name="bytes"/>.</returns>
  229. /// <example>
  230. /// The string "01A" is converted into string "303140" because ord('0') = 0x30, ord('1') = 0x31, ord('A') = 0x40.
  231. /// </example>
  232. [ImplementsFunction("bin2hex")]
  233. [PureFunction]
  234. public static string BinToHex(PhpBytes bytes)
  235. {
  236. return (bytes == null) ? String.Empty : StringUtils.BinToHex(bytes.ReadonlyData, null);
  237. }
  238. /// <summary>
  239. /// Converts a string into hexadecimal representation.
  240. /// </summary>
  241. /// <param name="str">The string to be converted.</param>
  242. /// <returns>
  243. /// The concatenated four-characters long hexadecimal numbers each representing one character of <paramref name="str"/>.
  244. /// </returns>
  245. [ImplementsFunction("bin2hex_unicode")]
  246. [PureFunction]
  247. public static string BinToHex(string str)
  248. {
  249. if (str == null) return null;
  250. int length = str.Length;
  251. StringBuilder result = new StringBuilder(length * 4, length * 4);
  252. result.Length = length * 4;
  253. const string hex_digs = "0123456789abcdef";
  254. for (int i = 0; i < length; i++)
  255. {
  256. int c = (int)str[i];
  257. result[4 * i + 0] = hex_digs[(c & 0xf000) >> 12];
  258. result[4 * i + 1] = hex_digs[(c & 0x0f00) >> 8];
  259. result[4 * i + 2] = hex_digs[(c & 0x00f0) >> 4];
  260. result[4 * i + 3] = hex_digs[(c & 0x000f)];
  261. }
  262. return result.ToString();
  263. }
  264. /// <summary>
  265. /// Converts a variable to a string of binary data.
  266. /// </summary>
  267. /// <param name="var">A variable.</param>
  268. /// <returns>Binary data.</returns>
  269. [ImplementsFunction("to_binary")]
  270. [PureFunction]
  271. public static PhpBytes ToBinary(PhpBytes var)
  272. {
  273. return var;
  274. }
  275. #endregion
  276. #region convert_cyr_string
  277. #region cyrWin1251 (1251), cyrCp866 (20866), cyrIso88595 (28595), cyrMac (10007) conversion tables
  278. /// <summary>
  279. /// Cyrillic translation table for Windows CP1251 character set.
  280. /// </summary>
  281. private static readonly byte[] cyrWin1251 = new byte[]
  282. {
  283. 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
  284. 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,
  285. 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,
  286. 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,
  287. 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,
  288. 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,
  289. 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,
  290. 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,
  291. 46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,
  292. 46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,
  293. 154,174,190,46,159,189,46,46,179,191,180,157,46,46,156,183,
  294. 46,46,182,166,173,46,46,158,163,152,164,155,46,46,46,167,
  295. 225,226,247,231,228,229,246,250,233,234,235,236,237,238,239,240,
  296. 242,243,244,245,230,232,227,254,251,253,255,249,248,252,224,241,
  297. 193,194,215,199,196,197,214,218,201,202,203,204,205,206,207,208,
  298. 210,211,212,213,198,200,195,222,219,221,223,217,216,220,192,209,
  299. 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
  300. 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,
  301. 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,
  302. 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,
  303. 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,
  304. 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,
  305. 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,
  306. 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,
  307. 32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
  308. 32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
  309. 32,32,32,184,186,32,179,191,32,32,32,32,32,180,162,32,
  310. 32,32,32,168,170,32,178,175,32,32,32,32,32,165,161,169,
  311. 254,224,225,246,228,229,244,227,245,232,233,234,235,236,237,238,
  312. 239,255,240,241,242,243,230,226,252,251,231,248,253,249,247,250,
  313. 222,192,193,214,196,197,212,195,213,200,201,202,203,204,205,206,
  314. 207,223,208,209,210,211,198,194,220,219,199,216,221,217,215,218,
  315. };
  316. /// <summary>
  317. /// Cyrillic translation table for CP866 character set.
  318. /// </summary>
  319. private static readonly byte[] cyrCp866 = new byte[]
  320. {
  321. 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
  322. 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,
  323. 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,
  324. 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,
  325. 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,
  326. 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,
  327. 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,
  328. 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,
  329. 225,226,247,231,228,229,246,250,233,234,235,236,237,238,239,240,
  330. 242,243,244,245,230,232,227,254,251,253,255,249,248,252,224,241,
  331. 193,194,215,199,196,197,214,218,201,202,203,204,205,206,207,208,
  332. 35,35,35,124,124,124,124,43,43,124,124,43,43,43,43,43,
  333. 43,45,45,124,45,43,124,124,43,43,45,45,124,45,43,45,
  334. 45,45,45,43,43,43,43,43,43,43,43,35,35,124,124,35,
  335. 210,211,212,213,198,200,195,222,219,221,223,217,216,220,192,209,
  336. 179,163,180,164,183,167,190,174,32,149,158,32,152,159,148,154,
  337. 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
  338. 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,
  339. 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,
  340. 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,
  341. 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,
  342. 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,
  343. 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,
  344. 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,
  345. 32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
  346. 32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
  347. 205,186,213,241,243,201,32,245,187,212,211,200,190,32,247,198,
  348. 199,204,181,240,242,185,32,244,203,207,208,202,216,32,246,32,
  349. 238,160,161,230,164,165,228,163,229,168,169,170,171,172,173,174,
  350. 175,239,224,225,226,227,166,162,236,235,167,232,237,233,231,234,
  351. 158,128,129,150,132,133,148,131,149,136,137,138,139,140,141,142,
  352. 143,159,144,145,146,147,134,130,156,155,135,152,157,153,151,154,
  353. };
  354. /// <summary>
  355. /// Cyrillic translation table for ISO88595 character set.
  356. /// </summary>
  357. private static readonly byte[] cyrIso88595 = new byte[]
  358. {
  359. 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
  360. 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,
  361. 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,
  362. 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,
  363. 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,
  364. 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,
  365. 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,
  366. 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,
  367. 32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
  368. 32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
  369. 32,179,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
  370. 225,226,247,231,228,229,246,250,233,234,235,236,237,238,239,240,
  371. 242,243,244,245,230,232,227,254,251,253,255,249,248,252,224,241,
  372. 193,194,215,199,196,197,214,218,201,202,203,204,205,206,207,208,
  373. 210,211,212,213,198,200,195,222,219,221,223,217,216,220,192,209,
  374. 32,163,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
  375. 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
  376. 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,
  377. 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,
  378. 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,
  379. 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,
  380. 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,
  381. 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,
  382. 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,
  383. 32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
  384. 32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
  385. 32,32,32,241,32,32,32,32,32,32,32,32,32,32,32,32,
  386. 32,32,32,161,32,32,32,32,32,32,32,32,32,32,32,32,
  387. 238,208,209,230,212,213,228,211,229,216,217,218,219,220,221,222,
  388. 223,239,224,225,226,227,214,210,236,235,215,232,237,233,231,234,
  389. 206,176,177,198,180,181,196,179,197,184,185,186,187,188,189,190,
  390. 191,207,192,193,194,195,182,178,204,203,183,200,205,201,199,202,
  391. };
  392. /// <summary>
  393. /// Cyrillic translation table for Mac character set.
  394. /// </summary>
  395. private static readonly byte[] cyrMac = new byte[]
  396. {
  397. 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
  398. 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,
  399. 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,
  400. 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,
  401. 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,
  402. 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,
  403. 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,
  404. 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,
  405. 225,226,247,231,228,229,246,250,233,234,235,236,237,238,239,240,
  406. 242,243,244,245,230,232,227,254,251,253,255,249,248,252,224,241,
  407. 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
  408. 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
  409. 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
  410. 144,145,146,147,148,149,150,151,152,153,154,155,156,179,163,209,
  411. 193,194,215,199,196,197,214,218,201,202,203,204,205,206,207,208,
  412. 210,211,212,213,198,200,195,222,219,221,223,217,216,220,192,255,
  413. 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
  414. 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,
  415. 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,
  416. 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,
  417. 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,
  418. 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,
  419. 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,
  420. 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,
  421. 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,
  422. 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
  423. 160,161,162,222,164,165,166,167,168,169,170,171,172,173,174,175,
  424. 176,177,178,221,180,181,182,183,184,185,186,187,188,189,190,191,
  425. 254,224,225,246,228,229,244,227,245,232,233,234,235,236,237,238,
  426. 239,223,240,241,242,243,230,226,252,251,231,248,253,249,247,250,
  427. 158,128,129,150,132,133,148,131,149,136,137,138,139,140,141,142,
  428. 143,159,144,145,146,147,134,130,156,155,135,152,157,153,151,154,
  429. };
  430. #endregion
  431. /// <summary>
  432. /// Returns a Cyrillic translation table for a specified character set,
  433. /// </summary>
  434. /// <param name="code">The character set code. Can be one of 'k', 'w', 'i', 'a', 'd', 'm'.</param>
  435. /// <returns>The translation table or null if no table is associated with given charset code.</returns>
  436. internal static byte[] GetCyrTableInternal(char code)
  437. {
  438. switch (Char.ToUpper(code))
  439. {
  440. case 'W':
  441. return cyrWin1251;
  442. case 'A':
  443. case 'D':
  444. return cyrCp866;
  445. case 'I':
  446. return cyrIso88595;
  447. case 'M':
  448. return cyrMac;
  449. case 'K':
  450. return null;
  451. default:
  452. return ArrayUtils.EmptyBytes;
  453. }
  454. }
  455. /// <include file='Doc/Strings.xml' path='docs/method[@name="ConvertCyrillic"]/*'/>
  456. /// <exception cref="PhpException">Thrown if source or destination charset is invalid. </exception>
  457. [ImplementsFunction("convert_cyr_string")]
  458. public static PhpBytes ConvertCyrillic(PhpBytes bytes, string srcCharset, string dstCharset)
  459. {
  460. if (bytes == null) return null;
  461. if (bytes.Length == 0) return PhpBytes.Empty;
  462. // checks srcCharset argument:
  463. if (srcCharset == null || srcCharset == String.Empty)
  464. {
  465. PhpException.InvalidArgument("srcCharset", LibResources.GetString("arg:null_or_empty"));
  466. return PhpBytes.Empty;
  467. }
  468. // checks dstCharset argument:
  469. if (dstCharset == null || dstCharset == String.Empty)
  470. {
  471. PhpException.InvalidArgument("dstCharset", LibResources.GetString("arg:null_or_empty"));
  472. return PhpBytes.Empty;
  473. }
  474. // get and check source charset table:
  475. byte[] fromTable = GetCyrTableInternal(srcCharset[0]);
  476. if (fromTable != null && fromTable.Length < 256)
  477. {
  478. PhpException.Throw(PhpError.Warning, LibResources.GetString("invalid_src_charser"));
  479. return PhpBytes.Empty;
  480. }
  481. // get and check destination charset table:
  482. byte[] toTable = GetCyrTableInternal(dstCharset[0]);
  483. if (toTable != null && toTable.Length < 256)
  484. {
  485. PhpException.Throw(PhpError.Warning, LibResources.GetString("invalid_dst_charser"));
  486. return PhpBytes.Empty;
  487. }
  488. byte[] data = bytes.ReadonlyData;
  489. byte[] result = new byte[data.Length];
  490. // perform conversion:
  491. if (fromTable == null)
  492. {
  493. if (toTable != null)
  494. {
  495. for (int i = 0; i < data.Length; i++) result[i] = toTable[data[i] + 256];
  496. }
  497. }
  498. else
  499. {
  500. if (toTable == null)
  501. {
  502. for (int i = 0; i < data.Length; i++) result[i] = fromTable[data[i]];
  503. }
  504. else
  505. {
  506. for (int i = 0; i < data.Length; i++) result[i] = toTable[fromTable[data[i]] + 256];
  507. }
  508. }
  509. return new PhpBytes(result);
  510. }
  511. #endregion
  512. #region count_chars
  513. /// <summary>
  514. /// Creates a histogram of Unicode character occurence in the given string.
  515. /// </summary>
  516. /// <param name="str">The string to be processed.</param>
  517. /// <returns>The array of characters frequency (unsorted).</returns>
  518. [ImplementsFunction("count_chars_unicode")]
  519. public static PhpArray CountChars(string str)
  520. {
  521. PhpArray count = new PhpArray();
  522. for (int i = str.Length - 1; i >= 0; i--)
  523. {
  524. int j = (int)str[i];
  525. object c = count[j];
  526. count[j] = (c == null) ? 1 : (int)c + 1;
  527. }
  528. return count;
  529. }
  530. /// <summary>
  531. /// Creates a histogram of byte occurence in the given array of bytes.
  532. /// </summary>
  533. /// <param name="bytes">The array of bytes to be processed.</param>
  534. /// <returns>The array of bytes frequency.</returns>
  535. public static int[] CountBytes(byte[] bytes)
  536. {
  537. if (bytes == null)
  538. throw new ArgumentNullException("bytes");
  539. int[] count = new int[256];
  540. for (int i = bytes.Length - 1; i >= 0; i--)
  541. count[bytes[i]]++;
  542. return count;
  543. }
  544. /// <summary>
  545. /// Creates a histogram of byte occurrence in specified string of bytes.
  546. /// </summary>
  547. /// <param name="bytes">Bytes to be processed.</param>
  548. /// <returns>The array of characters frequency.</returns>
  549. [ImplementsFunction("count_chars")]
  550. public static PhpArray CountChars(PhpBytes bytes)
  551. {
  552. return (bytes == null) ? new PhpArray() : new PhpArray(CountBytes(bytes.ReadonlyData), 0, 256);
  553. }
  554. /// <summary>
  555. /// Creates a histogram of character occurence in a string or string of bytes.
  556. /// </summary>
  557. /// <param name="data">The string or bytes to be processed.</param>
  558. /// <param name="mode">Determines the type of result.</param>
  559. /// <returns>Depending on <paramref name="mode"/> the following is returned:
  560. /// <list type="bullet">
  561. /// <item><term>0</term><description>an array with the character ordinals as key and their frequency as value,</description></item>
  562. /// <item><term>1</term><description>same as 0 but only characters with a frequency greater than zero are listed,</description></item>
  563. /// <item><term>2</term><description>same as 0 but only characters with a frequency equal to zero are listed,</description></item>
  564. /// <item><term>3</term><description>a string containing all used characters is returned,</description></item>
  565. /// <item><term>4</term><description>a string containing all not used characters is returned.</description></item>
  566. /// </list>
  567. /// </returns>
  568. /// <exception cref="PhpException">The <paramref name="mode"/> is invalid.</exception>
  569. /// <exception cref="PhpException">The <paramref name="data"/> contains Unicode characters greater than '\u0800'.</exception>
  570. [ImplementsFunction("count_chars")]
  571. public static object CountChars(object data, int mode)
  572. {
  573. try
  574. {
  575. switch (mode)
  576. {
  577. case 0: return new PhpArray(CountBytes(Core.Convert.ObjectToPhpBytes(data).ReadonlyData), 0, 256);
  578. case 1: return new PhpArray(CountBytes(Core.Convert.ObjectToPhpBytes(data).ReadonlyData), 0, 256, 0, true);
  579. case 2: return new PhpArray(CountBytes(Core.Convert.ObjectToPhpBytes(data).ReadonlyData), 0, 256, 0, false);
  580. case 3: return GetBytesContained(Core.Convert.ObjectToPhpBytes(data), 0, 255);
  581. case 4: return GetBytesNotContained(Core.Convert.ObjectToPhpBytes(data), 0, 255);
  582. default: PhpException.InvalidArgument("mode"); return null;
  583. }
  584. }
  585. catch (IndexOutOfRangeException)
  586. {
  587. // thrown by char map:
  588. PhpException.Throw(PhpError.Warning, LibResources.GetString("too_big_unicode_character"));
  589. return null;
  590. }
  591. }
  592. /// <summary>
  593. /// Returns a <see cref="String"/> containing all characters used in the specified <see cref="String"/>.
  594. /// </summary>
  595. /// <param name="str">The string to process.</param>
  596. /// <param name="lower">The lower limit for returned chars.</param>
  597. /// <param name="upper">The upper limit for returned chars.</param>
  598. /// <returns>
  599. /// The string containing characters used in <paramref name="str"/> which are sorted according to their ordinal values.
  600. /// </returns>
  601. /// <exception cref="IndexOutOfRangeException"><paramref name="str"/> contains characters greater than '\u0800'.</exception>
  602. public static string GetCharactersContained(string str, char lower, char upper)
  603. {
  604. CharMap charmap = InitializeCharMap();
  605. charmap.Add(str);
  606. return charmap.ToString(lower, upper, false);
  607. }
  608. /// <summary>
  609. /// Returns a <see cref="String"/> containing all characters used in the specified <see cref="String"/>.
  610. /// </summary>
  611. /// <param name="str">The string to process.</param>
  612. /// <param name="lower">The lower limit for returned chars.</param>
  613. /// <param name="upper">The upper limit for returned chars.</param>
  614. /// <returns>
  615. /// The string containing characters used in <paramref name="str"/> which are sorted according to their ordinal values.
  616. /// </returns>
  617. /// <exception cref="IndexOutOfRangeException"><paramref name="str"/> contains characters greater than '\u0800'.</exception>
  618. public static string GetCharactersNotContained(string str, char lower, char upper)
  619. {
  620. CharMap charmap = InitializeCharMap();
  621. charmap.Add(str);
  622. return charmap.ToString(lower, upper, true);
  623. }
  624. private static BitArray CreateByteMap(PhpBytes/*!*/ bytes, out int count)
  625. {
  626. BitArray map = new BitArray(256);
  627. map.Length = 256;
  628. count = 0;
  629. for (int i = 0; i < bytes.Length; i++)
  630. {
  631. if (!map[bytes[i]])
  632. {
  633. map[bytes[i]] = true;
  634. count++;
  635. }
  636. }
  637. return map;
  638. }
  639. public static PhpBytes GetBytesContained(PhpBytes bytes, byte lower, byte upper)
  640. {
  641. if (bytes == null) bytes = PhpBytes.Empty;
  642. int count;
  643. BitArray map = CreateByteMap(bytes, out count);
  644. byte[] result = new byte[count];
  645. int j = 0;
  646. for (int i = lower; i <= upper; i++)
  647. {
  648. if (map[i]) result[j++] = (byte)i;
  649. }
  650. return new PhpBytes(result);
  651. }
  652. public static PhpBytes GetBytesNotContained(PhpBytes bytes, byte lower, byte upper)
  653. {
  654. if (bytes == null) bytes = PhpBytes.Empty;
  655. int count;
  656. BitArray map = CreateByteMap(bytes, out count);
  657. byte[] result = new byte[map.Length - count];
  658. int j = 0;
  659. for (int i = lower; i <= upper; i++)
  660. {
  661. if (!map[i]) result[j++] = (byte)i;
  662. }
  663. return new PhpBytes(result);
  664. }
  665. #endregion
  666. #region crypt (CLR only)
  667. #if !SILVERLIGHT
  668. /// <summary>
  669. /// Specifies whether standard DES algorithm is implemented.
  670. /// We set it to 1, but it's not really true - our DES encryption is nothing like PHP's, so the values will be different
  671. /// If you want key compatibility with PHP, use CRYPT_MD5 by passing in a key starting with "?1?"
  672. /// </summary>
  673. [ImplementsConstant("CRYPT_STD_DES")]
  674. public const int CryptStandardDES = 1;
  675. /// <summary>
  676. /// Specifies whether extended DES algorithm is implemented.
  677. /// </summary>
  678. [ImplementsConstant("CRYPT_EXT_DES")]
  679. public const int CryptExtendedDES = 0;
  680. /// <summary>
  681. /// Specifies whether MD5 algorithm is implemented.
  682. /// </summary>
  683. [ImplementsConstant("CRYPT_MD5")]
  684. public const int CryptMD5 = 1;
  685. /// <summary>
  686. /// Specifies whether Blowfish encryption is implemented.
  687. /// </summary>
  688. [ImplementsConstant("CRYPT_BLOWFISH")]
  689. public const int CryptBlowfish = 0;
  690. /// <summary>
  691. /// Specifies the length of the salt applicable to the <see cref="Encrypt"/> method.
  692. /// </summary>
  693. [ImplementsConstant("CRYPT_SALT_LENGTH")]
  694. public const int CryptSaltLength = 9;
  695. /// <summary>
  696. /// Encrypts a string (one-way) with a random key.
  697. /// </summary>
  698. /// <param name="str">The string to encrypt.</param>
  699. /// <returns>The encrypted string.</returns>
  700. [ImplementsFunction("crypt")]
  701. public static PhpBytes Encrypt(PhpBytes str)
  702. {
  703. return Encrypt(str, null);
  704. }
  705. private const int MaxMD5Key = 12;
  706. private const int InternalMD5Key = 8;
  707. private const int MaxKeyLength = MaxMD5Key;
  708. private const int MaxDESKey = 8;
  709. public static bool ByteArrayEquals(byte[] array1, byte[] array2, int compareLength)
  710. {
  711. // If the other object is null, of a diffent type, or
  712. // of an array of a different length then skip out now.
  713. if ((array2 == null) || (array1 == null) || (compareLength <= 0 && (array1.Length != array2.Length)))
  714. return false;
  715. int minArray = Math.Min(array1.Length, array2.Length);
  716. if (compareLength <= 0)
  717. compareLength = minArray;
  718. else
  719. compareLength = Math.Min(minArray, compareLength);
  720. // If any of the elements are not equal, skip out.
  721. for (int i = 0; i < compareLength; ++i)
  722. if (array1[i] != array2[i])
  723. return false;
  724. // They're both the same length and the elements are all
  725. // equal so consider the arrays to be equal.
  726. return true;
  727. }
  728. //PHP's non-standard base64 used in converting md5 binary crypt() into chars
  729. //0 ... 63 => ascii - 64
  730. //aka bin_to_ascii ((c) >= 38 ? ((c) - 38 + 'a') : (c) >= 12 ? ((c) - 12 + 'A') : (c) + '.')
  731. private static byte[] itoa64 = System.Text.Encoding.ASCII.GetBytes("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
  732. private static void to64(MemoryStream stream, UInt32 v, int n)
  733. {
  734. while (--n >= 0)
  735. {
  736. stream.WriteByte(itoa64[v & 0x3f]);
  737. v >>= 6;
  738. }
  739. }
  740. private static byte[] MD5MagicString = System.Text.Encoding.ASCII.GetBytes("$1$");
  741. private static PhpBytes DoMD5Password(byte[] key, PhpBytes password)
  742. {
  743. MD5CryptoServiceProvider ctx = new MD5CryptoServiceProvider(), ctx1 = new MD5CryptoServiceProvider();
  744. MemoryStream result = new MemoryStream();
  745. byte[] final = new byte[16];
  746. int startOffset = 0, endOffset = 0;
  747. /* If it starts with the magic string, then skip that */
  748. if (ByteArrayEquals(key, MD5MagicString, MD5MagicString.Length))
  749. startOffset += MD5MagicString.Length;
  750. /* It stops at the first '$', max InternalMD5Key chars */
  751. for (endOffset = startOffset; key[endOffset] != '\0' && key[endOffset] != '$' && endOffset < (startOffset + InternalMD5Key); ++endOffset)
  752. continue;
  753. int keyLength = endOffset - startOffset;
  754. // PHP puts the relevant salt characters in the beginning
  755. result.Write(MD5MagicString, 0, MD5MagicString.Length);
  756. result.Write(key, startOffset, keyLength);
  757. result.Write(System.Text.Encoding.ASCII.GetBytes(new char[] { '$' }), 0, 1);
  758. ctx.Initialize();
  759. /* The password first, since that is what is most unknown */
  760. ctx.TransformBlock(password.ReadonlyData, 0, password.Length, null, 0);
  761. ctx.TransformBlock(MD5MagicString, 0, MD5MagicString.Length, null, 0);
  762. ctx.TransformBlock(key, startOffset, keyLength, null, 0);
  763. ctx1.Initialize();
  764. /* Then just as many characters of the MD5(pw,salt,pw) */
  765. ctx1.TransformBlock(password.ReadonlyData, 0, password.Length, null, 0);
  766. ctx1.TransformBlock(key, startOffset, keyLength, null, 0);
  767. ctx1.TransformFinalBlock(password.ReadonlyData, 0, password.Length);
  768. Array.Copy(ctx1.Hash, final, final.Length);
  769. for (int pl = password.Length; pl > 0; pl -= 16)
  770. ctx.TransformBlock(final, 0, pl > 16 ? 16 : pl, null, 0);
  771. //Clear the data
  772. for (int i = 0; i < final.Length; ++i)
  773. final[i] = 0;
  774. // "Then something really weird...", per zend PHP - what a ridiculous waste of CPU cycles
  775. byte[] zeroByte = new byte[1] { 0 };
  776. for (int i = password.Length; i != 0; i >>= 1)
  777. {
  778. if ((i & 1) != 0)
  779. ctx.TransformBlock(zeroByte, 0, 1, null, 0);
  780. else
  781. ctx.TransformBlock(password.ReadonlyData, 0, 1, null, 0);
  782. }
  783. ctx.TransformFinalBlock(ArrayUtils.EmptyBytes, 0, 0);
  784. Array.Copy(ctx.Hash, final, final.Length);
  785. /* Per md5crypt.c, again ridiculous but we want to keep consistent "
  786. * And now, just to make sure things don't run too fast. On a 60 MHz
  787. * Pentium this takes 34 msec, so you would need 30 seconds to build
  788. * a 1000 entry dictionary... "
  789. */
  790. for (int i = 0; i < 1000; ++i)
  791. {
  792. ctx1.Initialize();
  793. if ((i & 1) != 0)
  794. ctx1.TransformBlock(password.ReadonlyData, 0, password.Length, null, 0);
  795. else
  796. ctx1.TransformBlock(final, 0, final.Length, null, 0);
  797. if ((i % 3) != 0)
  798. ctx1.TransformBlock(key, startOffset, keyLength, null, 0);
  799. if ((i % 7) != 0)
  800. ctx1.TransformBlock(password.ReadonlyData, 0, password.Length, null, 0);
  801. if ((i & 1) != 0)
  802. ctx1.TransformFinalBlock(final, 0, final.Length);
  803. else
  804. ctx1.TransformFinalBlock(password.ReadonlyData, 0, password.Length);
  805. Array.Copy(ctx1.Hash, final, final.Length);
  806. }
  807. to64(result, ((UInt32)final[0] << 16) | ((UInt32)final[6] << 8) | (UInt32)final[12], 4);
  808. to64(result, ((UInt32)final[1] << 16) | ((UInt32)final[7] << 8) | (UInt32)final[13], 4);
  809. to64(result, ((UInt32)final[2] << 16) | ((UInt32)final[8] << 8) | (UInt32)final[14], 4);
  810. to64(result, ((UInt32)final[3] << 16) | ((UInt32)final[9] << 8) | (UInt32)final[15], 4);
  811. to64(result, ((UInt32)final[4] << 16) | ((UInt32)final[10] << 8) | (UInt32)final[5], 4);
  812. to64(result, (UInt32)final[11], 2);
  813. return new PhpBytes(result.ToArray());
  814. }
  815. /// <summary>
  816. /// Encrypts a string (one-way) with given key.
  817. /// </summary>
  818. /// <param name="str">The string of bytes to encrypt</param>
  819. /// <param name="salt">The key.</param>
  820. /// <returns>The encrypted string.</returns>
  821. [ImplementsFunction("crypt")]
  822. public static PhpBytes Encrypt(PhpBytes str, PhpBytes salt)
  823. {
  824. if (str == null) str = PhpBytes.Empty;
  825. Stream stream = new System.IO.MemoryStream(str.ReadonlyData);
  826. bool usemd5 = (salt == null) || (salt.Length == 0) || ByteArrayEquals(salt.ReadonlyData, MD5MagicString, MD5MagicString.Length);
  827. int requiredKeyLength = usemd5 ? MaxMD5Key : MaxDESKey;
  828. byte[] key = new byte[requiredKeyLength];
  829. int saltLength = requiredKeyLength;
  830. DES des = new DESCryptoServiceProvider();
  831. // prepare the key if salt is provided:
  832. if ((salt != null) && (salt.Length > 0))
  833. {
  834. //Fill with $'s first, same as zend PHP
  835. Array.Copy(System.Text.Encoding.ASCII.GetBytes(new String('$', requiredKeyLength)), key, requiredKeyLength);
  836. saltLength = System.Math.Min(requiredKeyLength, salt.Length);
  837. Array.Copy(salt.ReadonlyData, key, saltLength);
  838. }
  839. else
  840. Array.Copy(des.Key, key, InternalMD5Key); //Random 8-byte sequence
  841. if (usemd5)
  842. {
  843. return DoMD5Password(key, str);
  844. }
  845. else
  846. {
  847. MemoryStream result = new MemoryStream();
  848. des.IV = new byte[8];
  849. des.Key = key;
  850. ICryptoTransform transform = des.CreateEncryptor(des.Key, des.IV);
  851. CryptoStream cs = new CryptoStream(stream, transform, CryptoStreamMode.Read);
  852. // PHP puts the relevant salt characters in the beginning
  853. result.Write(key, 0, saltLength);
  854. byte[] buffer = new byte[256];
  855. int rd;
  856. while ((rd = cs.Read(buffer, 0, buffer.Length)) > 0)
  857. {
  858. int i;
  859. for (i = 0; i < rd; ++i)
  860. {
  861. switch (i % 3)
  862. {
  863. case 0:
  864. result.WriteByte(itoa64[buffer[i] >> 2]);
  865. break;
  866. case 1:
  867. result.WriteByte(itoa64[((buffer[i - 1] & 0x3) << 4) | (buffer[i] >> 4)]);
  868. break;
  869. case 2:
  870. result.WriteByte(itoa64[((buffer[i - 1] & 0xF) << 2) | (buffer[i] >> 6)]);
  871. result.WriteByte(itoa64[buffer[i] & 0x3F]);
  872. break;
  873. }
  874. }
  875. //Leftover bits
  876. switch (i % 3)
  877. {
  878. case 1:
  879. result.WriteByte(itoa64[((buffer[i - 1] & 0x3) << 4)]);
  880. break;
  881. case 2:
  882. result.WriteByte(itoa64[((buffer[i - 1] & 0xF) << 2)]);
  883. break;
  884. }
  885. }
  886. return new PhpBytes(result.ToArray());
  887. }
  888. }
  889. #endif
  890. #endregion
  891. #endregion
  892. #region strrev, strspn, strcspn
  893. /// <summary>
  894. /// Reverses the given string.
  895. /// </summary>
  896. /// <param name="obj">The string to be reversed.</param>
  897. /// <returns>The reversed string or empty string if <paramref name="obj"/> is null.</returns>
  898. [ImplementsFunction("strrev")]
  899. [PureFunction]
  900. public static object Reverse(object obj)
  901. {
  902. PhpBytes bytes;
  903. if ((bytes = obj as PhpBytes) != null)
  904. {
  905. return Reverse(bytes);
  906. }
  907. else
  908. {
  909. return Reverse(PHP.Core.Convert.ObjectToString(obj));
  910. }
  911. }
  912. internal static PhpBytes Reverse(PhpBytes bytes)
  913. {
  914. int length;
  915. if ((length = bytes.Length) == 0)
  916. return PhpBytes.Empty;
  917. byte[] reversed = new byte[length];
  918. byte[] data = bytes.ReadonlyData;
  919. for (int i = 0, j = length - 1; j >= 0; j--, i++)
  920. reversed[i] = data[j];
  921. return new PhpBytes(reversed);
  922. }
  923. internal static string Reverse(string str)
  924. {
  925. if (String.IsNullOrEmpty(str))
  926. return String.Empty;
  927. int length = str.Length;
  928. StringBuilder result = new StringBuilder(length, length);
  929. result.Length = length;
  930. for (int i = 0, j = length - 1; j >= 0; j--, i++)
  931. result[i] = str[j];
  932. return result.ToString();
  933. }
  934. /// <summary>
  935. /// Finds a length of an initial segment consisting entirely of specified characters.
  936. /// </summary>
  937. /// <param name="str">The string to be searched in.</param>
  938. /// <param name="acceptedChars">Accepted characters.</param>
  939. /// <returns>
  940. /// The length of the initial segment consisting entirely of characters in <paramref name="acceptedChars"/>
  941. /// or zero if any argument is null.
  942. /// </returns>
  943. [ImplementsFunction("strspn")]
  944. public static int StrSpn(string str, string acceptedChars)
  945. {
  946. return StrSpnInternal(str, acceptedChars, 0, int.MaxValue, false);
  947. }
  948. /// <summary>
  949. /// Finds a length of a segment consisting entirely of specified characters.
  950. /// </summary>
  951. /// <param name="str">The string to be searched in.</param>
  952. /// <param name="acceptedChars">Accepted characters.</param>
  953. /// <param name="offset">The relativized offset of the first item of the slice.</param>
  954. /// <returns>
  955. /// The length of the substring consisting entirely of characters in <paramref name="acceptedChars"/> or
  956. /// zero if any argument is null. Search starts from absolutized <paramref name="offset"/>
  957. /// (see <see cref="PhpMath.AbsolutizeRange"/> where <c>length</c> is infinity).
  958. /// </returns>
  959. [ImplementsFunction("strspn")]
  960. public static int StrSpn(string str, string acceptedChars, int offset)
  961. {
  962. return StrSpnInternal(str, acceptedChars, offset, int.MaxValue, false);
  963. }
  964. /// <summary>
  965. /// Finds a length of a segment consisting entirely of specified characters.
  966. /// </summary>
  967. /// <param name="str">The string to be searched in.</param>
  968. /// <param name="acceptedChars">Accepted characters.</param>
  969. /// <param name="offset">The relativized offset of the first item of the slice.</param>
  970. /// <param name="length">The relativized length of the slice.</param>
  971. /// <returns>
  972. /// The length of the substring consisting entirely of characters in <paramref name="acceptedChars"/> or
  973. /// zero if any argument is null. Search starts from absolutized <paramref name="offset"/>
  974. /// (see <see cref="PhpMath.AbsolutizeRange"/> and takes at most absolutized <paramref name="length"/> characters.
  975. /// </returns>
  976. [ImplementsFunction("strspn")]
  977. public static int StrSpn(string str, string acceptedChars, int offset, int length)
  978. {
  979. return StrSpnInternal(str, acceptedChars, offset, length, false);
  980. }
  981. /// <summary>
  982. /// Finds a length of an initial segment consisting entirely of any characters excpept for specified ones.
  983. /// </summary>
  984. /// <param name="str">The string to be searched in.</param>
  985. /// <param name="acceptedChars">Accepted characters.</param>
  986. /// <returns>
  987. /// The length of the initial segment consisting entirely of characters not in <paramref name="acceptedChars"/>
  988. /// or zero if any argument is null.
  989. /// </returns>
  990. [ImplementsFunction("strcspn")]
  991. public static int StrCSpn(string str, string acceptedChars)
  992. {
  993. return StrSpnInternal(str, acceptedChars, 0, int.MaxValue, true);
  994. }
  995. /// <summary>
  996. /// Finds a length of a segment consisting entirely of any characters excpept for specified ones.
  997. /// </summary>
  998. /// <param name="str">The string to be searched in.</param>
  999. /// <param name="acceptedChars">Accepted characters.</param>
  1000. /// <param name="offset">The relativized offset of the first item of the slice.</param>
  1001. /// <returns>
  1002. /// The length of the substring consisting entirely of characters not in <paramref name="acceptedChars"/> or
  1003. /// zero if any argument is null. Search starts from absolutized <paramref name="offset"/>
  1004. /// (see <see cref="PhpMath.AbsolutizeRange"/> where <c>length</c> is infinity).
  1005. /// </returns>
  1006. [ImplementsFunction("strcspn")]
  1007. public static int StrCSpn(string str, string acceptedChars, int offset)
  1008. {
  1009. return StrSpnInternal(str, acceptedChars, offset, int.MaxValue, true);
  1010. }
  1011. /// <summary>
  1012. /// Finds a length of a segment consisting entirely of any characters except for specified ones.
  1013. /// </summary>
  1014. /// <param name="str">The string to be searched in.</param>
  1015. /// <param name="acceptedChars">Accepted characters.</param>
  1016. /// <param name="offset">The relativized offset of the first item of the slice.</param>
  1017. /// <param name="length">The relativized length of the slice.</param>
  1018. /// <returns>
  1019. /// The length of the substring consisting entirely of characters not in <paramref name="acceptedChars"/> or
  1020. /// zero if any argument is null. Search starts from absolutized <paramref name="offset"/>
  1021. /// (see <see cref="PhpMath.AbsolutizeRange"/> and takes at most absolutized <paramref name="length"/> characters.
  1022. /// </returns>
  1023. [ImplementsFunction("strcspn")]
  1024. public static int StrCSpn(string str, string acceptedChars, int offset, int length)
  1025. {
  1026. return StrSpnInternal(str, acceptedChars, offset, length, true);
  1027. }
  1028. /// <summary>
  1029. /// Internal version of <see cref="StrSpn"/> (complement off) and <see cref="StrCSpn"/> (complement on).
  1030. /// </summary>
  1031. internal static int StrSpnInternal(string str, string acceptedChars, int offset, int length, bool complement)
  1032. {
  1033. if (str == null || acceptedChars == null) return 0;
  1034. PhpMath.AbsolutizeRange(ref offset, ref length, str.Length);
  1035. char[] chars = acceptedChars.ToCharArray();
  1036. Array.Sort(chars);
  1037. int j = offset;
  1038. if (complement)
  1039. {
  1040. while (length > 0 && ArrayUtils.BinarySearch(chars, str[j]) < 0) { j++; length--; }
  1041. }
  1042. else
  1043. {
  1044. while (length > 0 && ArrayUtils.BinarySearch(chars, str[j]) >= 0) { j++; length--; }
  1045. }
  1046. return j - offset;
  1047. }
  1048. #endregion
  1049. #region explode, implode
  1050. /// <summary>
  1051. /// Splits a string by string separators.
  1052. /// </summary>
  1053. /// <param name="separator">The substrings separator. Must not be empty.</param>
  1054. /// <param name="str">The string to be split.</param>
  1055. /// <returns>The array of strings.</returns>
  1056. [ImplementsFunction("explode")]
  1057. [return: CastToFalse]
  1058. public static PhpArray Explode(string separator, string str)
  1059. {
  1060. return Explode(separator, str, Int32.MaxValue);
  1061. }
  1062. /// <summary>
  1063. /// Splits a string by string separators with limited resulting array size.
  1064. /// </summary>
  1065. /// <param name="separator">The substrings separator. Must not be empty.</param>
  1066. /// <param name="str">The string to be split.</param>
  1067. /// <param name="limit">
  1068. /// The maximum number of elements in the resultant array. Zero value is treated in the same way as 1.
  1069. /// If negative, then the number of separators found in the string + 1 is added to the limit.
  1070. /// </param>
  1071. /// <returns>The array of strings.</returns>
  1072. /// <remarks>
  1073. /// If <paramref name="str"/> is empty an array consisting of exacty one empty string is returned.
  1074. /// If <paramref name="limit"/> is zero
  1075. /// </remarks>
  1076. /// <exception cref="PhpException">Thrown if the <paramref name="separator"/> is null or empty or if <paramref name="limit"/>is not positive nor -1.</exception>
  1077. [ImplementsFunction("explode")]
  1078. [return: CastToFalse]
  1079. public static PhpArray Explode(string separator, string str, int limit)
  1080. {
  1081. // validate parameters:
  1082. if (String.IsNullOrEmpty(separator))
  1083. {
  1084. PhpException.InvalidArgument("separator", LibResources.GetString("arg:null_or_empty"));
  1085. return null;
  1086. }
  1087. if (str == null) str = String.Empty;
  1088. bool last_part_is_the_rest = limit >= 0;
  1089. if (limit == 0)
  1090. limit = 1;
  1091. else if (limit < 0)
  1092. limit += SubstringCountInternal(str, separator, 0, str.Length) + 2;
  1093. // splits <str> by <separator>:
  1094. int sep_len = separator.Length;
  1095. int i = 0; // start searching at this position
  1096. int pos; // found separator's first character position
  1097. PhpArray result = new PhpArray(); // creates integer-keyed array with default capacity
  1098. var/*!*/compareInfo = System.Globalization.CultureInfo.InvariantCulture.CompareInfo;
  1099. while (--limit > 0)
  1100. {
  1101. pos = compareInfo.IndexOf(str, separator, i, str.Length - i, System.Globalization.CompareOptions.Ordinal);
  1102. if (pos < 0) break; // not found
  1103. result.AddToEnd(str.Substring(i, pos - i)); // faster than Add()
  1104. i = pos + sep_len;
  1105. }
  1106. // Adds last chunk. If separator ends the string, it will add empty string (as PHP do).
  1107. if (i <= str.Length && last_part_is_the_rest)
  1108. {
  1109. result.AddToEnd(str.Substring(i));
  1110. }
  1111. return result;
  1112. }
  1113. /// <summary>
  1114. /// Concatenates items of an array into a string separating them by a glue.
  1115. /// </summary>
  1116. /// <param name="pieces">The array to be impleded.</param>
  1117. /// <returns>The glued string.</returns>
  1118. [ImplementsFunction("join")]
  1119. public static object JoinGeneric(PhpArray pieces)
  1120. {
  1121. return ImplodeGeneric(pieces);
  1122. }
  1123. /// <summary>
  1124. /// Concatenates items of an array into a string separating them by a glue.
  1125. /// </summary>
  1126. /// <param name="pieces">The array to be impleded.</param>
  1127. /// <param name="glue">The glue string.</param>
  1128. /// <returns>The glued string.</returns>
  1129. /// <exception cref="PhpException">Thrown if neither <paramref name="glue"/> nor <paramref name="pieces"/> is not null and of type <see cref="PhpArray"/>.</exception>
  1130. [ImplementsFunction("join")]
  1131. public static object JoinGeneric(object glue, object pieces)
  1132. {
  1133. return ImplodeGeneric(glue, pieces);
  1134. }
  1135. /// <summary>
  1136. /// Concatenates items of an array into a string.
  1137. /// </summary>
  1138. /// <param name="pieces">The <see cref="PhpArray"/> to be imploded.</param>
  1139. /// <returns>The glued string.</returns>
  1140. [ImplementsFunction("implode")]
  1141. public static object ImplodeGeneric(PhpArray pieces)
  1142. {
  1143. if (pieces == null)
  1144. {
  1145. PhpException.ArgumentNull("pieces");
  1146. return null;
  1147. }
  1148. return Implode("", pieces);
  1149. }
  1150. /// <summary>
  1151. /// Concatenates items of an array into a string separating them by a glue.
  1152. /// </summary>
  1153. /// <param name="glue">The glue of type <see cref="string"/> or <see cref="PhpArray"/> to be imploded.</param>
  1154. /// <param name="pieces">The <see cref="PhpArray"/> to be imploded or glue of type <see cref="string"/>.</param>
  1155. /// <returns>The glued string.</returns>
  1156. /// <exception cref="PhpException">Thrown if neither <paramref name="glue"/> nor <paramref name="pieces"/> is not null and of type <see cref="PhpArray"/>.</exception>
  1157. [ImplementsFunction("implode")]
  1158. public static object ImplodeGeneric(object glue, object pieces)
  1159. {
  1160. if (pieces != null && pieces.GetType() == typeof(PhpArray))
  1161. return Implode(glue, (PhpArray)pieces);
  1162. if (glue != null && glue.GetType() == typeof(PhpArray))
  1163. return Implode(pieces, (PhpArray)glue);
  1164. return ImplodeGenericEnumeration(glue, pieces);
  1165. }
  1166. private static object ImplodeGenericEnumeration(object glue, object pieces)
  1167. {
  1168. Core.Reflection.DObject dobj;
  1169. IEnumerable enumerable;
  1170. if ((dobj = pieces as Core.Reflection.DObject) != null && (enumerable = dobj.RealObject as IEnumerable) != null)
  1171. return Implode(glue, new PhpArray(enumerable));
  1172. if ((dobj = glue as Core.Reflection.DObject) != null && (enumerable = dobj.RealObject as IEnumerable) != null)
  1173. return Implode(pieces, new PhpArray(enumerable));
  1174. //
  1175. PhpException.InvalidArgument("pieces");
  1176. return null;
  1177. }
  1178. /// <summary>
  1179. /// Concatenates items of an array into a string separating them by a glue.
  1180. /// </summary>
  1181. /// <param name="glue">The glue string.</param>
  1182. /// <param name="pieces">The enumeration to be imploded.</param>
  1183. /// <returns>The glued string.</returns>
  1184. /// <remarks>
  1185. /// Items of <paramref name="pieces"/> are converted to strings in the manner of PHP
  1186. /// (i.e. by <see cref="PHP.Core.Convert.ObjectToString"/>).
  1187. /// </remarks>
  1188. /// <exception cref="PhpException">Thrown if <paramref name="pieces"/> is null.</exception>
  1189. public static object Implode(object glue, PhpArray/*!*/pieces)
  1190. {
  1191. Debug.Assert(pieces != null);
  1192. // handle empty pieces:
  1193. if (pieces.Count == 0)
  1194. return string.Empty;
  1195. // check whether we have to preserve a binary string
  1196. bool binary = glue != null && glue.GetType() == typeof(PhpBytes);
  1197. if (!binary) // try to find any binary string within pieces:
  1198. using (var x = pieces.GetFastEnumerator())
  1199. while (x.MoveNext())
  1200. if (x.CurrentValue != null && x.CurrentValue.GetType() == typeof(PhpBytes))
  1201. {
  1202. binary = true;
  1203. break;
  1204. }
  1205. // concatenate pieces and glue:
  1206. bool not_first = false; // not the first iteration
  1207. if (binary)
  1208. {
  1209. Debug.Assert(pieces.Count > 0);
  1210. PhpBytes gluebytes = PHP.Core.Convert.ObjectToPhpBytes(glue);
  1211. PhpBytes[] piecesBytes = new PhpBytes[pieces.Count + pieces.Count - 1]; // buffer of PhpBytes to be concatenated
  1212. int p = 0;
  1213. using (var x = pieces.GetFastEnumerator())
  1214. while (x.MoveNext())
  1215. {
  1216. if (not_first) piecesBytes[p++] = gluebytes;
  1217. else not_first = true;
  1218. piecesBytes[p++] = PHP.Core.Convert.ObjectToPhpBytes(x.CurrentValue);
  1219. }
  1220. return PhpBytes.Concat(piecesBytes, 0, piecesBytes.Length);
  1221. }
  1222. else
  1223. {
  1224. string gluestr = PHP.Core.Convert.ObjectToString(glue);
  1225. StringBuilder result = new StringBuilder();
  1226. using (var x = pieces.GetFastEnumerator())
  1227. while (x.MoveNext())
  1228. {
  1229. if (not_first) result.Append(gluestr);
  1230. else not_first = true;
  1231. result.Append(PHP.Core.Convert.ObjectToString(x.CurrentValue));
  1232. }
  1233. return result.ToString();
  1234. }
  1235. }
  1236. #endregion
  1237. #region strtr, str_rot13
  1238. /// <summary>
  1239. /// Replaces specified characters in a string with another ones.
  1240. /// </summary>
  1241. /// <param name="str">A string where to do the replacement.</param>
  1242. /// <param name="from">Characters to be replaced.</param>
  1243. /// <param name="to">Characters to replace those in <paramref name="from"/> with.</param>
  1244. /// <returns>
  1245. /// A copy of <paramref name="str"/> with all occurrences of each character in <paramref name="from"/>
  1246. /// replaced by the corresponding character in <paramref name="to"/>.
  1247. /// </returns>
  1248. /// <remarks>
  1249. /// <para>If <paramref name="from"/> and <paramref name="to"/> are different lengths, the extra characters
  1250. /// in the longer of the two are ignored.</para>
  1251. /// </remarks>
  1252. [ImplementsFunction("strtr")]
  1253. [PureFunction]
  1254. public static string Translate(string str, string from, string to)
  1255. {
  1256. if (String.IsNullOrEmpty(str) || from == null || to == null) return String.Empty;
  1257. int min_length = Math.Min(from.Length, to.Length);
  1258. Dictionary<char, char> ht = new Dictionary<char, char>(min_length);
  1259. // adds chars to the hashtable:
  1260. for (int i = 0; i < min_length; i++)
  1261. ht[from[i]] = to[i];
  1262. // creates result builder:
  1263. StringBuilder result = new StringBuilder(str.Length, str.Length);
  1264. result.Length = str.Length;
  1265. // translates:
  1266. for (int i = 0; i < str.Length; i++)
  1267. {
  1268. char c = str[i];
  1269. char h;
  1270. result[i] = ht.TryGetValue(c, out h) ? h : c;
  1271. // obsolete:
  1272. // object h = ht[c];
  1273. // result[i] = (h==null) ? c : h;
  1274. }
  1275. return result.ToString();
  1276. }
  1277. /// <summary>
  1278. /// Compares objects according to the length of their string representation
  1279. /// as the primary criteria and the alphabetical order as the secondary one.
  1280. /// </summary>
  1281. private sealed class StringLengthComparer : IComparer<string>
  1282. {
  1283. /// <summary>
  1284. /// Performs length and alphabetical comparability backwards (longer first).
  1285. /// </summary>
  1286. /// <param name="x"></param>
  1287. /// <param name="y"></param>
  1288. /// <returns></returns>
  1289. public int Compare(string x, string y)
  1290. {
  1291. int rv = x.Length - y.Length;
  1292. if (rv == 0) return -string.CompareOrdinal(x, y);
  1293. else return -rv;
  1294. }
  1295. }
  1296. /// <summary>
  1297. /// Replaces substrings according to a dictionary.
  1298. /// </summary>
  1299. /// <param name="str">Input string.</param>
  1300. /// <param name="replacePairs">
  1301. /// An dictionary that contains <see cref="string"/> to <see cref="string"/> replacement mapping.
  1302. /// </param>
  1303. /// <returns>A copy of str, replacing all substrings (looking for the longest possible match).</returns>
  1304. /// <remarks>This function will not try to replace stuff that it has already worked on.</remarks>
  1305. /// <exception cref="PhpException">Thrown if the <paramref name="replacePairs"/> argument is null.</exception>
  1306. [ImplementsFunction("strtr")]
  1307. [return: CastToFalse]
  1308. public static string Translate(string str, PhpArray replacePairs)
  1309. {
  1310. if (replacePairs == null)
  1311. {
  1312. PhpException.ArgumentNull("replacePairs");
  1313. return null;
  1314. }
  1315. if (string.IsNullOrEmpty(str))
  1316. return String.Empty;
  1317. // sort replacePairs according to the key length, longer first
  1318. var count = replacePairs.Count;
  1319. var sortedkeys = new string[count];
  1320. var sortedValues = new string[count];
  1321. int i = 0;
  1322. var replacePairsEnum = replacePairs.GetFastEnumerator();
  1323. while (replacePairsEnum.MoveNext())
  1324. {
  1325. string key = replacePairsEnum.CurrentKey.ToString();
  1326. string value = Core.Convert.ObjectToString(replacePairsEnum.CurrentValue);
  1327. if (key.Length == 0)
  1328. {
  1329. // TODO: an exception ?
  1330. return null;
  1331. }
  1332. sortedkeys[i] = key;
  1333. sortedValues[i] = value;
  1334. i++;
  1335. }
  1336. Array.Sort<string, string>(sortedkeys, sortedValues, new StringLengthComparer()); // perform quick sort, much faster than SortedList
  1337. // perform replacement
  1338. StringBuilder result = new StringBuilder(str);
  1339. StringBuilder temp = new StringBuilder(str);
  1340. int length = str.Length;
  1341. int[] offset = new int[length];
  1342. for (i = 0; i < sortedkeys.Length; i++)
  1343. {
  1344. var key = sortedkeys[i];
  1345. int index = 0;
  1346. while ((index = temp.ToString().IndexOf(key, index, StringComparison.Ordinal)) >= 0) // ordinal search, because of exotic Unicode characters are find always at the beginning of the temp
  1347. {
  1348. var value = sortedValues[i];
  1349. var keyLength = key.Length;
  1350. int replaceAtIndex = index + offset[index];
  1351. // replace occurrence in result
  1352. result.Replace(index + offset[index], keyLength, value);
  1353. // Pack the offset array (drop the items removed from temp)
  1354. for (int j = index + keyLength; j < offset.Length; j++)
  1355. offset[j - keyLength] = offset[j];
  1356. // Ensure that we don't replace stuff that we already have worked on by
  1357. // removing the replaced substring from temp.
  1358. temp.Remove(index, keyLength);
  1359. for (int j = index; j < length; j++) offset[j] += value.Length;
  1360. }
  1361. }
  1362. return result.ToString();
  1363. }
  1364. /// <summary>
  1365. /// GetUserEntryPoint encode a string by shifting every letter (a-z, A-Z) by 13 places in the alphabet.
  1366. /// </summary>
  1367. /// <param name="str">The string to be encoded.</param>
  1368. /// <returns>The string with characters rotated by 13 places.</returns>
  1369. [ImplementsFunction("str_rot13")]
  1370. [PureFunction]
  1371. public static string Rotate13(string str)
  1372. {
  1373. return Translate(str,
  1374. "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
  1375. "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM");
  1376. }
  1377. #endregion
  1378. #region substr, str_repeat
  1379. /// <summary>
  1380. /// Retrieves a substring from the given string.
  1381. /// </summary>
  1382. /// <param name="str">The source string (unicode or binary).</param>
  1383. /// <param name="offset">The relativized offset of the first item of the slice.</param>
  1384. /// <returns>The substring of the <paramref name="str"/>.</returns>
  1385. /// <remarks>
  1386. /// See <see cref="PhpMath.AbsolutizeRange"/> for details about <paramref name="offset"/> where <c>length</c> is infinity.
  1387. /// </remarks>
  1388. [ImplementsFunction("substr")]
  1389. [PureFunction]
  1390. [return: CastToFalse]
  1391. public static object Substring(object str, int offset)
  1392. {
  1393. return Substring(str, offset, int.MaxValue);
  1394. }
  1395. /// <summary>
  1396. /// Retrieves a substring from the given string.
  1397. /// </summary>
  1398. /// <param name="str">The source string (unicode or binary).</param>
  1399. /// <param name="offset">The relativized offset of the first item of the slice.</param>
  1400. /// <param name="length">The relativized length of the slice.</param>
  1401. /// <returns>The substring of the <paramref name="str"/>.</returns>
  1402. /// <remarks>
  1403. /// See <see cref="PhpMath.AbsolutizeRange"/> for details about <paramref name="offset"/> and <paramref name="length"/>.
  1404. /// </remarks>
  1405. [ImplementsFunction("substr")]
  1406. [PureFunction]
  1407. [return: CastToFalse]
  1408. public static object Substring(object str, int offset, int length)
  1409. {
  1410. PhpBytes binstr = str as PhpBytes;
  1411. if (binstr != null)
  1412. {
  1413. if (binstr.Length == 0) return null;
  1414. PhpMath.AbsolutizeRange(ref offset, ref length, binstr.Length);
  1415. // string is shorter than offset to start substring
  1416. if (offset == binstr.Length) return null;
  1417. if (length == 0) return PhpBytes.Empty;
  1418. byte[] substring = new byte[length];
  1419. Buffer.BlockCopy(binstr.ReadonlyData, offset, substring, 0, length);
  1420. return new PhpBytes(substring);
  1421. }
  1422. string unistr = Core.Convert.ObjectToString(str);
  1423. if (unistr != null)
  1424. {
  1425. if (unistr == String.Empty) return null;
  1426. PhpMath.AbsolutizeRange(ref offset, ref length, unistr.Length);
  1427. // string is shorter than offset to start substring
  1428. if (offset == unistr.Length) return null;
  1429. if (length == 0) return String.Empty;
  1430. return unistr.Substring(offset, length);
  1431. }
  1432. return null;
  1433. }
  1434. /// <summary>
  1435. /// Repeats a string.
  1436. /// </summary>
  1437. /// <param name="str">The input string, can be both binary and unicode.</param>
  1438. /// <param name="count">The number of times <paramref name="str"/> should be repeated.</param>
  1439. /// <returns>The string where <paramref name="str"/> is repeated <paramref name="count"/> times.</returns>
  1440. /// <remarks>If <paramref name="str"/> is <b>null</b> reference, the function will return an empty string.</remarks>
  1441. /// <remarks>If <paramref name="count"/> is set to 0, the function will return <b>null</b> reference.</remarks>
  1442. /// <exception cref="PhpException">Thrown if <paramref name="count"/> is negative.</exception>
  1443. [ImplementsFunction("str_repeat")]
  1444. [PureFunction]
  1445. public static object Repeat(object str, int count)
  1446. {
  1447. if (str == null) return String.Empty;
  1448. if (count < 0)
  1449. {
  1450. PhpException.Throw(PhpError.Warning, LibResources.GetString("number_of_repetitions_negative"));
  1451. return null;
  1452. }
  1453. if (count == 0) return null;
  1454. PhpBytes binstr = str as PhpBytes;
  1455. if (binstr != null)
  1456. {
  1457. byte[] result = new byte[binstr.Length * count];
  1458. for (int i = 0; i < count; i++) Buffer.BlockCopy(binstr.ReadonlyData, 0, result, binstr.Length * i, binstr.Length);
  1459. return new PhpBytes(result);
  1460. }
  1461. string unistr = Core.Convert.ObjectToString(str);
  1462. if (unistr != null)
  1463. {
  1464. StringBuilder result = new StringBuilder(count * unistr.Length);
  1465. while (count-- > 0) result.Append(unistr);
  1466. return result.ToString();
  1467. }
  1468. return null;
  1469. }
  1470. #endregion
  1471. #region substr_count, substr_replace, substr_compare
  1472. #region substr_count internals
  1473. private static bool SubstringCountInternalCheck(string needle)
  1474. {
  1475. if (String.IsNullOrEmpty(needle))
  1476. {
  1477. PhpException.InvalidArgument("needle", LibResources.GetString("arg:null_or_empty"));
  1478. return false;
  1479. }
  1480. return true;
  1481. }
  1482. private static bool SubstringCountInternalCheck(string haystack, int offset)
  1483. {
  1484. if (offset < 0)
  1485. {
  1486. PhpException.InvalidArgument("offset", LibResources.GetString("substr_count_offset_zero"));
  1487. return false;
  1488. }
  1489. if (offset > haystack.Length)
  1490. {
  1491. PhpException.InvalidArgument("offset", LibResources.GetString("substr_count_offset_exceeds", offset));
  1492. return false;
  1493. }
  1494. return true;
  1495. }
  1496. private static bool SubstringCountInternalCheck(string haystack, int offset, int length)
  1497. {
  1498. if (!SubstringCountInternalCheck(haystack, offset))
  1499. return false;
  1500. if (length == 0)
  1501. {
  1502. PhpException.InvalidArgument("length", LibResources.GetString("substr_count_zero_length"));
  1503. return false;
  1504. }
  1505. if (offset + length > haystack.Length)
  1506. {
  1507. PhpException.InvalidArgument("length", LibResources.GetString("substr_count_length_exceeds", length));
  1508. return false;
  1509. }
  1510. return true;
  1511. }
  1512. /// <summary>
  1513. /// Count the number of substring occurrences. Expects correct argument values.
  1514. /// </summary>
  1515. internal static int SubstringCountInternal(string/*!*/ haystack, string/*!*/ needle, int offset, int end)
  1516. {
  1517. int result = 0;
  1518. if (needle.Length == 1)
  1519. {
  1520. while (offset < end)
  1521. {
  1522. if (haystack[offset] == needle[0]) result++;
  1523. offset++;
  1524. }
  1525. }
  1526. else
  1527. {
  1528. while ((offset = haystack.IndexOf(needle, offset, end - offset)) != -1)
  1529. {
  1530. offset += needle.Length;
  1531. result++;
  1532. }
  1533. }
  1534. return result;
  1535. }
  1536. #endregion
  1537. /// <summary>
  1538. /// See <see cref="SubstringCount(string,string,int,int)"/>.
  1539. /// </summary>
  1540. [ImplementsFunction("substr_count")]
  1541. [PureFunction]
  1542. [return: CastToFalse]
  1543. public static int SubstringCount(string haystack, string needle)
  1544. {
  1545. if (String.IsNullOrEmpty(haystack)) return 0;
  1546. if (!SubstringCountInternalCheck(needle)) return -1;
  1547. return SubstringCountInternal(haystack, needle, 0, haystack.Length);
  1548. }
  1549. /// <summary>
  1550. /// See <see cref="SubstringCount(string,string,int,int)"/>.
  1551. /// </summary>
  1552. [ImplementsFunction("substr_count")]
  1553. [PureFunction]
  1554. [return: CastToFalse]
  1555. public static int SubstringCount(string haystack, string needle, int offset)
  1556. {
  1557. if (String.IsNullOrEmpty(haystack)) return 0;
  1558. if (!SubstringCountInternalCheck(needle)) return -1;
  1559. if (!SubstringCountInternalCheck(haystack, offset)) return -1;
  1560. return SubstringCountInternal(haystack, needle, offset, haystack.Length);
  1561. }
  1562. /// <summary>
  1563. /// Count the number of substring occurrences.
  1564. /// </summary>
  1565. /// <param name="haystack">The string.</param>
  1566. /// <param name="needle">The substring.</param>
  1567. /// <param name="offset">The relativized offset of the first item of the slice. Zero if missing in overloads</param>
  1568. /// <param name="length">The relativized length of the slice. Infinity if missing in overloads.</param>
  1569. /// <returns>The number of <paramref name="needle"/> occurences in <paramref name="haystack"/>.</returns>
  1570. /// <example>"aba" has one occurence in "ababa".</example>
  1571. /// <remarks>
  1572. /// See <see cref="PhpMath.AbsolutizeRange"/> for details about <paramref name="offset"/> and <paramref name="length"/>.
  1573. /// </remarks>
  1574. /// <exception cref="PhpException">Thrown if <paramref name="needle"/> is null.</exception>
  1575. [ImplementsFunction("substr_count")]
  1576. [PureFunction]
  1577. [return: CastToFalse]
  1578. public static int SubstringCount(string haystack, string needle, int offset, int length)
  1579. {
  1580. if (String.IsNullOrEmpty(haystack)) return 0;
  1581. if (!SubstringCountInternalCheck(needle)) return -1;
  1582. if (!SubstringCountInternalCheck(haystack, offset, length)) return -1;
  1583. return SubstringCountInternal(haystack, needle, offset, offset + length);
  1584. }
  1585. /// <summary>
  1586. /// See <see cref="SubstringReplace(object,object,object,object)"/>.
  1587. /// </summary>
  1588. [ImplementsFunction("substr_replace")]
  1589. [PureFunction]
  1590. public static object SubstringReplace(object subject, object replacement, object offset)
  1591. {
  1592. return SubstringReplace(subject, replacement, offset, int.MaxValue);
  1593. }
  1594. /// <summary>
  1595. /// Replaces a portion of a string or multiple strings with another string.
  1596. /// </summary>
  1597. /// <param name="subject">The subject of replacement (can be an array of subjects).</param>
  1598. /// <param name="replacement">The replacement string (can be array of replacements).</param>
  1599. /// <param name="offset">The relativized offset of the first item of the slice (can be array of offsets).</param>
  1600. /// <param name="length">The relativized length of the slice (can be array of lengths).</param>
  1601. /// <returns>
  1602. /// Either the <paramref name="subject"/> with a substring replaced by <paramref name="replacement"/> if it is a string
  1603. /// or an array containing items of the <paramref name="subject"/> with substrings replaced by <paramref name="replacement"/>
  1604. /// and indexed by integer keys starting from 0. If <paramref name="replacement"/> is an array, multiple replacements take place.
  1605. /// </returns>
  1606. /// <remarks>
  1607. /// See <see cref="PhpMath.AbsolutizeRange"/> for details about <paramref name="offset"/> and <paramref name="length"/>.
  1608. /// Missing <paramref name="length"/> is considered to be infinity.
  1609. /// If <paramref name="offset"/> and <paramref name="length"/> conversion results in position
  1610. /// less than or equal to zero and greater than or equal to string length, the replacement is prepended and appended, respectively.
  1611. /// </remarks>
  1612. [ImplementsFunction("substr_replace")]
  1613. [PureFunction]
  1614. public static object SubstringReplace(object subject, object replacement, object offset, object length)
  1615. {
  1616. IDictionary dict_subject, dict_replacement, dict_offset, dict_length;
  1617. string[] replacements = null, subjects = null;
  1618. int[] offsets = null, lengths = null;
  1619. int int_offset = 0, int_length = 0;
  1620. string str_replacement = null;
  1621. // prepares string array of subjects:
  1622. if ((dict_subject = subject as IDictionary) != null)
  1623. {
  1624. subjects = new string[dict_subject.Count];
  1625. int i = 0;
  1626. foreach (object item in dict_subject.Values)
  1627. subjects[i++] = Core.Convert.ObjectToString(item);
  1628. }
  1629. else
  1630. {
  1631. subjects = new string[] { Core.Convert.ObjectToString(subject) };
  1632. }
  1633. // prepares string array of replacements:
  1634. if ((dict_replacement = replacement as IDictionary) != null)
  1635. {
  1636. replacements = new string[dict_replacement.Count];
  1637. int i = 0;
  1638. foreach (object item in dict_replacement.Values)
  1639. replacements[i++] = Core.Convert.ObjectToString(item);
  1640. }
  1641. else
  1642. {
  1643. str_replacement = Core.Convert.ObjectToString(replacement);
  1644. }
  1645. // prepares integer array of offsets:
  1646. if ((dict_offset = offset as IDictionary) != null)
  1647. {
  1648. offsets = new int[dict_offset.Count];
  1649. int i = 0;
  1650. foreach (object item in dict_offset.Values)
  1651. offsets[i++] = Core.Convert.ObjectToInteger(item);
  1652. }
  1653. else
  1654. {
  1655. int_offset = Core.Convert.ObjectToInteger(offset);
  1656. }
  1657. // prepares integer array of lengths:
  1658. if ((dict_length = length as IDictionary) != null)
  1659. {
  1660. lengths = new int[dict_length.Count];
  1661. int i = 0;
  1662. foreach (object item in dict_length.Values)
  1663. lengths[i++] = Core.Convert.ObjectToInteger(item);
  1664. }
  1665. else
  1666. {
  1667. int_length = Core.Convert.ObjectToInteger(length);
  1668. }
  1669. for (int i = 0; i < subjects.Length; i++)
  1670. {
  1671. if (dict_offset != null) int_offset = (i < offsets.Length) ? offsets[i] : 0;
  1672. if (dict_length != null) int_length = (i < lengths.Length) ? lengths[i] : subjects[i].Length;
  1673. if (dict_replacement != null) str_replacement = (i < replacements.Length) ? replacements[i] : "";
  1674. subjects[i] = SubstringReplace(subjects[i], str_replacement, int_offset, int_length);
  1675. }
  1676. if (dict_subject != null)
  1677. return new PhpArray(subjects);
  1678. else
  1679. return subjects[0];
  1680. }
  1681. /// <summary>
  1682. /// Performs substring replacements on subject.
  1683. /// </summary>
  1684. private static string SubstringReplace(string subject, string replacement, int offset, int length)
  1685. {
  1686. PhpMath.AbsolutizeRange(ref offset, ref length, subject.Length);
  1687. return new StringBuilder(subject).Remove(offset, length).Insert(offset, replacement).ToString();
  1688. }
  1689. /// <summary>
  1690. /// Case sensitive comparison of <paramref name="mainStr"/> from position <paramref name="offset"/>
  1691. /// with <paramref name="str"/>.
  1692. /// </summary>
  1693. /// <seealso cref="SubstringCompare(string,string,int,int,bool)"/>.
  1694. [ImplementsFunction("substr_compare")]
  1695. [PureFunction]
  1696. public static int SubstringCompare(string mainStr, string str, int offset)
  1697. {
  1698. return SubstringCompare(mainStr, str, offset, Int32.MaxValue, false);
  1699. }
  1700. /// <summary>
  1701. /// Case sensitive comparison of <paramref name="mainStr"/> from position <paramref name="offset"/>
  1702. /// with <paramref name="str"/> up to the <paramref name="length"/> characters.
  1703. /// </summary>
  1704. /// <seealso cref="SubstringCompare(string,string,int,int,bool)"/>.
  1705. [ImplementsFunction("substr_compare")]
  1706. [PureFunction]
  1707. public static int SubstringCompare(string mainStr, string str, int offset, int length)
  1708. {
  1709. return SubstringCompare(mainStr, str, offset, length, false);
  1710. }
  1711. /// <summary>
  1712. /// Compares substrings.
  1713. /// </summary>
  1714. /// <param name="mainStr">A string whose substring to compare with <paramref name="str"/>.</param>
  1715. /// <param name="str">The second operand of the comparison.</param>
  1716. /// <param name="offset">An offset in <paramref name="mainStr"/> where to start. Negative value means zero. Offsets beyond <paramref name="mainStr"/> means its length.</param>
  1717. /// <param name="length">A maximal number of characters to compare. Non-positive values means infinity.</param>
  1718. /// <param name="ignoreCase">Whether to ignore case.</param>
  1719. [ImplementsFunction("substr_compare")]
  1720. [PureFunction]
  1721. public static int SubstringCompare(string mainStr, string str, int offset, int length, bool ignoreCase)
  1722. {
  1723. if (mainStr == null) mainStr = "";
  1724. if (str == null) str = "";
  1725. if (length <= 0) length = Int32.MaxValue;
  1726. if (offset < 0) offset = 0;
  1727. if (offset > mainStr.Length) offset = mainStr.Length;
  1728. return String.Compare(mainStr, offset, str, 0, length, ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture);
  1729. }
  1730. #endregion
  1731. #region str_replace, str_ireplace
  1732. #region ReplaceInternal
  1733. /// <summary>
  1734. /// A class that enables customized replacement of substrings.
  1735. /// Optimized for multiple replacements.
  1736. /// </summary>
  1737. internal class SubstringReplacer
  1738. {
  1739. private Regex regex;
  1740. private int count;
  1741. private MatchEvaluator evaluator;
  1742. private string replacement;
  1743. private string search;
  1744. public SubstringReplacer(string/*!*/ search, string/*!*/ replacement, bool ignoreCase)
  1745. {
  1746. Debug.Assert(!string.IsNullOrEmpty(search), "Searched string shouln't be empty");
  1747. if (ignoreCase)
  1748. {
  1749. this.regex = new Regex(Regex.Escape(search), RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline);
  1750. this.evaluator = new MatchEvaluator(Evaluator);
  1751. }
  1752. this.search = search;
  1753. this.replacement = replacement;
  1754. }
  1755. /// <summary>
  1756. /// Called for each matched substring.
  1757. /// </summary>
  1758. /// <returns>Replacement.</returns>
  1759. private string Evaluator(Match match)
  1760. {
  1761. count++;
  1762. return replacement;
  1763. }
  1764. /// <summary>
  1765. /// Replaces all substrings of <paramref name="subject"/> specified in constructor parameter <c>search</c>
  1766. /// with <see cref="replacement"/>. If <paramref name="replacementCount"/> is non-negative,
  1767. /// advances it by the number of replacements. Retuns resulting string.
  1768. /// </summary>
  1769. public string Replace(string/*!*/ subject, ref int replacementCount)
  1770. {
  1771. string result;
  1772. if (regex == null)
  1773. {
  1774. if (replacementCount >= 0)
  1775. replacementCount += SubstringCountInternal(subject, search, 0, subject.Length);
  1776. if (search.Length != 1 || replacement.Length != 1)
  1777. result = subject.Replace(search, replacement);
  1778. else
  1779. result = subject.Replace(search[0], replacement[0]);
  1780. }
  1781. else
  1782. {
  1783. this.count = 0;
  1784. result = regex.Replace(subject, evaluator);
  1785. if (replacementCount >= 0)
  1786. replacementCount += this.count;
  1787. }
  1788. return result;
  1789. }
  1790. }
  1791. /*
  1792. /// <summary>
  1793. /// Get enumeration of given parameter.
  1794. /// </summary>
  1795. /// <param name="objOrDictionary">Single object or IDictionary of objects.</param>
  1796. /// <returns>IEnumerable of object/objects.</returns>
  1797. private static IEnumerable ValuesEnumerator(object objOrDictionary)
  1798. {
  1799. IDictionary dict;
  1800. if ((dict = objOrDictionary as IDictionary) != null)
  1801. return dict.Values;
  1802. else
  1803. return new object[] { objOrDictionary ?? string.Empty };
  1804. }
  1805. */
  1806. private class InifiniteEnumerator : IEnumerator
  1807. {
  1808. private readonly object obj;
  1809. public InifiniteEnumerator(object obj)
  1810. {
  1811. this.obj = obj;
  1812. }
  1813. #region IEnumerator Members
  1814. public object Current
  1815. {
  1816. get { return obj; }
  1817. }
  1818. public bool MoveNext()
  1819. {
  1820. return true;
  1821. }
  1822. public void Reset()
  1823. {
  1824. }
  1825. #endregion
  1826. }
  1827. internal static string ReplaceInternal(string/*!*/search, string replace, string/*!*/subject, bool ignoreCase, ref int count)
  1828. {
  1829. SubstringReplacer replacer = new SubstringReplacer(search, replace, ignoreCase);
  1830. return replacer.Replace(subject, ref count);
  1831. }
  1832. internal static string ReplaceInternal(IEnumerable searches, IEnumerator replacements_enum, string/*!*/subject, bool ignoreCase, ref int count)
  1833. {
  1834. //IEnumerator replacements_enum = replacements.GetEnumerator();
  1835. foreach (object s in searches)
  1836. {
  1837. string search_str = Core.Convert.ObjectToString(s);
  1838. string replacement_str = (replacements_enum.MoveNext()) ? Core.Convert.ObjectToString(replacements_enum.Current) : string.Empty;
  1839. if (search_str != string.Empty)
  1840. {
  1841. SubstringReplacer replacer = new SubstringReplacer(search_str, replacement_str, ignoreCase);
  1842. subject = replacer.Replace(subject, ref count);
  1843. if (subject == string.Empty)
  1844. break;
  1845. }
  1846. }
  1847. return subject;
  1848. }
  1849. internal static PhpArray ReplaceInternal(string search, string replace, ref DictionaryEntry[] subjects, bool ignoreCase, ref int count)
  1850. {
  1851. SubstringReplacer replacer = new SubstringReplacer(search, replace, ignoreCase);
  1852. PhpArray result = new PhpArray();
  1853. foreach (var entry in subjects)
  1854. {
  1855. // subjects has already been converted to CLR strings:
  1856. string subject_str = entry.Value as string;
  1857. result.Add(entry.Key, string.IsNullOrEmpty(subject_str) ? entry.Value : replacer.Replace(subject_str, ref count));
  1858. }
  1859. return result;
  1860. }
  1861. internal static PhpArray ReplaceInternal(IEnumerable searches, IEnumerator replacements_enum, ref DictionaryEntry[] subjects, bool ignoreCase, ref int count)
  1862. {
  1863. // performs replacement - subjects are enumerated once per each search-replacement pair;
  1864. // this order of loops enables to reuse instances of SubstringReplacer:
  1865. //IEnumerator replacements_enum = replacements.GetEnumerator();
  1866. foreach (object s in searches)
  1867. {
  1868. string search_str = Core.Convert.ObjectToString(s);
  1869. string replacement_str = (replacements_enum.MoveNext()) ? Core.Convert.ObjectToString(replacements_enum.Current) : string.Empty;
  1870. // skips empty strings:
  1871. if (search_str != string.Empty)
  1872. {
  1873. SubstringReplacer replacer = new SubstringReplacer(search_str, replacement_str, ignoreCase);
  1874. for (int i = 0; i < subjects.Length; i++)
  1875. {
  1876. // subjects has already been converted to CLR strings:
  1877. string subject_str = subjects[i].Value as string;
  1878. if (subject_str != null)
  1879. {
  1880. subjects[i].Value = replacer.Replace(subject_str, ref count);
  1881. }
  1882. }
  1883. }
  1884. }
  1885. // copy into PhpArray
  1886. return ToPhpArray(ref subjects);
  1887. }
  1888. /// <summary>
  1889. /// Convert array of DictionaryEntry into PhpArray.
  1890. /// </summary>
  1891. /// <param name="subjects"></param>
  1892. /// <returns></returns>
  1893. internal static PhpArray ToPhpArray(ref DictionaryEntry[]/*!*/subjects)
  1894. {
  1895. Debug.Assert(subjects != null);
  1896. var result = new PhpArray(subjects.Length);
  1897. foreach (var entry in subjects)
  1898. result.Add(entry.Key, entry.Value);
  1899. return result;
  1900. }
  1901. ///// <summary>
  1902. ///// Returns first item from values of given collection converted to string or empty string.
  1903. ///// </summary>
  1904. ///// <param name="dict"></param>
  1905. ///// <returns></returns>
  1906. //internal static string FirstOrEmpty(IDictionary/*!*/dict)
  1907. //{
  1908. // if (dict.Count > 0)
  1909. // {
  1910. // var dict_enum = dict.Values.GetEnumerator();
  1911. // if (dict_enum.MoveNext())
  1912. // return Core.Convert.ObjectToString(dict_enum.Current);
  1913. // }
  1914. // return string.Empty;
  1915. //}
  1916. /// <summary>
  1917. /// Implements <c>str_replace</c> and <c>str_ireplace</c> functions.
  1918. /// </summary>
  1919. internal static object ReplaceInternal(object search, object replace, object subject, bool ignoreCase, ref int count)
  1920. {
  1921. if (subject == null)
  1922. return null;
  1923. IDictionary searches = search as IDictionary;
  1924. IDictionary replacements = replace as IDictionary;
  1925. IDictionary subjects = subject as IDictionary;
  1926. //
  1927. // several cases of search/replace/subject combinations
  1928. //
  1929. if (subjects == null)
  1930. {
  1931. // string str_replace(..., ... , {string}, ...)
  1932. string subject_str = Core.Convert.ObjectToString(subject);
  1933. if (subject_str == string.Empty)
  1934. return string.Empty;
  1935. if (searches == null)
  1936. {
  1937. string search_str = Core.Convert.ObjectToString(search);
  1938. if (search_str == string.Empty)
  1939. return subject_str;
  1940. ////
  1941. //if (replacements == null)// str_replace({string},{string},{string},...);
  1942. // return ReplaceInternal(search_str, Core.Convert.ObjectToString(replace), subject_str, ignoreCase, ref count);
  1943. //else// str_replace({string},{array}[0],{string},...);
  1944. // return ReplaceInternal(search_str, FirstOrEmpty(replacements), subject_str, ignoreCase, ref count);
  1945. return ReplaceInternal(search_str, Core.Convert.ObjectToString(replace), subject_str, ignoreCase, ref count);
  1946. }
  1947. else
  1948. {
  1949. if (replacements == null)// str_replace({array},{string[1]},{string},...);
  1950. return ReplaceInternal(searches.Values, new InifiniteEnumerator(Core.Convert.ObjectToString(replace)), subject_str, ignoreCase, ref count);
  1951. else// str_replace({array},{array},{string},...);
  1952. return ReplaceInternal(searches.Values, replacements.Values.GetEnumerator(), subject_str, ignoreCase, ref count);
  1953. }
  1954. }
  1955. else
  1956. {
  1957. // converts scalars (and nulls) to strings:
  1958. var subjectEntries = new DictionaryEntry[subjects.Count];
  1959. int i = 0;
  1960. foreach (DictionaryEntry entry in subjects)
  1961. {
  1962. subjectEntries[i] = entry;
  1963. if (PhpVariable.IsScalar(entry.Value))
  1964. subjectEntries[i].Value = Core.Convert.ObjectToString(entry.Value);
  1965. else if (entry.Value == null)
  1966. subjectEntries[i].Value = string.Empty;
  1967. i++;
  1968. }
  1969. // PhpArray str_replace(..., ... , {array}, ...)
  1970. if (searches == null)
  1971. {
  1972. string search_str = Core.Convert.ObjectToString(search);
  1973. if (search_str == string.Empty)
  1974. return ToPhpArray(ref subjectEntries);
  1975. ////
  1976. //if (replacements == null)// str_replace({string},{string},{array},...);
  1977. // return ReplaceInternal(search_str, Core.Convert.ObjectToString(replace), ref subjectEntries, ignoreCase, ref count);
  1978. //else// str_replace({string},{array}[0],{array},...);
  1979. // return ReplaceInternal(search_str, FirstOrEmpty(replacements), ref subjectEntries, ignoreCase, ref count);
  1980. return ReplaceInternal(search_str, Core.Convert.ObjectToString(replace), ref subjectEntries, ignoreCase, ref count);
  1981. }
  1982. else
  1983. {
  1984. if (replacements == null)// str_replace({array},{string[1]},{array},...);
  1985. return ReplaceInternal(searches.Values, new InifiniteEnumerator(Core.Convert.ObjectToString(replace)), ref subjectEntries, ignoreCase, ref count);
  1986. else// str_replace({array},{array},{array},...);
  1987. return ReplaceInternal(searches.Values, replacements.Values.GetEnumerator(), ref subjectEntries, ignoreCase, ref count);
  1988. }
  1989. }
  1990. /*
  1991. //
  1992. // previous (common) implementation:
  1993. //
  1994. // assembles a dictionary of subject strings:
  1995. bool return_array;
  1996. DictionaryEntry[] subjects;
  1997. IDictionary dict;
  1998. if ((dict = subject as IDictionary) != null)
  1999. {
  2000. subjects = new DictionaryEntry[dict.Count];
  2001. // converts scalars to strings:
  2002. int i = 0;
  2003. foreach (DictionaryEntry entry in dict)
  2004. {
  2005. subjects[i] = entry;
  2006. if (PhpVariable.IsScalar(entry.Value))
  2007. subjects[i].Value = Core.Convert.ObjectToString(entry.Value);
  2008. i++;
  2009. }
  2010. return_array = true;
  2011. }
  2012. else
  2013. {
  2014. subjects = new DictionaryEntry[] { new DictionaryEntry(string.Empty, Core.Convert.ObjectToString(subject)) };
  2015. return_array = false;
  2016. }
  2017. // performs replacement - subjects are enumerated once per each search-replacement pair;
  2018. // this order of loops enables to reuse instances of SubstringReplacer:
  2019. IEnumerator replacements_enum = replacements.GetEnumerator();
  2020. foreach (object s in searches)
  2021. {
  2022. string search_str = Core.Convert.ObjectToString(s);
  2023. string replacement_str = (replacements_enum.MoveNext()) ? Core.Convert.ObjectToString(replacements_enum.Current) : string.Empty;
  2024. ReplaceInplace(search_str, replacement_str, ref subjects, ignoreCase, ref count);
  2025. }
  2026. // constructs resulting array or single item from subjects:
  2027. if (return_array)
  2028. {
  2029. PhpArray result = new PhpArray();
  2030. foreach (DictionaryEntry entry in subjects)
  2031. result.Add(entry.Key, entry.Value);
  2032. return result;
  2033. }
  2034. else
  2035. {
  2036. return subjects[0].Value;
  2037. }*/
  2038. }
  2039. #endregion
  2040. /// <summary>
  2041. /// Replaces all occurrences of the <paramref name="searched"/> string
  2042. /// with the <paramref name="replacement"/> string counting the number of occurrences.
  2043. /// </summary>
  2044. /// <param name="searched">
  2045. /// The substring(s) to replace. Can be string or <see cref="IDictionary"/> of strings.
  2046. /// </param>
  2047. /// <param name="replacement">
  2048. /// The string(s) to replace <paramref name="searched"/>. Can be string or <see cref="IDictionary"/> of strings.
  2049. /// </param>
  2050. /// <param name="subject">
  2051. /// The string or <see cref="IDictionary"/> of strings to perform the search and replace with.
  2052. /// </param>
  2053. /// <param name="count">
  2054. /// The number of matched and replaced occurrences.
  2055. /// </param>
  2056. /// <returns>
  2057. /// A string or an <see cref="IDictionary"/> with all occurrences of
  2058. /// <paramref name="searched"/> in <paramref name="subject"/> replaced
  2059. /// with the given <paramref name="replacement"/> value.
  2060. /// </returns>
  2061. [ImplementsFunction("str_replace")]
  2062. [PureFunction]
  2063. public static object Replace(object searched, object replacement, object subject, out int count)
  2064. {
  2065. count = 0;
  2066. return ReplaceInternal(searched, replacement, subject, false, ref count);
  2067. }
  2068. /// <summary>
  2069. /// Replaces all occurrences of the <paramref name="searched"/> string
  2070. /// with the <paramref name="replacement"/> string.
  2071. /// <seealso cref="Replace(object,object,object,out int)"/>
  2072. /// </summary>
  2073. [ImplementsFunction("str_replace")]
  2074. [PureFunction]
  2075. public static object Replace(object searched, object replacement, object subject)
  2076. {
  2077. int count = -1;
  2078. return ReplaceInternal(searched, replacement, subject, false, ref count);
  2079. }
  2080. /// <summary>
  2081. /// Case insensitive version of <see cref="Replace(object,object,object,out int)"/>.
  2082. /// </summary>
  2083. [ImplementsFunction("str_ireplace")]
  2084. [PureFunction]
  2085. public static object ReplaceInsensitively(object searched, object replacement, object subject, out int count)
  2086. {
  2087. count = 0;
  2088. return ReplaceInternal(searched, replacement, subject, true, ref count);
  2089. }
  2090. /// <summary>
  2091. /// Case insensitive version of <see cref="Replace(object,object,object)"/>.
  2092. /// </summary>
  2093. [ImplementsFunction("str_ireplace")]
  2094. [PureFunction]
  2095. public static object ReplaceInsensitively(object searched, object replacement, object subject)
  2096. {
  2097. int count = -1;
  2098. return ReplaceInternal(searched, replacement, subject, true, ref count);
  2099. }
  2100. #endregion
  2101. #region str_shuffle, str_split
  2102. /// <summary>
  2103. /// Randomly shuffles a string.
  2104. /// </summary>
  2105. /// <param name="str">The string to shuffle.</param>
  2106. /// <returns>One random permutation of <paramref name="str"/>.</returns>
  2107. [ImplementsFunction("str_shuffle")]
  2108. public static string Shuffle(string str)
  2109. {
  2110. if (str == null) return String.Empty;
  2111. Random generator = PhpMath.Generator;
  2112. int count = str.Length;
  2113. if (count <= 1) return str;
  2114. StringBuilder newstr = new StringBuilder(str);
  2115. // Takes n-th character from the string at random with probability 1/i
  2116. // and exchanges it with the one on the i-th position.
  2117. // Thus a random permutation is formed in the second part of the string (from i to count)
  2118. // and the set of remaining characters is stored in the first part.
  2119. for (int i = count - 1; i > 0; i--)
  2120. {
  2121. int n = generator.Next(i + 1);
  2122. char ch = newstr[i];
  2123. newstr[i] = newstr[n];
  2124. newstr[n] = ch;
  2125. }
  2126. return newstr.ToString();
  2127. }
  2128. /// <summary>
  2129. /// Converts a string to an array.
  2130. /// </summary>
  2131. /// <param name="str">The string to split.</param>
  2132. /// <returns>An array with keys being character indeces and values being characters.</returns>
  2133. [ImplementsFunction("str_split")]
  2134. [return: CastToFalse]
  2135. public static PhpArray Split(string str)
  2136. {
  2137. return Split(str, 1);
  2138. }
  2139. /// <summary>
  2140. /// Converts a string to an array.
  2141. /// </summary>
  2142. /// <param name="obj">The string to split.</param>
  2143. /// <param name="splitLength">Length of chunks <paramref name="obj"/> should be split into.</param>
  2144. /// <returns>An array with keys being chunk indeces and values being chunks of <paramref name="splitLength"/>
  2145. /// length.</returns>
  2146. /// <exception cref="PhpException">The <paramref name="splitLength"/> parameter is not positive (Warning).</exception>
  2147. [ImplementsFunction("str_split")]
  2148. [return: CastToFalse]
  2149. public static PhpArray Split(object obj, int splitLength)
  2150. {
  2151. if (splitLength < 1)
  2152. {
  2153. PhpException.Throw(PhpError.Warning, LibResources.GetString("segment_length_not_positive"));
  2154. return null;
  2155. }
  2156. if (obj == null)
  2157. return new PhpArray();
  2158. PhpBytes bytes;
  2159. if ((bytes = obj as PhpBytes) != null)
  2160. {
  2161. int length = bytes.Length;
  2162. PhpArray result = new PhpArray(length / splitLength + 1, 0);
  2163. // add items of length splitLength
  2164. int i;
  2165. for (i = 0; i < (length - splitLength + 1); i += splitLength)
  2166. {
  2167. byte[] chunk = new byte[splitLength];
  2168. Array.Copy(bytes.ReadonlyData, i, chunk, 0, chunk.Length);
  2169. result.Add(new PhpBytes(chunk));
  2170. }
  2171. // add the last item
  2172. if (i < length)
  2173. {
  2174. byte[] chunk = new byte[length - i];
  2175. Array.Copy(bytes.ReadonlyData, i, chunk, 0, chunk.Length);
  2176. result.Add(new PhpBytes(chunk));
  2177. }
  2178. return result;
  2179. }
  2180. else
  2181. {
  2182. return Split(PHP.Core.Convert.ObjectToString(obj), splitLength);
  2183. }
  2184. }
  2185. private static PhpArray Split(string str, int splitLength)
  2186. {
  2187. int length = str.Length;
  2188. PhpArray result = new PhpArray(length / splitLength + 1, 0);
  2189. // add items of length splitLength
  2190. int i;
  2191. for (i = 0; i < (length - splitLength + 1); i += splitLength)
  2192. {
  2193. result.Add(str.Substring(i, splitLength));
  2194. }
  2195. // add the last item
  2196. if (i < length) result.Add(str.Substring(i));
  2197. return result;
  2198. }
  2199. #endregion
  2200. #region quoted_printable_decode, quoted_printable_encode
  2201. /// <summary>
  2202. /// Maximum length of line according to quoted-printable specification.
  2203. /// </summary>
  2204. internal const int PHP_QPRINT_MAXL = 75;
  2205. /// <summary>
  2206. /// Converts a quoted-printable string into (an 8-bit) string.
  2207. /// </summary>
  2208. /// <param name="str">The quoted-printable string.</param>
  2209. /// <returns>The 8-bit string corresponding to the decoded <paramref name="str"/>.</returns>
  2210. /// <remarks>Based on the implementation in quot_print.c PHP source file.</remarks>
  2211. [ImplementsFunction("quoted_printable_decode")]
  2212. public static string QuotedPrintableDecode(string str)
  2213. {
  2214. if (str == null) return String.Empty;
  2215. Encoding encoding = Configuration.Application.Globalization.PageEncoding;
  2216. MemoryStream stream = new MemoryStream();
  2217. StringBuilder result = new StringBuilder(str.Length / 2);
  2218. int i = 0;
  2219. while (i < str.Length)
  2220. {
  2221. char c = str[i];
  2222. if (c == '=')
  2223. {
  2224. if (i + 2 < str.Length && Uri.IsHexDigit(str[i + 1]) && Uri.IsHexDigit(str[i + 2]))
  2225. {
  2226. stream.WriteByte((byte)((Uri.FromHex(str[i + 1]) << 4) + Uri.FromHex(str[i + 2])));
  2227. i += 3;
  2228. }
  2229. else // check for soft line break according to RFC 2045
  2230. {
  2231. int k = 1;
  2232. // Possibly, skip spaces/tabs at the end of line
  2233. while (i + k < str.Length && (str[i + k] == ' ' || str[i + k] == '\t')) k++;
  2234. // End of line reached
  2235. if (i + k >= str.Length)
  2236. {
  2237. i += k;
  2238. }
  2239. else if (str[i + k] == '\r' && i + k + 1 < str.Length && str[i + k + 1] == '\n')
  2240. {
  2241. // CRLF
  2242. i += k + 2;
  2243. }
  2244. else if (str[i + k] == '\r' || str[i + k] == '\n')
  2245. {
  2246. // CR or LF
  2247. i += k + 1;
  2248. }
  2249. else
  2250. {
  2251. // flush stream
  2252. if (stream.Position > 0)
  2253. {
  2254. result.Append(encoding.GetChars(stream.GetBuffer(), 0, (int)stream.Position));
  2255. stream.Seek(0, SeekOrigin.Begin);
  2256. }
  2257. result.Append(str[i++]);
  2258. }
  2259. }
  2260. }
  2261. else
  2262. {
  2263. // flush stream
  2264. if (stream.Position > 0)
  2265. {
  2266. result.Append(encoding.GetChars(stream.GetBuffer(), 0, (int)stream.Position));
  2267. stream.Seek(0, SeekOrigin.Begin);
  2268. }
  2269. result.Append(c);
  2270. i++;
  2271. }
  2272. }
  2273. // flush stream
  2274. if (stream.Position > 0)
  2275. {
  2276. result.Append(encoding.GetChars(stream.GetBuffer(), 0, (int)stream.Position));
  2277. stream.Seek(0, SeekOrigin.Begin);
  2278. }
  2279. return result.ToString();
  2280. }
  2281. /// <summary>
  2282. /// Convert a 8 bit string to a quoted-printable string
  2283. /// </summary>
  2284. /// <param name="str">The input string.</param>
  2285. /// <returns>The quoted-printable string.</returns>
  2286. /// <remarks>Based on the implementation in quot_print.c PHP source file.</remarks>
  2287. [ImplementsFunction("quoted_printable_encode")]
  2288. public static string QuotedPrintableEncode(string str)
  2289. {
  2290. if (str == null) return String.Empty;
  2291. Encoding encoding = Configuration.Application.Globalization.PageEncoding;
  2292. MemoryStream stream = new MemoryStream();
  2293. StringBuilder result = new StringBuilder(3 * str.Length + 3 * (((3 * str.Length) / PHP_QPRINT_MAXL) + 1));
  2294. string hex = "0123456789ABCDEF";
  2295. byte[] bytes = new byte[encoding.GetMaxByteCount(1)];
  2296. int encodedChars;
  2297. int i = 0;
  2298. int j = 0;
  2299. int charsOnLine = 0;
  2300. char c;
  2301. while (i < str.Length)
  2302. {
  2303. c = str[i];
  2304. if (c == '\r' && i + 1 < str.Length && str[i + 1] == '\n')
  2305. {
  2306. result.Append("\r\n");
  2307. charsOnLine = 0;
  2308. i += 2;
  2309. }
  2310. else
  2311. {
  2312. if (char.IsControl(c) ||
  2313. c >= 0x7F || // is not ascii char
  2314. (c == '=') ||
  2315. ((c == ' ') && i + 1 < str.Length && (str[i + 1] == '\r')))
  2316. {
  2317. if ((charsOnLine += 3) > PHP_QPRINT_MAXL)
  2318. {
  2319. result.Append("=\r\n");
  2320. charsOnLine = 3;
  2321. }
  2322. // encode c(==str[i])
  2323. encodedChars = encoding.GetBytes(str, i, 1, bytes, 0);
  2324. for (j = 0; j < encodedChars; ++j)
  2325. {
  2326. result.Append('=');
  2327. result.Append(hex[bytes[j] >> 4]);
  2328. result.Append(hex[bytes[j] & 0xf]);
  2329. }
  2330. }
  2331. else
  2332. {
  2333. if ((++charsOnLine) > PHP_QPRINT_MAXL)
  2334. {
  2335. result.Append("=\r\n");
  2336. charsOnLine = 1;
  2337. }
  2338. result.Append(c);
  2339. }
  2340. ++i;
  2341. }
  2342. }
  2343. return result.ToString();
  2344. }
  2345. #endregion
  2346. #region addslashes, addcslashes, quotemeta
  2347. /// <summary>
  2348. /// Adds backslashes before characters depending on current configuration.
  2349. /// </summary>
  2350. /// <param name="str">Data to process.</param>
  2351. /// <returns>
  2352. /// The string or string of bytes where some characters are preceded with the backslash character.
  2353. /// </returns>
  2354. /// <remarks>
  2355. /// If <see cref="LocalConfiguration.VariablesSection.QuoteInDbManner"/> ("magic_quotes_sybase" in PHP) option
  2356. /// is set then '\0' characters are slashed and single quotes are replaced with two single quotes. Otherwise,
  2357. /// '\'', '"', '\\' and '\0 characters are slashed.
  2358. /// </remarks>
  2359. [ImplementsFunction("addslashes")]
  2360. public static string AddSlashes(string str)
  2361. {
  2362. ScriptContext context = ScriptContext.CurrentContext;
  2363. if (context.Config.Variables.QuoteInDbManner)
  2364. return StringUtils.AddDbSlashes(str);
  2365. else
  2366. return StringUtils.AddCSlashes(str, true, true);
  2367. }
  2368. /// <include file='Doc/Strings.xml' path='docs/method[@name="AddCSlashes"]/*'/>
  2369. /// <exception cref="PhpException">Thrown if <paramref name="str"/> interval is invalid.</exception>
  2370. [ImplementsFunction("addcslashes_unicode")]
  2371. public static string AddCSlashes(string str, string mask)
  2372. {
  2373. if (str == null) return String.Empty;
  2374. if (mask == null) return str;
  2375. return AddCSlashesInternal(str, str, mask);
  2376. }
  2377. /// <include file='Doc/Strings.xml' path='docs/method[@name="AddCSlashes"]/*'/>
  2378. /// <exception cref="PhpException">Thrown if <paramref name="str"/> interval is invalid.</exception>
  2379. [ImplementsFunction("addcslashes")]
  2380. public static string AddCSlashesAscii(string str, string mask)
  2381. {
  2382. if (string.IsNullOrEmpty(str)) return String.Empty;
  2383. if (string.IsNullOrEmpty(mask)) return str;
  2384. //Encoding encoding = Configuration.Application.Globalization.PageEncoding;
  2385. //// to guarantee the same result both the string and the mask has to be converted to bytes:
  2386. //string c = ArrayUtils.ToString(encoding.GetBytes(mask));
  2387. //string s = ArrayUtils.ToString(encoding.GetBytes(str));
  2388. string c = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(mask));
  2389. string s = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(str));
  2390. // the result contains ASCII characters only, so there is no need to conversions:
  2391. return AddCSlashesInternal(str, s, c);
  2392. }
  2393. /// <param name="translatedStr">A sequence of chars or ints from which to take character codes.</param>
  2394. /// <param name="translatedMask">A mask containing codes.</param>
  2395. /// <param name="str">A string to be slashed.</param>
  2396. /// <exception cref="PhpException"><paramref name="translatedStr"/> interval is invalid.</exception>
  2397. /// <exception cref="PhpException"><paramref name="translatedStr"/> contains Unicode characters greater than '\u0800'.</exception>
  2398. internal static string AddCSlashesInternal(string str, string translatedStr, string translatedMask)
  2399. {
  2400. Debug.Assert(str != null && translatedMask != null && translatedStr != null && str.Length == translatedStr.Length);
  2401. // prepares the mask:
  2402. CharMap charmap = InitializeCharMap();
  2403. try
  2404. {
  2405. charmap.AddUsingMask(translatedMask);
  2406. }
  2407. catch (IndexOutOfRangeException)
  2408. {
  2409. PhpException.Throw(PhpError.Warning, LibResources.GetString("too_big_unicode_character"));
  2410. return null;
  2411. }
  2412. const string cslashed_chars = "abtnvfr";
  2413. StringBuilder result = new StringBuilder();
  2414. for (int i = 0; i < str.Length; i++)
  2415. {
  2416. //char c = translatedStr[i];
  2417. if (charmap.Contains(translatedStr[i]))
  2418. {
  2419. result.Append('\\');
  2420. char c = str[i]; // J: translatedStr and translatedMask are used only in context of CharMap, later we are working with original str only
  2421. // performs conversion to C representation:
  2422. if (c < '\u0020' || c > '\u007f')
  2423. {
  2424. if (c >= '\u0007' && c <= '\u000d')
  2425. result.Append(cslashed_chars[c - '\u0007']);
  2426. else
  2427. result.Append(System.Convert.ToString((int)c, 8)); // 0x01234567
  2428. }
  2429. else
  2430. result.Append(c);
  2431. }
  2432. else
  2433. result.Append(str[i]);
  2434. }
  2435. return result.ToString();
  2436. }
  2437. /// <summary>
  2438. /// A map of following characters: {'.', '\', '+', '*', '?', '[', '^', ']', '(', '$', ')'}.
  2439. /// </summary>
  2440. internal static readonly CharMap metaCharactersMap = new CharMap(new uint[] { 0, 0x08f20001, 0x0000001e });
  2441. /// <summary>
  2442. /// Adds backslashes before following characters: {'.', '\', '+', '*', '?', '[', '^', ']', '(', '$', ')'}
  2443. /// </summary>
  2444. /// <param name="str">The string to be processed.</param>
  2445. /// <returns>The string where said characters are backslashed.</returns>
  2446. [ImplementsFunction("quotemeta")]
  2447. public static string QuoteMeta(string str)
  2448. {
  2449. if (str == null) return String.Empty;
  2450. int length = str.Length;
  2451. StringBuilder result = new StringBuilder(length);
  2452. for (int i = 0; i < length; i++)
  2453. {
  2454. char c = str[i];
  2455. if (metaCharactersMap.Contains(c)) result.Append('\\');
  2456. result.Append(c);
  2457. }
  2458. return result.ToString();
  2459. }
  2460. #endregion
  2461. #region stripslashes, stripcslashes
  2462. /// <summary>
  2463. /// Unquote string quoted with <see cref="AddSlashes"/>.
  2464. /// </summary>
  2465. /// <param name="str">The string to unquote.</param>
  2466. /// <returns>The unquoted string.</returns>
  2467. [ImplementsFunction("stripslashes")]
  2468. public static string StripSlashes(string str)
  2469. {
  2470. ScriptContext context = ScriptContext.CurrentContext;
  2471. if (context.Config.Variables.QuoteInDbManner)
  2472. return StringUtils.StripDbSlashes(str);
  2473. else
  2474. return StringUtils.StripCSlashes(str);
  2475. }
  2476. /// <summary>
  2477. /// Returns a string with backslashes stripped off. Recognizes \a, \b, \f, \n, \r, \t, \v, \\, octal
  2478. /// and hexadecimal representation.
  2479. /// </summary>
  2480. /// <param name="str">The string to strip.</param>
  2481. /// <returns>The stripped string.</returns>
  2482. [ImplementsFunction("stripcslashes")]
  2483. public static string StripCSlashesAscii(string str)
  2484. {
  2485. if (str == null) return String.Empty;
  2486. Encoding encoding = Configuration.Application.Globalization.PageEncoding;
  2487. const char escape = '\\';
  2488. int length = str.Length;
  2489. StringBuilder result = new StringBuilder(length);
  2490. bool state1 = false;
  2491. byte[] bA1 = new byte[1];
  2492. for (int i = 0; i < length; i++)
  2493. {
  2494. char c = str[i];
  2495. if (c == escape && state1 == false)
  2496. {
  2497. state1 = true;
  2498. continue;
  2499. }
  2500. if (state1 == true)
  2501. {
  2502. switch (c)
  2503. {
  2504. case 'a': result.Append('\a'); break;
  2505. case 'b': result.Append('\b'); break;
  2506. case 'f': result.Append('\f'); break;
  2507. case 'n': result.Append('\n'); break;
  2508. case 'r': result.Append('\r'); break;
  2509. case 't': result.Append('\t'); break;
  2510. case 'v': result.Append('\v'); break;
  2511. case '\\': result.Append('\\'); break;
  2512. // hex ASCII code
  2513. case 'x':
  2514. {
  2515. int code = 0;
  2516. if (i + 1 < length && Uri.IsHexDigit(str[i + 1])) // first digit
  2517. {
  2518. code = Uri.FromHex(str[i + 1]);
  2519. i++;
  2520. if (i + 1 < length && Uri.IsHexDigit(str[i + 1])) // second digit
  2521. {
  2522. code = (code << 4) + Uri.FromHex(str[i + 1]);
  2523. i++;
  2524. }
  2525. bA1[0] = (byte)code;
  2526. result.Append(encoding.GetChars(bA1)[0]);
  2527. break;
  2528. }
  2529. goto default;
  2530. }
  2531. // octal ASCII code
  2532. default:
  2533. {
  2534. int code = 0, j = 0;
  2535. for (; j < 3 && i < length && str[i] >= '0' && str[i] <= '8'; i++, j++)
  2536. {
  2537. code = (code << 3) + (str[i] - '0');
  2538. }
  2539. if (j > 0)
  2540. {
  2541. i--;
  2542. bA1[0] = (byte)code;
  2543. result.Append(encoding.GetChars(bA1)[0]);
  2544. }
  2545. else result.Append(c);
  2546. break;
  2547. }
  2548. }
  2549. state1 = false;
  2550. }
  2551. else result.Append(c);
  2552. }
  2553. return result.ToString();
  2554. }
  2555. #endregion
  2556. #region htmlspecialchars, htmlspecialchars_decode
  2557. /// <summary>
  2558. /// Converts special characters to HTML entities.
  2559. /// </summary>
  2560. /// <param name="str">The string to convert.</param>
  2561. /// <returns>The converted string.</returns>
  2562. [ImplementsFunction("htmlspecialchars")]
  2563. public static string HtmlSpecialCharsEncode(string str)
  2564. {
  2565. return HtmlSpecialCharsEncode(str, 0, str.Length, QuoteStyle.Compatible, "ISO-8859-1");
  2566. }
  2567. /// <summary>
  2568. /// Converts special characters to HTML entities.
  2569. /// </summary>
  2570. /// <param name="str">The string to convert.</param>
  2571. /// <param name="quoteStyle">Quote conversion.</param>
  2572. /// <returns>The converted string.</returns>
  2573. [ImplementsFunction("htmlspecialchars")]
  2574. public static string HtmlSpecialCharsEncode(string str, QuoteStyle quoteStyle)
  2575. {
  2576. return HtmlSpecialCharsEncode(str, 0, str.Length, quoteStyle, "ISO-8859-1");
  2577. }
  2578. /// <summary>
  2579. /// Converts special characters to HTML entities.
  2580. /// </summary>
  2581. /// <param name="str">The string to convert.</param>
  2582. /// <param name="quoteStyle">Quote conversion.</param>
  2583. /// <param name="charSet">The character set used in conversion. This parameter is ignored.</param>
  2584. /// <returns>The converted string.</returns>
  2585. [ImplementsFunction("htmlspecialchars")]
  2586. public static string HtmlSpecialCharsEncode(string str, QuoteStyle quoteStyle, string charSet)
  2587. {
  2588. return HtmlSpecialCharsEncode(str, 0, str.Length, quoteStyle, charSet);
  2589. }
  2590. /// <summary>
  2591. /// Converts special characters to HTML entities.
  2592. /// </summary>
  2593. /// <param name="str">The string to convert.</param>
  2594. /// <param name="quoteStyle">Quote conversion.</param>
  2595. /// <param name="charSet">The character set used in conversion. This parameter is ignored.</param>
  2596. /// <param name="doubleEncode">When double_encode is turned off PHP will not encode existing html entities, the default is to convert everything.</param>
  2597. /// <returns>The converted string.</returns>
  2598. [ImplementsFunction("htmlspecialchars")]
  2599. public static string HtmlSpecialCharsEncode(string str, QuoteStyle quoteStyle, string charSet, bool doubleEncode /* = true */)
  2600. {
  2601. if (!doubleEncode)
  2602. PhpException.ArgumentValueNotSupported("doubleEncode", doubleEncode); // TODO: is doubleEncode is false
  2603. return HtmlSpecialCharsEncode(str, 0, str.Length, quoteStyle, charSet);
  2604. }
  2605. /// <summary>
  2606. /// Converts special characters of substring to HTML entities.
  2607. /// </summary>
  2608. /// <param name="str">The string.</param>
  2609. /// <param name="index">First character of the string to covert.</param>
  2610. /// <param name="length">Length of the substring to covert.</param>
  2611. /// <returns>The converted substring.</returns>
  2612. internal static string HtmlSpecialChars(string str, int index, int length)
  2613. {
  2614. return HtmlSpecialCharsEncode(str, index, length, QuoteStyle.Compatible, "ISO-8859-1");
  2615. }
  2616. /// <summary>
  2617. /// Converts special characters of substring to HTML entities.
  2618. /// </summary>
  2619. /// <param name="str">The string.</param>
  2620. /// <param name="index">First character of the string to covert.</param>
  2621. /// <param name="length">Length of the substring to covert.</param>
  2622. /// <param name="quoteStyle">Quote conversion.</param>
  2623. /// <param name="charSet">The character set used in conversion. This parameter is ignored.</param>
  2624. /// <returns>The converted substring.</returns>
  2625. internal static string HtmlSpecialCharsEncode(string str, int index, int length, QuoteStyle quoteStyle, string charSet)
  2626. {
  2627. if (str == null) return String.Empty;
  2628. Debug.Assert(index + length <= str.Length);
  2629. StringBuilder result = new StringBuilder(length);
  2630. // quote style is anded to emulate PHP behavior (any value is allowed):
  2631. string single_quote = (quoteStyle & QuoteStyle.SingleQuotes) != 0 ? "&#039;" : "'";
  2632. string double_quote = (quoteStyle & QuoteStyle.DoubleQuotes) != 0 ? "&quot;" : "\"";
  2633. for (int i = index; i < index + length; i++)
  2634. {
  2635. char c = str[i];
  2636. switch (c)
  2637. {
  2638. case '&': result.Append("&amp;"); break;
  2639. case '"': result.Append(double_quote); break;
  2640. case '\'': result.Append(single_quote); break;
  2641. case '<': result.Append("&lt;"); break;
  2642. case '>': result.Append("&gt;"); break;
  2643. default: result.Append(c); break;
  2644. }
  2645. }
  2646. return result.ToString();
  2647. }
  2648. /// <summary>
  2649. /// Converts HTML entities (&amp;amp;, &amp;quot;, &amp;lt;, and &amp;gt;)
  2650. /// in a specified string to the respective characters.
  2651. /// </summary>
  2652. /// <param name="str">The string to be converted.</param>
  2653. /// <returns>String with converted entities.</returns>
  2654. [ImplementsFunction("htmlspecialchars_decode")]
  2655. public static string HtmlSpecialCharsDecode(string str)
  2656. {
  2657. return HtmlSpecialCharsDecode(str, QuoteStyle.Compatible);
  2658. }
  2659. /// <summary>
  2660. /// Converts HTML entities (&amp;amp;, &amp;lt;, &amp;gt;, and optionally &amp;quot; and &amp;#039;)
  2661. /// in a specified string to the respective characters.
  2662. /// </summary>
  2663. /// <param name="str">The string to be converted.</param>
  2664. /// <param name="quoteStyle">Which quote entities to convert.</param>
  2665. /// <returns>String with converted entities.</returns>
  2666. [ImplementsFunction("htmlspecialchars_decode")]
  2667. public static string HtmlSpecialCharsDecode(string str, QuoteStyle quoteStyle)
  2668. {
  2669. if (str == null) return null;
  2670. StringBuilder result = new StringBuilder(str.Length);
  2671. bool dq = (quoteStyle & QuoteStyle.DoubleQuotes) != 0;
  2672. bool sq = (quoteStyle & QuoteStyle.SingleQuotes) != 0;
  2673. int i = 0;
  2674. while (i < str.Length)
  2675. {
  2676. char c = str[i];
  2677. if (c == '&')
  2678. {
  2679. i++;
  2680. if (i + 4 < str.Length && str[i + 4] == ';') // quot; #039;
  2681. {
  2682. if (dq && str[i] == 'q' && str[i + 1] == 'u' && str[i + 2] == 'o' && str[i + 3] == 't') { i += 5; result.Append('"'); continue; }
  2683. if (sq && str[i] == '#' && str[i + 1] == '0' && str[i + 2] == '3' && str[i + 3] == '9') { i += 5; result.Append('\''); continue; }
  2684. }
  2685. if (i + 3 < str.Length && str[i + 3] == ';') // amp; #39;
  2686. {
  2687. if (str[i] == 'a' && str[i + 1] == 'm' && str[i + 2] == 'p') { i += 4; result.Append('&'); continue; }
  2688. if (sq && str[i] == '#' && str[i + 1] == '3' && str[i + 2] == '9') { i += 4; result.Append('\''); continue; }
  2689. }
  2690. if (i + 2 < str.Length && str[i + 2] == ';' && str[i + 1] == 't') // lt; gt;
  2691. {
  2692. if (str[i] == 'l') { i += 3; result.Append('<'); continue; }
  2693. if (str[i] == 'g') { i += 3; result.Append('>'); continue; }
  2694. }
  2695. }
  2696. else
  2697. {
  2698. i++;
  2699. }
  2700. result.Append(c);
  2701. }
  2702. return result.ToString();
  2703. }
  2704. #endregion
  2705. #region htmlentities, get_html_translation_table, html_entity_decode
  2706. /// <summary>
  2707. /// Default <c>encoding</c> used in <c>htmlentities</c>.
  2708. /// </summary>
  2709. private const string DefaultHtmlEntitiesCharset = "UTF-8";
  2710. /// <summary>
  2711. /// Converts special characters to HTML entities.
  2712. /// </summary>
  2713. /// <param name="str">The string to convert.</param>
  2714. /// <returns>The converted string.</returns>
  2715. /// <remarks>This method is identical to <see cref="HtmlSpecialChars"/> in all ways, except with
  2716. /// <b>htmlentities</b> (<see cref="EncodeHtmlEntities"/>), all characters that have HTML character entity equivalents are
  2717. /// translated into these entities.</remarks>
  2718. [ImplementsFunction("htmlentities")]
  2719. public static string EncodeHtmlEntities(object str)
  2720. {
  2721. return EncodeHtmlEntities(str, QuoteStyle.HtmlEntitiesDefault, DefaultHtmlEntitiesCharset, true);
  2722. }
  2723. /// <summary>
  2724. /// Converts special characters to HTML entities.
  2725. /// </summary>
  2726. /// <param name="str">The string to convert.</param>
  2727. /// <param name="quoteStyle">Quote conversion.</param>
  2728. /// <returns>The converted string.</returns>
  2729. /// <remarks>This method is identical to <see cref="HtmlSpecialChars"/> in all ways, except with
  2730. /// <b>htmlentities</b> (<see cref="EncodeHtmlEntities"/>), all characters that have HTML character entity equivalents are
  2731. /// translated into these entities.</remarks>
  2732. [ImplementsFunction("htmlentities")]
  2733. public static string EncodeHtmlEntities(object str, QuoteStyle quoteStyle)
  2734. {
  2735. return EncodeHtmlEntities(str, quoteStyle, DefaultHtmlEntitiesCharset, true);
  2736. }
  2737. /// <summary>
  2738. /// Converts special characters to HTML entities.
  2739. /// </summary>
  2740. /// <param name="str">The string to convert.</param>
  2741. /// <param name="quoteStyle">Quote conversion.</param>
  2742. /// <param name="charSet">The character set used in conversion.</param>
  2743. /// <returns>The converted string.</returns>
  2744. /// <remarks>This method is identical to <see cref="HtmlSpecialChars"/> in all ways, except with
  2745. /// <b>htmlentities</b> (<see cref="EncodeHtmlEntities"/>), all characters that have HTML character entity equivalents are
  2746. /// translated into these entities.</remarks>
  2747. [ImplementsFunction("htmlentities")]
  2748. public static string EncodeHtmlEntities(object str, QuoteStyle quoteStyle, string charSet)
  2749. {
  2750. return EncodeHtmlEntities(str, quoteStyle, charSet, true);
  2751. }
  2752. /// <summary>
  2753. /// Converts special characters to HTML entities.
  2754. /// </summary>
  2755. /// <param name="str">The string to convert.</param>
  2756. /// <param name="quoteStyle">Quote conversion.</param>
  2757. /// <param name="charSet">The character set used in conversion. This parameter is ignored.</param>
  2758. /// <param name="doubleEncode">When it is turned off existing HTML entities will not be encoded. The default is to convert everything.</param>
  2759. /// <returns>The converted string.</returns>
  2760. /// <remarks>This method is identical to <see cref="HtmlSpecialChars"/> in all ways, except with
  2761. /// <b>htmlentities</b> (<see cref="EncodeHtmlEntities"/>), all characters that have HTML character entity equivalents are
  2762. /// translated into these entities.</remarks>
  2763. [ImplementsFunction("htmlentities")]
  2764. public static string EncodeHtmlEntities(object str, QuoteStyle quoteStyle, string charSet, bool doubleEncode)
  2765. {
  2766. try
  2767. {
  2768. var s = ObjectToString(str, charSet);
  2769. return EncodeHtmlEntities(s, quoteStyle, doubleEncode);
  2770. }
  2771. catch (ArgumentException ex)
  2772. {
  2773. PhpException.Throw(PhpError.Warning, ex.Message);
  2774. return string.Empty;
  2775. }
  2776. }
  2777. private static string EncodeHtmlEntities(string str, QuoteStyle quoteStyle, bool doubleEncode)
  2778. {
  2779. if (string.IsNullOrEmpty(str))
  2780. return string.Empty;
  2781. if (!doubleEncode)
  2782. { // existing HTML entities will not be double encoded // TODO: do it nicely
  2783. str = DecodeHtmlEntities(str, quoteStyle);
  2784. }
  2785. // if only double quotes should be encoded, we can use HttpUtility.HtmlEncode right away:
  2786. if ((quoteStyle & QuoteStyle.BothQuotes) == QuoteStyle.DoubleQuotes)
  2787. {
  2788. return HttpUtility.HtmlEncode(str);
  2789. }
  2790. // quote style is anded to emulate PHP behavior (any value is allowed):
  2791. string single_quote = (quoteStyle & QuoteStyle.SingleQuotes) != 0 ? "&#039;" : "'";
  2792. string double_quote = (quoteStyle & QuoteStyle.DoubleQuotes) != 0 ? "&quot;" : "\"";
  2793. StringBuilder str_builder = new StringBuilder(str.Length);
  2794. StringWriter result = new StringWriter(str_builder);
  2795. // convert ' and " manually, rely on HttpUtility.HtmlEncode for everything else
  2796. char[] quotes = new char[] { '\'', '\"' };
  2797. int old_index = 0, index = 0;
  2798. while (index < str.Length && (index = str.IndexOfAny(quotes, index)) >= 0)
  2799. {
  2800. result.Write(HttpUtility.HtmlEncode(str.Substring(old_index, index - old_index)));
  2801. if (str[index] == '\'') result.Write(single_quote);
  2802. else result.Write(double_quote);
  2803. old_index = ++index;
  2804. }
  2805. if (old_index < str.Length) result.Write(HttpUtility.HtmlEncode(str.Substring(old_index)));
  2806. result.Flush();
  2807. return str_builder.ToString();
  2808. }
  2809. /// <summary>
  2810. /// Returns the translation table used by <see cref="HtmlSpecialChars"/> and <see cref="EncodeHtmlEntities"/>.
  2811. /// </summary>
  2812. /// <param name="table">Type of the table that should be returned.</param>
  2813. /// <returns>The table.</returns>
  2814. [ImplementsFunction("get_html_translation_table")]
  2815. public static PhpArray GetHtmlTranslationTable(HtmlEntitiesTable table)
  2816. {
  2817. return GetHtmlTranslationTable(table, QuoteStyle.Compatible);
  2818. }
  2819. /// <summary>
  2820. /// Returns the translation table used by <see cref="HtmlSpecialChars"/> and <see cref="EncodeHtmlEntities"/>.
  2821. /// </summary>
  2822. /// <param name="table">Type of the table that should be returned.</param>
  2823. /// <param name="quoteStyle">Quote conversion.</param>
  2824. /// <returns>The table.</returns>
  2825. [ImplementsFunction("get_html_translation_table")]
  2826. public static PhpArray GetHtmlTranslationTable(HtmlEntitiesTable table, QuoteStyle quoteStyle)
  2827. {
  2828. PhpArray result = new PhpArray();
  2829. if (table == HtmlEntitiesTable.SpecialChars)
  2830. {
  2831. // return the table used with HtmlSpecialChars
  2832. if ((quoteStyle & QuoteStyle.SingleQuotes) != 0) result.Add("\'", "&#039;");
  2833. if ((quoteStyle & QuoteStyle.DoubleQuotes) != 0) result.Add("\"", "&quot;");
  2834. result.Add("&", "&amp;");
  2835. result.Add("<", "&lt;");
  2836. result.Add(">", "&gt;");
  2837. }
  2838. else
  2839. {
  2840. // return the table used with HtmlEntities
  2841. if ((quoteStyle & QuoteStyle.SingleQuotes) != 0) result.Add("\'", "&#039;");
  2842. if ((quoteStyle & QuoteStyle.DoubleQuotes) != 0) result.Add("\"", "&quot;");
  2843. for (char ch = (char)0; ch < 0x100; ch++)
  2844. {
  2845. if (ch != '\'' && ch != '\"')
  2846. {
  2847. string str = ch.ToString();
  2848. string enc = HttpUtility.HtmlEncode(str);
  2849. // if the character was encoded:
  2850. if (str != enc) result.Add(str, enc);
  2851. }
  2852. }
  2853. }
  2854. return result;
  2855. }
  2856. /// <summary>
  2857. /// Converts all HTML entities to their applicable characters.
  2858. /// </summary>
  2859. /// <param name="str">The string to convert.</param>
  2860. /// <returns>The converted string.</returns>
  2861. [ImplementsFunction("html_entity_decode")]
  2862. public static string DecodeHtmlEntities(object str)
  2863. {
  2864. return DecodeHtmlEntities(str, QuoteStyle.Compatible, DefaultHtmlEntitiesCharset);
  2865. }
  2866. /// <summary>
  2867. /// Converts all HTML entities to their applicable characters.
  2868. /// </summary>
  2869. /// <param name="str">The string to convert.</param>
  2870. /// <param name="quoteStyle">Quote conversion.</param>
  2871. /// <returns>The converted string.</returns>
  2872. [ImplementsFunction("html_entity_decode")]
  2873. public static string DecodeHtmlEntities(object str, QuoteStyle quoteStyle)
  2874. {
  2875. return DecodeHtmlEntities(str, quoteStyle, DefaultHtmlEntitiesCharset);
  2876. }
  2877. /// <summary>
  2878. /// Converts all HTML entities to their applicable characters.
  2879. /// </summary>
  2880. /// <param name="str">The string to convert.</param>
  2881. /// <param name="quoteStyle">Quote conversion.</param>
  2882. /// <param name="charSet">The character set used in conversion.</param>
  2883. /// <returns>The converted string.</returns>
  2884. [ImplementsFunction("html_entity_decode")]
  2885. public static string DecodeHtmlEntities(object str, QuoteStyle quoteStyle, string charSet)
  2886. {
  2887. try
  2888. {
  2889. string s = ObjectToString(str, charSet);
  2890. return DecodeHtmlEntities(s, quoteStyle);
  2891. }
  2892. catch (ArgumentException ex)
  2893. {
  2894. PhpException.Throw(PhpError.Warning, ex.Message);
  2895. return string.Empty;
  2896. }
  2897. }
  2898. private static string DecodeHtmlEntities(string str, QuoteStyle quoteStyle)
  2899. {
  2900. if (str == null) return String.Empty;
  2901. // if both quotes should be decoded, we can use HttpUtility.HtmlDecode right away:
  2902. if ((quoteStyle & QuoteStyle.BothQuotes) == QuoteStyle.BothQuotes)
  2903. {
  2904. return HttpUtility.HtmlDecode(str);
  2905. }
  2906. StringBuilder str_builder = new StringBuilder(str.Length);
  2907. StringWriter result = new StringWriter(str_builder);
  2908. // convert &#039;, &#39; and &quot; manually, rely on HttpUtility.HtmlDecode for everything else
  2909. int old_index = 0, index = 0;
  2910. while (index < str.Length && (index = str.IndexOf('&', index)) >= 0)
  2911. {
  2912. // &quot;
  2913. if ((quoteStyle & QuoteStyle.DoubleQuotes) == 0 && index < str.Length - 5 &&
  2914. str[index + 1] == 'q' && str[index + 2] == 'u' &&
  2915. str[index + 3] == 'o' && str[index + 4] == 't' &&
  2916. str[index + 5] == ';')
  2917. {
  2918. result.Write(HttpUtility.HtmlDecode(str.Substring(old_index, index - old_index)));
  2919. result.Write("&quot;");
  2920. old_index = (index += 6);
  2921. continue;
  2922. }
  2923. if ((quoteStyle & QuoteStyle.SingleQuotes) == 0)
  2924. {
  2925. // &#039;
  2926. if (index < str.Length - 5 && str[index + 1] == '#' &&
  2927. str[index + 2] == '0' && str[index + 3] == '3' &&
  2928. str[index + 4] == '9' && str[index + 5] == ';')
  2929. {
  2930. result.Write(HttpUtility.HtmlDecode(str.Substring(old_index, index - old_index)));
  2931. result.Write("&#039;");
  2932. old_index = (index += 6);
  2933. continue;
  2934. }
  2935. // &#39;
  2936. if (index < str.Length - 4 && str[index + 1] == '#' &&
  2937. str[index + 2] == '3' && str[index + 3] == '9' && str[index + 4] == ';')
  2938. {
  2939. result.Write(HttpUtility.HtmlDecode(str.Substring(old_index, index - old_index)));
  2940. result.Write("&#39;");
  2941. old_index = (index += 5);
  2942. continue;
  2943. }
  2944. }
  2945. index++; // for the &
  2946. }
  2947. if (old_index < str.Length) result.Write(HttpUtility.HtmlDecode(str.Substring(old_index)));
  2948. result.Flush();
  2949. return str_builder.ToString();
  2950. }
  2951. #endregion
  2952. #region strip_tags, nl2br
  2953. /// <summary>
  2954. /// Strips HTML and PHP tags from a string.
  2955. /// </summary>
  2956. /// <param name="str">The string to strip tags from.</param>
  2957. /// <returns>The result.</returns>
  2958. [ImplementsFunction("strip_tags")]
  2959. public static string StripTags(string str)
  2960. {
  2961. return StripTags(str, null);
  2962. }
  2963. /// <summary>
  2964. /// Strips HTML and PHP tags from a string.
  2965. /// </summary>
  2966. /// <param name="str">The string to strip tags from.</param>
  2967. /// <param name="allowableTags">Tags which should not be stripped in the following format:
  2968. /// &lt;tag1&gt;&lt;tag2&gt;&lt;tag3&gt;.</param>
  2969. /// <returns>The result.</returns>
  2970. /// <remarks>This is a slightly modified php_strip_tags which can be found in PHP sources.</remarks>
  2971. [ImplementsFunction("strip_tags")]
  2972. public static string StripTags(string str, string allowableTags)
  2973. {
  2974. int state = 0;
  2975. return StripTags(str, allowableTags, ref state);
  2976. }
  2977. /// <summary>
  2978. /// Strips tags allowing to set automaton start state and read its accepting state.
  2979. /// </summary>
  2980. internal static string StripTags(string str, string allowableTags, ref int state)
  2981. {
  2982. if (str == null) return String.Empty;
  2983. int br = 0, i = 0, depth = 0, length = str.Length;
  2984. char lc = '\0';
  2985. // Simple state machine. State 0 is the output state, State 1 means we are inside a
  2986. // normal html tag and state 2 means we are inside a php tag.
  2987. //
  2988. // lc holds the last significant character read and br is a bracket counter.
  2989. // When an allowableTags string is passed in we keep track of the string in
  2990. // state 1 and when the tag is closed check it against the allowableTags string
  2991. // to see if we should allow it.
  2992. StringBuilder result = new StringBuilder(), tagBuf = new StringBuilder();
  2993. if (allowableTags != null) allowableTags = allowableTags.ToLower();
  2994. while (i < length)
  2995. {
  2996. char c = str[i];
  2997. switch (c)
  2998. {
  2999. case '<':
  3000. if (i + 1 < length && Char.IsWhiteSpace(str[i + 1])) goto default;
  3001. if (state == 0)
  3002. {
  3003. lc = '<';
  3004. state = 1;
  3005. if (allowableTags != null)
  3006. {
  3007. tagBuf.Length = 0;
  3008. tagBuf.Append(c);
  3009. }
  3010. }
  3011. else if (state == 1) depth++;
  3012. break;
  3013. case '(':
  3014. if (state == 2)
  3015. {
  3016. if (lc != '"' && lc != '\'')
  3017. {
  3018. lc = '(';
  3019. br++;
  3020. }
  3021. }
  3022. else if (allowableTags != null && state == 1) tagBuf.Append(c);
  3023. else if (state == 0) result.Append(c);
  3024. break;
  3025. case ')':
  3026. if (state == 2)
  3027. {
  3028. if (lc != '"' && lc != '\'')
  3029. {
  3030. lc = ')';
  3031. br--;
  3032. }
  3033. }
  3034. else if (allowableTags != null && state == 1) tagBuf.Append(c);
  3035. else if (state == 0) result.Append(c);
  3036. break;
  3037. case '>':
  3038. if (depth > 0)
  3039. {
  3040. depth--;
  3041. break;
  3042. }
  3043. switch (state)
  3044. {
  3045. case 1: /* HTML/XML */
  3046. lc = '>';
  3047. state = 0;
  3048. if (allowableTags != null)
  3049. {
  3050. // find out whether this tag is allowable or not
  3051. tagBuf.Append(c);
  3052. StringBuilder normalized = new StringBuilder();
  3053. bool done = false;
  3054. int tagBufLen = tagBuf.Length, substate = 0;
  3055. // normalize the tagBuf by removing leading and trailing whitespace and turn
  3056. // any <a whatever...> into just <a> and any </tag> into <tag>
  3057. for (int j = 0; j < tagBufLen; j++)
  3058. {
  3059. char d = Char.ToLower(tagBuf[j]);
  3060. switch (d)
  3061. {
  3062. case '<':
  3063. normalized.Append(d);
  3064. break;
  3065. case '>':
  3066. done = true;
  3067. break;
  3068. default:
  3069. if (!Char.IsWhiteSpace(d))
  3070. {
  3071. if (substate == 0)
  3072. {
  3073. substate = 1;
  3074. if (d != '/') normalized.Append(d);
  3075. }
  3076. else normalized.Append(d);
  3077. }
  3078. else if (substate == 1) done = true;
  3079. break;
  3080. }
  3081. if (done) break;
  3082. }
  3083. normalized.Append('>');
  3084. if (allowableTags.IndexOf(normalized.ToString()) >= 0) result.Append(tagBuf);
  3085. tagBuf.Length = 0;
  3086. }
  3087. break;
  3088. case 2: /* PHP */
  3089. if (br == 0 && lc != '\"' && i > 0 && str[i] == '?') state = 0;
  3090. {
  3091. state = 0;
  3092. tagBuf.Length = 0;
  3093. }
  3094. break;
  3095. case 3:
  3096. state = 0;
  3097. tagBuf.Length = 0;
  3098. break;
  3099. case 4: /* JavaScript/CSS/etc... */
  3100. if (i >= 2 && str[i - 1] == '-' && str[i - 2] == '-')
  3101. {
  3102. state = 0;
  3103. tagBuf.Length = 0;
  3104. }
  3105. break;
  3106. default:
  3107. result.Append(c);
  3108. break;
  3109. }
  3110. break;
  3111. case '"':
  3112. goto case '\'';
  3113. case '\'':
  3114. if (state == 2 && i > 0 && str[i - 1] != '\\')
  3115. {
  3116. if (lc == c) lc = '\0';
  3117. else if (lc != '\\') lc = c;
  3118. }
  3119. else if (state == 0) result.Append(c);
  3120. else if (allowableTags != null && state == 1) tagBuf.Append(c);
  3121. break;
  3122. case '!':
  3123. /* JavaScript & Other HTML scripting languages */
  3124. if (state == 1 && i > 0 && str[i - 1] == '<')
  3125. {
  3126. state = 3;
  3127. lc = c;
  3128. }
  3129. else
  3130. {
  3131. if (state == 0) result.Append(c);
  3132. else if (allowableTags != null && state == 1) tagBuf.Append(c);
  3133. }
  3134. break;
  3135. case '-':
  3136. if (state == 3 && i >= 2 && str[i - 1] == '-' && str[i - 2] == '!') state = 4;
  3137. else goto default;
  3138. break;
  3139. case '?':
  3140. if (state == 1 && i > 0 && str[i - 1] == '<')
  3141. {
  3142. br = 0;
  3143. state = 2;
  3144. break;
  3145. }
  3146. goto case 'e';
  3147. case 'E':
  3148. goto case 'e';
  3149. case 'e':
  3150. /* !DOCTYPE exception */
  3151. if (state == 3 && i > 6
  3152. && Char.ToLower(str[i - 1]) == 'p' && Char.ToLower(str[i - 2]) == 'y'
  3153. && Char.ToLower(str[i - 3]) == 't' && Char.ToLower(str[i - 4]) == 'c'
  3154. && Char.ToLower(str[i - 5]) == 'o' && Char.ToLower(str[i - 6]) == 'd')
  3155. {
  3156. state = 1;
  3157. break;
  3158. }
  3159. goto case 'l';
  3160. case 'l':
  3161. /*
  3162. If we encounter '<?xml' then we shouldn't be in
  3163. state == 2 (PHP). Switch back to HTML.
  3164. */
  3165. if (state == 2 && i > 2 && str[i - 1] == 'm' && str[i - 2] == 'x')
  3166. {
  3167. state = 1;
  3168. break;
  3169. }
  3170. goto default;
  3171. /* fall-through */
  3172. default:
  3173. if (state == 0) result.Append(c);
  3174. else if (allowableTags != null && state == 1) tagBuf.Append(c);
  3175. break;
  3176. }
  3177. i++;
  3178. }
  3179. return result.ToString();
  3180. }
  3181. /// <summary>
  3182. /// Inserts HTML line breaks before all newlines in a string.
  3183. /// </summary>
  3184. /// <param name="str">The input string.</param>
  3185. /// <returns>The output string.</returns>
  3186. /// <remarks>Inserts "&lt;br/&gt;" before each "\n", "\n\r", "\r", "\r\n".</remarks>
  3187. [ImplementsFunction("nl2br")]
  3188. public static string NewLinesToBreaks(string str)
  3189. {
  3190. return NewLinesToBreaks(str, true);
  3191. }
  3192. /// <summary>
  3193. /// Inserts HTML line breaks before all newlines in a string.
  3194. /// </summary>
  3195. /// <param name="str">The input string.</param>
  3196. /// <param name="isXHTML">Whenever to use XHTML compatible line breaks or not. </param>
  3197. /// <returns>The output string.</returns>
  3198. /// <remarks>Inserts "&lt;br/&gt;" before each "\n", "\n\r", "\r", "\r\n".</remarks>
  3199. [ImplementsFunction("nl2br")]
  3200. public static string NewLinesToBreaks(string str, bool isXHTML/*=true*/ )
  3201. {
  3202. if (string.IsNullOrEmpty(str))
  3203. return String.Empty;
  3204. StringReader reader = new StringReader(str);
  3205. StringWriter writer = new StringWriter(new StringBuilder(str.Length));
  3206. NewLinesToBreaks(reader, writer, isXHTML ? "<br />" : "<br>");
  3207. return writer.ToString();
  3208. }
  3209. public static void NewLinesToBreaks(TextReader/*!*/ input, TextWriter/*!*/ output, string lineBreakString)
  3210. {
  3211. if (input == null)
  3212. throw new ArgumentNullException("input");
  3213. if (output == null)
  3214. throw new ArgumentNullException("output");
  3215. for (; ; )
  3216. {
  3217. int d = input.Read();
  3218. if (d == -1) break;
  3219. char c = (char)d;
  3220. if (c == '\r' || c == '\n')
  3221. {
  3222. output.Write(lineBreakString);
  3223. d = input.Peek();
  3224. if (d != -1)
  3225. {
  3226. char c1 = (char)d;
  3227. if ((c == '\r' && c1 == '\n') || (c == '\n' && c1 == '\r'))
  3228. {
  3229. output.Write(c);
  3230. c = c1;
  3231. input.Read();
  3232. }
  3233. }
  3234. }
  3235. output.Write(c);
  3236. }
  3237. }
  3238. #endregion
  3239. #region chunk_split
  3240. /// <summary>
  3241. /// Splits a string into chunks 76 characters long separated by "\r\n".
  3242. /// </summary>
  3243. /// <param name="str">The string to split.</param>
  3244. /// <returns>The splitted string.</returns>
  3245. /// <remarks>"\r\n" is also appended after the last chunk.</remarks>
  3246. [ImplementsFunction("chunk_split")]
  3247. [return: CastToFalse]
  3248. public static string ChunkSplit(string str)
  3249. {
  3250. return ChunkSplit(str, 76, "\r\n");
  3251. }
  3252. /// <summary>
  3253. /// Splits a string into chunks of a specified length separated by "\r\n".
  3254. /// </summary>
  3255. /// <param name="str">The string to split.</param>
  3256. /// <param name="chunkLength">The chunk length.</param>
  3257. /// <returns>The splitted string.</returns>
  3258. /// <remarks>"\r\n" is also appended after the last chunk.</remarks>
  3259. [ImplementsFunction("chunk_split")]
  3260. [return: CastToFalse]
  3261. public static string ChunkSplit(string str, int chunkLength)
  3262. {
  3263. return ChunkSplit(str, chunkLength, "\r\n");
  3264. }
  3265. /// <summary>
  3266. /// Splits a string into chunks of a specified length separated by a specified string.
  3267. /// </summary>
  3268. /// <param name="str">The string to split.</param>
  3269. /// <param name="chunkLength">The chunk length.</param>
  3270. /// <param name="endOfChunk">The chunk separator.</param>
  3271. /// <returns><paramref name="endOfChunk"/> is also appended after the last chunk.</returns>
  3272. [ImplementsFunction("chunk_split")]
  3273. [return: CastToFalse]
  3274. public static string ChunkSplit(string str, int chunkLength, string endOfChunk)
  3275. {
  3276. if (str == null) return String.Empty;
  3277. if (chunkLength <= 0)
  3278. {
  3279. PhpException.InvalidArgument("chunkLength", LibResources.GetString("arg:negative_or_zero"));
  3280. return null;
  3281. }
  3282. int length = str.Length;
  3283. StringBuilder result = new StringBuilder(length + (length / chunkLength + 1) * endOfChunk.Length);
  3284. // append the chunks one by one to the result
  3285. for (int i = 0, j = length - chunkLength; i < length; i += chunkLength)
  3286. {
  3287. if (i > j) result.Append(str, i, length - i); else result.Append(str, i, chunkLength);
  3288. result.Append(endOfChunk);
  3289. }
  3290. return result.ToString();
  3291. }
  3292. #endregion
  3293. #region soundex, metaphone, levenshtein, similar_text
  3294. /// <summary>
  3295. /// A map of following characters: {'A', 'E', 'I', 'Y', 'O', 'U', 'a', 'e', 'i', 'y', 'o', 'u'}.
  3296. /// </summary>
  3297. internal static readonly CharMap vowelsMap = new CharMap(new uint[] { 0, 0, 0x44410440, 0x44410440 });
  3298. /// <summary>
  3299. /// Indicates whether a character is recognized as an English vowel.
  3300. /// </summary>
  3301. /// <param name="c">The character.</param>
  3302. /// <returns>True iff recognized as an English vowel.</returns>
  3303. public static bool IsVowel(char c)
  3304. {
  3305. return vowelsMap.Contains(c);
  3306. }
  3307. /// <summary>
  3308. /// Calculates the soundex key of a string.
  3309. /// </summary>
  3310. /// <param name="str">The string to calculate soundex key of.</param>
  3311. /// <returns>The soundex key of <paramref name="str"/>.</returns>
  3312. [ImplementsFunction("soundex")]
  3313. public static string Soundex(string str)
  3314. {
  3315. if (str == null || str == String.Empty) return String.Empty;
  3316. int length = str.Length;
  3317. const string sound = "01230120022455012623010202";
  3318. char[] result = new char[4];
  3319. int resPos = 0;
  3320. char lastIdx = '0';
  3321. for (int i = 0; i < length; i++)
  3322. {
  3323. char c = Char.ToUpper(str[i]);
  3324. if (c >= 'A' && c <= 'Z')
  3325. {
  3326. char idx = sound[(int)(c - 'A')];
  3327. if (resPos == 0)
  3328. {
  3329. result[resPos++] = c;
  3330. lastIdx = idx;
  3331. }
  3332. else
  3333. {
  3334. if (idx != '0' && idx != lastIdx)
  3335. {
  3336. result[resPos] = idx;
  3337. if (++resPos >= 4) return new string(result);
  3338. }
  3339. // Some soundex algorithm descriptions say that the following condition should
  3340. // be in effect...
  3341. /*if (c != 'W' && c != 'H')*/
  3342. lastIdx = idx;
  3343. }
  3344. }
  3345. }
  3346. // pad with '0'
  3347. do
  3348. {
  3349. result[resPos] = '0';
  3350. }
  3351. while (++resPos < 4);
  3352. return new string(result);
  3353. }
  3354. /// <summary>
  3355. /// Calculates the metaphone key of a string.
  3356. /// </summary>
  3357. /// <param name="str">The string to calculate metaphone key of.</param>
  3358. /// <returns>The metaphone key of <paramref name="str"/>.</returns>
  3359. [ImplementsFunction("metaphone")]
  3360. public static string Metaphone(string str)
  3361. {
  3362. if (str == null) return String.Empty;
  3363. int length = str.Length;
  3364. const int padL = 4, padR = 3;
  3365. StringBuilder sb = new StringBuilder(str.Length + padL + padR);
  3366. StringBuilder result = new StringBuilder();
  3367. // avoid index out of bounds problem when looking at previous and following characters
  3368. // by padding the string at both sides
  3369. sb.Append('\0', padL);
  3370. sb.Append(str.ToUpper());
  3371. sb.Append('\0', padR);
  3372. int i = padL;
  3373. char c = sb[i];
  3374. // transformations at the beginning of the string
  3375. if ((c == 'A' && sb[i + 1] == 'E') ||
  3376. (sb[i + 1] == 'N' && (c == 'G' || c == 'K' || c == 'P')) ||
  3377. (c == 'W' && sb[i + 1] == 'R')) i++;
  3378. if (c == 'X') sb[i] = 'S';
  3379. if (c == 'W' && sb[i + 1] == 'H') sb[++i] = 'W';
  3380. // if the string starts with a vowel it is copied to output
  3381. if (IsVowel(sb[i])) result.Append(sb[i++]);
  3382. int end = length + padL;
  3383. while (i < end)
  3384. {
  3385. c = sb[i];
  3386. if (c == sb[i - 1] && c != 'C')
  3387. {
  3388. i++;
  3389. continue;
  3390. }
  3391. // transformations of consonants (vowels as well as other characters are ignored)
  3392. switch (c)
  3393. {
  3394. case 'B':
  3395. if (sb[i - 1] != 'M') result.Append('B');
  3396. break;
  3397. case 'C':
  3398. if (sb[i + 1] == 'I' || sb[i + 1] == 'E' || sb[i + 1] == 'Y')
  3399. {
  3400. if (sb[i + 2] == 'A' && sb[i + 1] == 'I') result.Append('X');
  3401. else if (sb[i - 1] == 'S') break;
  3402. else result.Append('S');
  3403. }
  3404. else if (sb[i + 1] == 'H')
  3405. {
  3406. result.Append('X');
  3407. i++;
  3408. }
  3409. else result.Append('K');
  3410. break;
  3411. case 'D':
  3412. if (sb[i + 1] == 'G' && (sb[i + 2] == 'E' || sb[i + 2] == 'Y' ||
  3413. sb[i + 2] == 'I'))
  3414. {
  3415. result.Append('J');
  3416. i++;
  3417. }
  3418. else result.Append('T');
  3419. break;
  3420. case 'F':
  3421. result.Append('F');
  3422. break;
  3423. case 'G':
  3424. if (sb[i + 1] == 'H')
  3425. {
  3426. if (sb[i - 4] == 'H' || (sb[i - 3] != 'B' && sb[i - 3] != 'D' && sb[i - 3] != 'H'))
  3427. {
  3428. result.Append('F');
  3429. i++;
  3430. }
  3431. else break;
  3432. }
  3433. else if (sb[i + 1] == 'N')
  3434. {
  3435. if (sb[i + 2] < 'A' || sb[i + 2] > 'Z' ||
  3436. (sb[i + 2] == 'E' && sb[i + 3] == 'D')) break;
  3437. else result.Append('K');
  3438. }
  3439. else if ((sb[i + 1] == 'E' || sb[i + 1] == 'I' || sb[i + 1] == 'Y') && sb[i - 1] != 'G')
  3440. {
  3441. result.Append('J');
  3442. }
  3443. else result.Append('K');
  3444. break;
  3445. case 'H':
  3446. if (IsVowel(sb[i + 1]) && sb[i - 1] != 'C' && sb[i - 1] != 'G' &&
  3447. sb[i - 1] != 'P' && sb[i - 1] != 'S' && sb[i - 1] != 'T') result.Append('H');
  3448. break;
  3449. case 'J':
  3450. result.Append('J');
  3451. break;
  3452. case 'K':
  3453. if (sb[i - 1] != 'C') result.Append('K');
  3454. break;
  3455. case 'L':
  3456. result.Append('L');
  3457. break;
  3458. case 'M':
  3459. result.Append('M');
  3460. break;
  3461. case 'N':
  3462. result.Append('N');
  3463. break;
  3464. case 'P':
  3465. if (sb[i + 1] == 'H') result.Append('F');
  3466. else result.Append('P');
  3467. break;
  3468. case 'Q':
  3469. result.Append('K');
  3470. break;
  3471. case 'R':
  3472. result.Append('R');
  3473. break;
  3474. case 'S':
  3475. if (sb[i + 1] == 'I' && (sb[i + 2] == 'O' || sb[i + 2] == 'A')) result.Append('X');
  3476. else if (sb[i + 1] == 'H')
  3477. {
  3478. result.Append('X');
  3479. i++;
  3480. }
  3481. else result.Append('S');
  3482. break;
  3483. case 'T':
  3484. if (sb[i + 1] == 'I' && (sb[i + 2] == 'O' || sb[i + 2] == 'A')) result.Append('X');
  3485. else if (sb[i + 1] == 'H')
  3486. {
  3487. result.Append('0');
  3488. i++;
  3489. }
  3490. else result.Append('T');
  3491. break;
  3492. case 'V':
  3493. result.Append('F');
  3494. break;
  3495. case 'W':
  3496. if (IsVowel(sb[i + 1])) result.Append('W');
  3497. break;
  3498. case 'X':
  3499. result.Append("KS");
  3500. break;
  3501. case 'Y':
  3502. if (IsVowel(sb[i + 1])) result.Append('Y');
  3503. break;
  3504. case 'Z':
  3505. result.Append('S');
  3506. break;
  3507. }
  3508. i++;
  3509. }
  3510. return result.ToString();
  3511. }
  3512. /// <summary>
  3513. /// Calculates the Levenshtein distance between two strings.
  3514. /// </summary>
  3515. /// <param name="src">The first string.</param>
  3516. /// <param name="dst">The second string.</param>
  3517. /// <returns>The Levenshtein distance between <paramref name="src"/> and <paramref name="dst"/> or -1 if any of the
  3518. /// strings is longer than 255 characters.</returns>
  3519. [ImplementsFunction("levenshtein")]
  3520. public static int Levenshtein(string src, string dst)
  3521. {
  3522. return Levenshtein(src, dst, 1, 1, 1);
  3523. }
  3524. /// <summary>
  3525. /// Calculates the Levenshtein distance between two strings given the cost of insert, replace
  3526. /// and delete operations.
  3527. /// </summary>
  3528. /// <param name="src">The first string.</param>
  3529. /// <param name="dst">The second string.</param>
  3530. /// <param name="insertCost">Cost of the insert operation.</param>
  3531. /// <param name="replaceCost">Cost of the replace operation.</param>
  3532. /// <param name="deleteCost">Cost of the delete operation.</param>
  3533. /// <returns>The Levenshtein distance between <paramref name="src"/> and <paramref name="dst"/> or -1 if any of the
  3534. /// strings is longer than 255 characters.</returns>
  3535. /// <remarks>See <A href="http://www.merriampark.com/ld.htm">http://www.merriampark.com/ld.htm</A> for description of the algorithm.</remarks>
  3536. [ImplementsFunction("levenshtein")]
  3537. public static int Levenshtein(string src, string dst, int insertCost, int replaceCost, int deleteCost)
  3538. {
  3539. if (src == null) src = String.Empty;
  3540. if (dst == null) dst = String.Empty;
  3541. int n = src.Length;
  3542. int m = dst.Length;
  3543. if (n > 255 || m > 255) return -1;
  3544. if (n == 0) return m * insertCost;
  3545. if (m == 0) return n * deleteCost;
  3546. int[,] matrix = new int[n + 1, m + 1];
  3547. for (int i = 0; i <= n; i++) matrix[i, 0] = i * deleteCost;
  3548. for (int j = 0; j <= m; j++) matrix[0, j] = j * insertCost;
  3549. for (int i = 1; i <= n; i++)
  3550. {
  3551. char cs = src[i - 1];
  3552. for (int j = 1; j <= m; j++)
  3553. {
  3554. char cd = dst[j - 1];
  3555. matrix[i, j] = System.Math.Min(System.Math.Min(
  3556. matrix[i - 1, j] + deleteCost,
  3557. matrix[i, j - 1] + insertCost),
  3558. matrix[i - 1, j - 1] + (cs == cd ? 0 : replaceCost));
  3559. }
  3560. }
  3561. return matrix[n, m];
  3562. }
  3563. /// <summary>
  3564. /// Calculates the similarity between two strings. Internal recursive function.
  3565. /// </summary>
  3566. /// <param name="first">The first string.</param>
  3567. /// <param name="second">The second string.</param>
  3568. /// <returns>The number of matching characters in both strings.</returns>
  3569. /// <remarks>Algorithm description is supposed to be found
  3570. /// <A href="http://citeseer.nj.nec.com/oliver93decision.html">here</A>.</remarks>
  3571. internal static int SimilarTextInternal(string first, string second)
  3572. {
  3573. Debug.Assert(first != null && second != null);
  3574. int posF = 0, lengthF = first.Length;
  3575. int posS = 0, lengthS = second.Length;
  3576. int maxK = 0;
  3577. for (int i = 0; i < lengthF; i++)
  3578. {
  3579. for (int j = 0; j < lengthS; j++)
  3580. {
  3581. int k;
  3582. for (k = 0; i + k < lengthF && j + k < lengthS && first[i + k] == second[j + k]; k++) ;
  3583. if (k > maxK)
  3584. {
  3585. maxK = k;
  3586. posF = i;
  3587. posS = j;
  3588. }
  3589. }
  3590. }
  3591. int sum = maxK;
  3592. if (sum > 0)
  3593. {
  3594. if (posF > 0 && posS > 0)
  3595. {
  3596. sum += SimilarTextInternal(first.Substring(0, posF), second.Substring(0, posS));
  3597. }
  3598. if (posF + maxK < lengthF && posS + maxK < lengthS)
  3599. {
  3600. sum += SimilarTextInternal(first.Substring(posF + maxK), second.Substring(posS + maxK));
  3601. }
  3602. }
  3603. return sum;
  3604. }
  3605. /// <summary>
  3606. /// Calculates the similarity between two strings.
  3607. /// </summary>
  3608. /// <param name="first">The first string.</param>
  3609. /// <param name="second">The second string.</param>
  3610. /// <returns>The number of matching characters in both strings.</returns>
  3611. [ImplementsFunction("similar_text")]
  3612. public static int SimilarText(string first, string second)
  3613. {
  3614. if (first == null || second == null) return 0;
  3615. return SimilarTextInternal(first, second);
  3616. }
  3617. /// <summary>
  3618. /// Calculates the similarity between two strings.
  3619. /// </summary>
  3620. /// <param name="first">The first string.</param>
  3621. /// <param name="second">The second string.</param>
  3622. /// <param name="percent">Will become the similarity in percent.</param>
  3623. /// <returns>The number of matching characters in both strings.</returns>
  3624. [ImplementsFunction("similar_text")]
  3625. public static int SimilarText(string first, string second, out double percent)
  3626. {
  3627. if (first == null || second == null) { percent = 0; return 0; }
  3628. int sum = SimilarTextInternal(first, second);
  3629. percent = (200.0 * sum) / (first.Length + second.Length);
  3630. return sum;
  3631. }
  3632. #endregion
  3633. #region strtok
  3634. /// <summary>
  3635. /// Holds a context of <see cref="Tokenize"/> method.
  3636. /// </summary>
  3637. private class TokenizerContext
  3638. {
  3639. /// <summary>
  3640. /// The <b>str</b> parameter of last <see cref="Tokenize"/> method call.
  3641. /// </summary>
  3642. public string String;
  3643. /// <summary>
  3644. /// Current position in <see cref="TokenizerContext"/>.
  3645. /// </summary>
  3646. public int Position;
  3647. /// <summary>
  3648. /// The length of <see cref="TokenizerContext"/>.
  3649. /// </summary>
  3650. public int Length;
  3651. /// <summary>
  3652. /// A context associated with the current thread.
  3653. /// </summary>
  3654. public static TokenizerContext CurrentContext
  3655. {
  3656. get
  3657. {
  3658. if (currentContext == null) currentContext = new TokenizerContext();
  3659. return currentContext;
  3660. }
  3661. }
  3662. #if !SILVERLIGHT
  3663. [ThreadStatic]
  3664. #endif
  3665. private static TokenizerContext currentContext;
  3666. /// <summary>
  3667. /// Clears thread static field. Called on request end.
  3668. /// </summary>
  3669. public static void Clear()
  3670. {
  3671. currentContext = null;
  3672. }
  3673. /// <summary>
  3674. /// Registeres <see cref="Clear"/> called on request end.
  3675. /// </summary>
  3676. static TokenizerContext()
  3677. {
  3678. RequestContext.RequestEnd += new Action(Clear);
  3679. }
  3680. }
  3681. /// <summary>
  3682. /// Splits a string into tokens using given set of delimiter characters. Tokenizes the string
  3683. /// that was passed to a previous call of the two-parameter version.
  3684. /// </summary>
  3685. /// <param name="delimiters">Set of delimiters.</param>
  3686. /// <returns>The next token or a <B>null</B> reference.</returns>
  3687. /// <remarks>This method implements the behavior introduced with PHP 4.1.0, i.e. empty tokens are
  3688. /// skipped and never returned.</remarks>
  3689. [ImplementsFunction("strtok")]
  3690. [return: CastToFalse]
  3691. public static string Tokenize(string delimiters)
  3692. {
  3693. TokenizerContext context = TokenizerContext.CurrentContext;
  3694. if (context.Position >= context.Length) return null;
  3695. if (delimiters == null) delimiters = String.Empty;
  3696. int index;
  3697. char[] delChars = delimiters.ToCharArray();
  3698. while ((index = context.String.IndexOfAny(delChars, context.Position)) == context.Position)
  3699. {
  3700. if (context.Position == context.Length - 1) return null; // last char is delimiter
  3701. context.Position++;
  3702. }
  3703. string token;
  3704. if (index == -1) // delimiter not found
  3705. {
  3706. token = context.String.Substring(context.Position);
  3707. context.Position = context.Length;
  3708. return token;
  3709. }
  3710. token = context.String.Substring(context.Position, index - context.Position);
  3711. context.Position = index + 1;
  3712. return token;
  3713. }
  3714. /// <summary>
  3715. /// Splits a string into tokens using given set of delimiter characters.
  3716. /// </summary>
  3717. /// <param name="str">The string to tokenize.</param>
  3718. /// <param name="delimiters">Set of delimiters.</param>
  3719. /// <returns>The first token or null. Call one-parameter version of this method to get next tokens.
  3720. /// </returns>
  3721. /// <remarks>This method implements the behavior introduced with PHP 4.1.0, i.e. empty tokens are
  3722. /// skipped and never returned.</remarks>
  3723. [ImplementsFunction("strtok")]
  3724. [return: CastToFalse]
  3725. public static string Tokenize(string str, string delimiters)
  3726. {
  3727. if (str == null) str = String.Empty;
  3728. TokenizerContext context = TokenizerContext.CurrentContext;
  3729. context.String = str;
  3730. context.Length = str.Length;
  3731. context.Position = 0;
  3732. return Tokenize(delimiters);
  3733. }
  3734. #endregion
  3735. #region trim, rtrim, ltrim, chop
  3736. /// <summary>
  3737. /// Strips whitespace characters from the beginning and end of a string.
  3738. /// </summary>
  3739. /// <param name="str">The string to trim.</param>
  3740. /// <returns>The trimmed string.</returns>
  3741. /// <remarks>This one-parameter version trims '\0', '\t', '\n', '\r', '\x0b' and ' ' (space).</remarks>
  3742. [ImplementsFunction("trim")]
  3743. public static string Trim(string str)
  3744. {
  3745. return Trim(str, "\0\t\n\r\x0b\x20");
  3746. }
  3747. /// <summary>
  3748. /// Strips given characters from the beginning and end of a string.
  3749. /// </summary>
  3750. /// <param name="str">The string to trim.</param>
  3751. /// <param name="whiteSpaceCharacters">The characters to strip from <paramref name="str"/>. Can contain ranges
  3752. /// of characters, e.g. "\0x00..\0x1F".</param>
  3753. /// <returns>The trimmed string.</returns>
  3754. /// <exception cref="PhpException"><paramref name="whiteSpaceCharacters"/> is invalid char mask. Multiple errors may be printed out.</exception>
  3755. /// <exception cref="PhpException"><paramref name="str"/> contains Unicode characters greater than '\u0800'.</exception>
  3756. [ImplementsFunction("trim")]
  3757. public static string Trim(string str, string whiteSpaceCharacters)
  3758. {
  3759. if (str == null) return String.Empty;
  3760. // As whiteSpaceCharacters may contain intervals, I see two possible implementations:
  3761. // 1) Call CharMap.AddUsingMask and do the trimming "by hand".
  3762. // 2) Write another version of CharMap.AddUsingMask that would return char[] of characters
  3763. // that fit the mask, and do the trimming with String.Trim(char[]).
  3764. // I have chosen 1).
  3765. CharMap charmap = InitializeCharMap();
  3766. // may throw an exception:
  3767. try
  3768. {
  3769. charmap.AddUsingMask(whiteSpaceCharacters);
  3770. }
  3771. catch (IndexOutOfRangeException)
  3772. {
  3773. PhpException.Throw(PhpError.Warning, LibResources.GetString("unicode_characters"));
  3774. return null;
  3775. }
  3776. int length = str.Length, i = 0, j = length - 1;
  3777. // finds the new beginning:
  3778. while (i < length && charmap.Contains(str[i])) i++;
  3779. // finds the new end:
  3780. while (j >= 0 && charmap.Contains(str[j])) j--;
  3781. return (i <= j) ? str.Substring(i, j - i + 1) : String.Empty;
  3782. }
  3783. /// <summary>Characters treated as blanks by the PHP.</summary>
  3784. private static char[] phpBlanks = new char[] { '\0', '\t', '\n', '\r', '\u000b', ' ' };
  3785. /// <summary>
  3786. /// Strips whitespace characters from the beginning of a string.
  3787. /// </summary>
  3788. /// <param name="str">The string to trim.</param>
  3789. /// <returns>The trimmed string.</returns>
  3790. /// <remarks>This one-parameter version trims '\0', '\t', '\n', '\r', '\u000b' and ' ' (space).</remarks>
  3791. [ImplementsFunction("ltrim")]
  3792. public static string TrimStart(string str)
  3793. {
  3794. return (str != null) ? str.TrimStart(phpBlanks) : String.Empty;
  3795. }
  3796. /// <summary>
  3797. /// Strips given characters from the beginning of a string.
  3798. /// </summary>
  3799. /// <param name="str">The string to trim.</param>
  3800. /// <param name="whiteSpaceCharacters">The characters to strip from <paramref name="str"/>. Can contain ranges
  3801. /// of characters, e.g. \0x00..\0x1F.</param>
  3802. /// <returns>The trimmed string.</returns>
  3803. /// <exception cref="PhpException"><paramref name="whiteSpaceCharacters"/> is invalid char mask. Multiple errors may be printed out.</exception>
  3804. /// <exception cref="PhpException"><paramref name="whiteSpaceCharacters"/> contains Unicode characters greater than '\u0800'.</exception>
  3805. [ImplementsFunction("ltrim")]
  3806. public static string TrimStart(string str, string whiteSpaceCharacters)
  3807. {
  3808. if (str == null) return String.Empty;
  3809. CharMap charmap = InitializeCharMap();
  3810. // may throw an exception:
  3811. try
  3812. {
  3813. charmap.AddUsingMask(whiteSpaceCharacters);
  3814. }
  3815. catch (IndexOutOfRangeException)
  3816. {
  3817. PhpException.Throw(PhpError.Warning, LibResources.GetString("unicode_characters"));
  3818. return null;
  3819. }
  3820. int length = str.Length, i = 0;
  3821. while (i < length && charmap.Contains(str[i])) i++;
  3822. if (i < length) return str.Substring(i);
  3823. return String.Empty;
  3824. }
  3825. /// <summary>
  3826. /// Strips whitespace characters from the end of a string.
  3827. /// </summary>
  3828. /// <param name="str">The string to trim.</param>
  3829. /// <returns>The trimmed string.</returns>
  3830. /// <remarks>This one-parameter version trims '\0', '\t', '\n', '\r', '\u000b' and ' ' (space).</remarks>
  3831. [ImplementsFunction("rtrim")]
  3832. public static string TrimEnd(string str)
  3833. {
  3834. return (str != null) ? str.TrimEnd(phpBlanks) : String.Empty;
  3835. }
  3836. /// <summary>
  3837. /// Strips given characters from the end of a string.
  3838. /// </summary>
  3839. /// <param name="str">The string to trim.</param>
  3840. /// <param name="whiteSpaceCharacters">The characters to strip from <paramref name="str"/>. Can contain ranges
  3841. /// of characters, e.g. \0x00..\0x1F.</param>
  3842. /// <returns>The trimmed string.</returns>
  3843. /// <exception cref="PhpException"><paramref name="whiteSpaceCharacters"/> is invalid char mask. Multiple errors may be printed out.</exception>
  3844. /// <exception cref="PhpException"><paramref name="whiteSpaceCharacters"/> contains Unicode characters greater than '\u0800'.</exception>
  3845. [ImplementsFunction("rtrim")]
  3846. public static string TrimEnd(string str, string whiteSpaceCharacters)
  3847. {
  3848. if (str == null) return String.Empty;
  3849. CharMap charmap = InitializeCharMap();
  3850. try
  3851. {
  3852. charmap.AddUsingMask(whiteSpaceCharacters);
  3853. }
  3854. catch (IndexOutOfRangeException)
  3855. {
  3856. PhpException.Throw(PhpError.Warning, LibResources.GetString("unicode_characters"));
  3857. return null;
  3858. }
  3859. int j = str.Length - 1;
  3860. while (j >= 0 && charmap.Contains(str[j])) j--;
  3861. return (j >= 0) ? str.Substring(0, j + 1) : String.Empty;
  3862. }
  3863. /// <summary>
  3864. /// Strips whitespace characters from the end of a string.
  3865. /// </summary>
  3866. /// <param name="str">The string to trim.</param>
  3867. /// <returns>The trimmed string.</returns>
  3868. /// <remarks>This one-parameter version trims '\0', '\t', '\n', '\r', '\u000b' and ' ' (space).</remarks>
  3869. [ImplementsFunction("chop")]
  3870. public static string Chop(string str)
  3871. {
  3872. return TrimEnd(str);
  3873. }
  3874. /// <summary>
  3875. /// Strips given characters from the end of a string.
  3876. /// </summary>
  3877. /// <param name="str">The string to trim.</param>
  3878. /// <param name="whiteSpaceCharacters">The characters to strip from <paramref name="str"/>. Can contain ranges
  3879. /// of characters, e.g. \0x00..\0x1F.</param>
  3880. /// <returns>The trimmed string.</returns>
  3881. /// <exception cref="PhpException">Thrown if <paramref name="whiteSpaceCharacters"/> is invalid char mask. Multiple errors may be printed out.</exception>
  3882. [ImplementsFunction("chop")]
  3883. public static string Chop(string str, string whiteSpaceCharacters)
  3884. {
  3885. return TrimEnd(str, whiteSpaceCharacters);
  3886. }
  3887. #endregion
  3888. #region ucfirst, lcfirst, ucwords
  3889. /// <summary>
  3890. /// Makes a string's first character uppercase.
  3891. /// </summary>
  3892. /// <param name="str">The input string.</param>
  3893. /// <returns><paramref name="str"/> with the first character converted to uppercase.</returns>
  3894. [ImplementsFunction("ucfirst")]
  3895. public static string UpperCaseFirst(string str)
  3896. {
  3897. if (string.IsNullOrEmpty(str))
  3898. return string.Empty;
  3899. return Char.ToUpper(str[0]) + str.Substring(1);
  3900. }
  3901. /// <summary>
  3902. /// Returns a string with the first character of str , lowercased if that character is alphabetic.
  3903. /// Note that 'alphabetic' is determined by the current locale. For instance, in the default "C" locale characters such as umlaut-a (ä) will not be converted.
  3904. /// </summary>
  3905. /// <param name="str">The input string.</param>
  3906. /// <returns>Returns the resulting string.</returns>
  3907. [ImplementsFunction("lcfirst")]
  3908. public static string LowerCaseFirst(string str)
  3909. {
  3910. if (string.IsNullOrEmpty(str))
  3911. return string.Empty;
  3912. // first character to lower case
  3913. return Char.ToLower(str[0]) + str.Substring(1);
  3914. }
  3915. /// <summary>
  3916. /// Makes the first character of each word in a string uppercase.
  3917. /// </summary>
  3918. /// <param name="str">The input string.</param>
  3919. /// <returns><paramref name="str"/> with the first character of each word in a string converted to
  3920. /// uppercase.</returns>
  3921. [ImplementsFunction("ucwords")]
  3922. public static string UpperCaseWords(string str)
  3923. {
  3924. if (str == null) return String.Empty;
  3925. int length = str.Length;
  3926. StringBuilder result = new StringBuilder(str);
  3927. bool state = true;
  3928. for (int i = 0; i < length; i++)
  3929. {
  3930. if (Char.IsWhiteSpace(result[i])) state = true;
  3931. else
  3932. {
  3933. if (state)
  3934. {
  3935. result[i] = Char.ToUpper(result[i]);
  3936. state = false;
  3937. }
  3938. }
  3939. }
  3940. return result.ToString();
  3941. }
  3942. #endregion
  3943. #region sprintf, vsprintf
  3944. /// <summary>
  3945. /// Default number of decimals when formatting floating-point numbers (%f in printf).
  3946. /// </summary>
  3947. internal const int printfFloatPrecision = 6;
  3948. /// <summary>
  3949. /// Returns a formatted string.
  3950. /// </summary>
  3951. /// <param name="format">The format string.
  3952. /// See <A href="http://www.php.net/manual/en/function.sprintf.php">PHP manual</A> for details.
  3953. /// Besides, a type specifier "%C" is applicable. It converts an integer value to Unicode character.</param>
  3954. /// <param name="arguments">The arguments.</param>
  3955. /// <returns>The formatted string or null if there is too few arguments.</returns>
  3956. /// <remarks>Assumes that either <paramref name="format"/> nor <paramref name="arguments"/> is null.</remarks>
  3957. internal static string FormatInternal(string format, object[] arguments)
  3958. {
  3959. Debug.Assert(format != null && arguments != null);
  3960. Encoding encoding = Configuration.Application.Globalization.PageEncoding;
  3961. StringBuilder result = new StringBuilder();
  3962. int state = 0, width = 0, precision = -1, seqIndex = 0, swapIndex = -1;
  3963. bool leftAlign = false;
  3964. bool plusSign = false;
  3965. char padChar = ' ';
  3966. // process the format string using a 6-state finite automaton
  3967. int length = format.Length;
  3968. for (int i = 0; i < length; i++)
  3969. {
  3970. char c = format[i];
  3971. Lambda:
  3972. switch (state)
  3973. {
  3974. case 0: // the initial state
  3975. {
  3976. if (c == '%')
  3977. {
  3978. width = 0;
  3979. precision = -1;
  3980. swapIndex = -1;
  3981. leftAlign = false;
  3982. plusSign = false;
  3983. padChar = ' ';
  3984. state = 1;
  3985. }
  3986. else result.Append(c);
  3987. break;
  3988. }
  3989. case 1: // % character encountered, expecting format
  3990. {
  3991. switch (c)
  3992. {
  3993. case '-': leftAlign = true; break;
  3994. case '+': plusSign = true; break;
  3995. case ' ': padChar = ' '; break;
  3996. case '\'': state = 2; break;
  3997. case '.': state = 4; break;
  3998. case '%': result.Append(c); state = 0; break;
  3999. case '0': padChar = '0'; state = 3; break;
  4000. default:
  4001. {
  4002. if (Char.IsDigit(c)) state = 3;
  4003. else state = 5;
  4004. goto Lambda;
  4005. }
  4006. }
  4007. break;
  4008. }
  4009. case 2: // ' character encountered, expecting padding character
  4010. {
  4011. padChar = c;
  4012. state = 1;
  4013. break;
  4014. }
  4015. case 3: // number encountered, expecting width or argument number
  4016. {
  4017. switch (c)
  4018. {
  4019. case '$':
  4020. {
  4021. swapIndex = width;
  4022. if (swapIndex == 0)
  4023. {
  4024. PhpException.Throw(PhpError.Warning, LibResources.GetString("zero_argument_invalid"));
  4025. return result.ToString();
  4026. }
  4027. width = 0;
  4028. state = 1;
  4029. break;
  4030. }
  4031. case '.':
  4032. {
  4033. state = 4;
  4034. break;
  4035. }
  4036. default:
  4037. {
  4038. if (Char.IsDigit(c)) width = width * 10 + (int)Char.GetNumericValue(c);
  4039. else
  4040. {
  4041. state = 5;
  4042. goto Lambda;
  4043. }
  4044. break;
  4045. }
  4046. }
  4047. break;
  4048. }
  4049. case 4: // number after . encountered, expecting precision
  4050. {
  4051. if (precision == -1) precision = 0;
  4052. if (Char.IsDigit(c)) precision = precision * 10 + (int)Char.GetNumericValue(c);
  4053. else
  4054. {
  4055. state = 5;
  4056. goto case 5;
  4057. }
  4058. break;
  4059. }
  4060. case 5: // expecting type specifier
  4061. {
  4062. int index = (swapIndex <= 0 ? seqIndex++ : swapIndex - 1);
  4063. if (index >= arguments.Length)
  4064. {
  4065. // few arguments:
  4066. return null;
  4067. }
  4068. object obj = arguments[index];
  4069. string app = null;
  4070. char sign = '\0';
  4071. switch (c)
  4072. {
  4073. case 'b': // treat as integer, present as binary number without a sign
  4074. app = System.Convert.ToString(Core.Convert.ObjectToInteger(obj), 2);
  4075. break;
  4076. case 'c': // treat as integer, present as character
  4077. app = encoding.GetString(new byte[] { unchecked((byte)Core.Convert.ObjectToInteger(obj)) }, 0, 1);
  4078. break;
  4079. case 'C': // treat as integer, present as Unicode character
  4080. app = new String(unchecked((char)Core.Convert.ObjectToInteger(obj)), 1);
  4081. break;
  4082. case 'd': // treat as integer, present as signed decimal number
  4083. {
  4084. // use long to prevent overflow in Math.Abs:
  4085. long ivalue = Core.Convert.ObjectToInteger(obj);
  4086. if (ivalue < 0) sign = '-'; else if (ivalue >= 0 && plusSign) sign = '+';
  4087. app = Math.Abs((long)ivalue).ToString();
  4088. break;
  4089. }
  4090. case 'u': // treat as integer, present as unsigned decimal number, without sign
  4091. app = unchecked((uint)Core.Convert.ObjectToInteger(obj)).ToString();
  4092. break;
  4093. case 'e':
  4094. {
  4095. double dvalue = Core.Convert.ObjectToDouble(obj);
  4096. if (dvalue < 0) sign = '-'; else if (dvalue >= 0 && plusSign) sign = '+';
  4097. string f = String.Concat("0.", new String('0', precision == -1 ? printfFloatPrecision : precision), "e+0");
  4098. app = Math.Abs(dvalue).ToString(f);
  4099. break;
  4100. }
  4101. case 'f': // treat as float, present locale-aware floating point number
  4102. {
  4103. double dvalue = Core.Convert.ObjectToDouble(obj);
  4104. if (dvalue < 0) sign = '-'; else if (dvalue >= 0 && plusSign) sign = '+';
  4105. app = Math.Abs(dvalue).ToString("F" + (precision == -1 ? printfFloatPrecision : precision));
  4106. break;
  4107. }
  4108. case 'F': // treat as float, present locale-unaware floating point number with '.' decimal separator (PHP 5.0.3+ feature)
  4109. {
  4110. double dvalue = Core.Convert.ObjectToDouble(obj);
  4111. if (dvalue < 0) sign = '-'; else if (dvalue >= 0 && plusSign) sign = '+';
  4112. app = Math.Abs(dvalue).ToString("F" + (precision == -1 ? printfFloatPrecision : precision),
  4113. System.Globalization.NumberFormatInfo.InvariantInfo);
  4114. break;
  4115. }
  4116. case 'o': // treat as integer, present as octal number without sign
  4117. app = System.Convert.ToString(Core.Convert.ObjectToInteger(obj), 8);
  4118. break;
  4119. case 'x': // treat as integer, present as hex number (lower case) without sign
  4120. app = Core.Convert.ObjectToInteger(obj).ToString("x");
  4121. break;
  4122. case 'X': // treat as integer, present as hex number (upper case) without sign
  4123. app = Core.Convert.ObjectToInteger(obj).ToString("X");
  4124. break;
  4125. case 's': // treat as string, present as string
  4126. {
  4127. if (obj != null)
  4128. {
  4129. app = Core.Convert.ObjectToString(obj);
  4130. // undocumented feature:
  4131. if (precision != -1) app = app.Substring(0, Math.Min(precision, app.Length));
  4132. }
  4133. break;
  4134. }
  4135. }
  4136. if (app != null)
  4137. {
  4138. // pad:
  4139. if (leftAlign)
  4140. {
  4141. if (sign != '\0') result.Append(sign);
  4142. result.Append(app);
  4143. for (int j = width - app.Length; j > ((sign != '\0') ? 1 : 0); j--)
  4144. result.Append(padChar);
  4145. }
  4146. else
  4147. {
  4148. if (sign != '\0' && padChar == '0')
  4149. result.Append(sign);
  4150. for (int j = width - app.Length; j > ((sign != '\0') ? 1 : 0); j--)
  4151. result.Append(padChar);
  4152. if (sign != '\0' && padChar != '0')
  4153. result.Append(sign);
  4154. result.Append(app);
  4155. }
  4156. }
  4157. state = 0;
  4158. break;
  4159. }
  4160. }
  4161. }
  4162. return result.ToString();
  4163. }
  4164. /// <summary>
  4165. /// Returns a formatted string.
  4166. /// </summary>
  4167. /// <param name="format">The format string. For details, see PHP manual.</param>
  4168. /// <param name="arguments">The arguments.
  4169. /// See <A href="http://www.php.net/manual/en/function.sprintf.php">PHP manual</A> for details.
  4170. /// Besides, a type specifier "%C" is applicable. It converts an integer value to Unicode character.</param>
  4171. /// <returns>The formatted string.</returns>
  4172. /// <exception cref="ArgumentNullException">Thrown when the <paramref name="arguments"/> parameter is null.</exception>
  4173. /// <exception cref="PhpException">Thrown when there is less arguments than expeceted by formatting string.</exception>
  4174. [ImplementsFunction("sprintf")]
  4175. [return: CastToFalse]
  4176. public static string Format(string format, params object[] arguments)
  4177. {
  4178. if (format == null) return String.Empty;
  4179. // null arguments would be compiler's error (or error of the user):
  4180. if (arguments == null) throw new ArgumentNullException("arguments");
  4181. string result = FormatInternal(format, arguments);
  4182. if (result == null)
  4183. PhpException.Throw(PhpError.Warning, LibResources.GetString("too_few_arguments"));
  4184. return result;
  4185. }
  4186. /// <summary>
  4187. /// Returns a formatted string.
  4188. /// </summary>
  4189. /// <param name="format">The format string. For details, see PHP manual.</param>
  4190. /// <param name="arguments">The arguments.</param>
  4191. /// <returns>The formatted string.</returns>
  4192. /// <exception cref="PhpException">Thrown when there is less arguments than expeceted by formatting string.</exception>
  4193. [ImplementsFunction("vsprintf")]
  4194. [return: CastToFalse]
  4195. public static string Format(string format, PhpArray arguments)
  4196. {
  4197. if (format == null) return String.Empty;
  4198. object[] array;
  4199. if (arguments != null)
  4200. {
  4201. array = new object[arguments.Count];
  4202. arguments.Values.CopyTo(array, 0);
  4203. }
  4204. else
  4205. array = ArrayUtils.EmptyObjects;
  4206. string result = FormatInternal(format, array);
  4207. if (result == null)
  4208. PhpException.Throw(PhpError.Warning, LibResources.GetString("too_few_arguments"));
  4209. return result;
  4210. }
  4211. #endregion
  4212. #region sscanf
  4213. /// <summary>
  4214. /// Parses input from a string according to a format.
  4215. /// </summary>
  4216. /// <param name="str">The string to be parsed.</param>
  4217. /// <param name="format">The format. See <c>sscanf</c> C function for details.</param>
  4218. /// <param name="arg">A PHP reference which value is set to the first parsed value.</param>
  4219. /// <param name="arguments">PHP references which values are set to the next parsed values.</param>
  4220. /// <returns>The number of parsed values.</returns>
  4221. /// <remarks><seealso cref="ParseString"/>.</remarks>
  4222. [ImplementsFunction("sscanf")]
  4223. public static int ScanFormat(string str, string format, PhpReference arg, params PhpReference[] arguments)
  4224. {
  4225. if (arg == null)
  4226. throw new ArgumentNullException("arg");
  4227. if (arguments == null)
  4228. throw new ArgumentNullException("arguments");
  4229. // assumes capacity same as the number of arguments:
  4230. ArrayList result = new ArrayList(arguments.Length + 1);
  4231. // parses string and fills the result with parsed values:
  4232. ParseString(str, format, result);
  4233. // the number of specifiers differs from the number of arguments:
  4234. if (result.Count != arguments.Length + 1)
  4235. {
  4236. PhpException.Throw(PhpError.Warning, LibResources.GetString("different_variables_and_specifiers", arguments.Length + 1, result.Count));
  4237. return -1;
  4238. }
  4239. // the number of non-null parsed values:
  4240. int count = 0;
  4241. if (result[0] != null)
  4242. {
  4243. arg.Value = result[0];
  4244. count = 1;
  4245. }
  4246. for (int i = 0; i < arguments.Length; i++)
  4247. {
  4248. if (arguments[i] != null && result[i + 1] != null)
  4249. {
  4250. arguments[i].Value = result[i + 1];
  4251. count++;
  4252. }
  4253. }
  4254. return count;
  4255. }
  4256. /// <summary>
  4257. /// Parses input from a string according to a format.
  4258. /// </summary>
  4259. /// <param name="str">The string to be parsed.</param>
  4260. /// <param name="format">The format. See <c>sscanf</c> C function for details.</param>
  4261. /// <returns>A new instance of <see cref="PhpArray"/> containing parsed values indexed by integers starting from 0.</returns>
  4262. /// <remarks><seealso cref="ParseString"/>.</remarks>
  4263. [ImplementsFunction("sscanf")]
  4264. public static PhpArray ScanFormat(string str, string format)
  4265. {
  4266. return (PhpArray)ParseString(str, format, new PhpArray());
  4267. }
  4268. /// <summary>
  4269. /// Parses a string according to a specified format.
  4270. /// </summary>
  4271. /// <param name="str">The string to be parsed.</param>
  4272. /// <param name="format">The format. See <c>sscanf</c> C function for details.</param>
  4273. /// <param name="result">A list which to fill with results.</param>
  4274. /// <returns><paramref name="result"/> for convenience.</returns>
  4275. /// <exception cref="ArgumentNullException"><paramref name="result"/> is a <B>null</B> reference.</exception>
  4276. /// <exception cref="PhpException">Invalid formatting specifier.</exception>
  4277. public static IList ParseString(string str, string format, IList result)
  4278. {
  4279. if (result == null)
  4280. throw new ArgumentNullException("result");
  4281. if (str == null || format == null)
  4282. return result;
  4283. int s = 0, f = 0;
  4284. while (f < format.Length)
  4285. {
  4286. char c = format[f++];
  4287. if (c == '%')
  4288. {
  4289. if (f == format.Length) break;
  4290. int width; // max. parsed characters
  4291. bool store; // whether to store parsed item to the result
  4292. // checks for asterisk which means matching value is not stored:
  4293. if (format[f] == '*')
  4294. {
  4295. f++;
  4296. if (f == format.Length) break;
  4297. store = false;
  4298. }
  4299. else
  4300. {
  4301. store = true;
  4302. }
  4303. // parses width (a sequence of digits without sign):
  4304. if (format[f] >= '0' && format[f] <= '9')
  4305. {
  4306. width = (int)Core.Convert.SubstringToLongStrict(format, -1, 10, Int32.MaxValue, ref f);
  4307. if (width == 0) width = Int32.MaxValue;
  4308. // format string ends with "%number"
  4309. if (f == format.Length)
  4310. {
  4311. PhpException.Throw(PhpError.Warning, LibResources.GetString("invalid_scan_conversion_character", "null"));
  4312. return null;
  4313. }
  4314. }
  4315. else
  4316. {
  4317. width = Int32.MaxValue;
  4318. }
  4319. // adds null if string parsing has been finished:
  4320. if (s == str.Length)
  4321. {
  4322. if (store)
  4323. {
  4324. if (format[f] == 'n')
  4325. result.Add(s);
  4326. else if (format[f] != '%')
  4327. result.Add(null);
  4328. }
  4329. continue;
  4330. }
  4331. // parses the string according to the format specifier:
  4332. object item = ParseSubstring(format[f], width, str, ref s);
  4333. // unknown specifier:
  4334. if (item == null)
  4335. {
  4336. if (format[f] == '%')
  4337. {
  4338. // stops string parsing if characters don't match:
  4339. if (str[s++] != '%') s = str.Length;
  4340. }
  4341. else if (format[f] == '[')
  4342. {
  4343. bool complement;
  4344. CharMap charmap = ParseRangeSpecifier(format, ref f, out complement);
  4345. if (charmap != null)
  4346. {
  4347. int start = s;
  4348. // skip characters contained in the specifier:
  4349. if (complement)
  4350. {
  4351. while (s < str.Length && !charmap.Contains(str[s])) s++;
  4352. }
  4353. else
  4354. {
  4355. while (s < str.Length && charmap.Contains(str[s])) s++;
  4356. }
  4357. item = str.Substring(start, s - start);
  4358. }
  4359. else
  4360. {
  4361. PhpException.Throw(PhpError.Warning, LibResources.GetString("unmatched_separator"));
  4362. return null;
  4363. }
  4364. }
  4365. else
  4366. {
  4367. PhpException.Throw(PhpError.Warning, LibResources.GetString("invalid_scan_conversion_character", c));
  4368. return null;
  4369. }
  4370. }
  4371. // stores the parsed value:
  4372. if (store && item != null)
  4373. result.Add(item);
  4374. // shift:
  4375. f++;
  4376. }
  4377. else if (Char.IsWhiteSpace(c))
  4378. {
  4379. // skips additional white space in the format:
  4380. while (f < format.Length && Char.IsWhiteSpace(format[f])) f++;
  4381. // skips white space in the string:
  4382. while (s < str.Length && Char.IsWhiteSpace(str[s])) s++;
  4383. }
  4384. else if (s < str.Length && c != str[s++])
  4385. {
  4386. // stops string parsing if characters don't match:
  4387. s = str.Length;
  4388. }
  4389. }
  4390. return result;
  4391. }
  4392. /// <summary>
  4393. /// Extracts a range specifier from the formatting string.
  4394. /// </summary>
  4395. /// <param name="format">The formatting string.</param>
  4396. /// <param name="f">The position if the string pointing to the '[' at the beginning and to ']' at the end.</param>
  4397. /// <param name="complement">Whether '^' was stated as the first character in the specifier.</param>
  4398. /// <returns>
  4399. /// <see cref="CharMap"/> containing the characters belonging to the range or a <B>null</B> reference on error.
  4400. /// </returns>
  4401. /// <remarks>
  4402. /// Specifier should be enclosed to brackets '[', ']' and can contain complement character '^' at the beginning.
  4403. /// The first character after '[' or '^' can be ']'. In such a case the specifier continues to the next ']'.
  4404. /// </remarks>
  4405. private static CharMap ParseRangeSpecifier(string format, ref int f, out bool complement)
  4406. {
  4407. Debug.Assert(format != null && f > 0 && f < format.Length && format[f] == '[');
  4408. complement = false;
  4409. f++;
  4410. if (f < format.Length)
  4411. {
  4412. if (format[f] == '^')
  4413. {
  4414. complement = true;
  4415. f++;
  4416. }
  4417. if (f + 1 < format.Length)
  4418. {
  4419. // search for ending bracket (the first symbol can be the bracket so skip it):
  4420. int end = format.IndexOf(']', f + 1);
  4421. if (end >= 0)
  4422. {
  4423. CharMap result = InitializeCharMap();
  4424. result.AddUsingRegularMask(format, f, end, '-');
  4425. f = end;
  4426. return result;
  4427. }
  4428. }
  4429. }
  4430. return null;
  4431. }
  4432. /// <summary>
  4433. /// Parses a string according to a given specifier.
  4434. /// </summary>
  4435. /// <param name="specifier">The specifier.</param>
  4436. /// <param name="width">A width of the maximal parsed substring.</param>
  4437. /// <param name="str">The string to be parsed.</param>
  4438. /// <param name="s">A current position in the string.</param>
  4439. /// <returns>The parsed value or a <B>null</B> reference on error.</returns>
  4440. private static object ParseSubstring(char specifier, int width, string str, ref int s)
  4441. {
  4442. Debug.Assert(width >= 0 && str != null && s < str.Length);
  4443. object result;
  4444. int limit = (width < str.Length - s) ? s + width : str.Length;
  4445. switch (specifier)
  4446. {
  4447. case 'S': // string
  4448. case 's':
  4449. {
  4450. // skips initial white spaces:
  4451. while (s < limit && Char.IsWhiteSpace(str[s])) s++;
  4452. int i = s;
  4453. // skips black spaces:
  4454. while (i < limit && !Char.IsWhiteSpace(str[i])) i++;
  4455. // if s = length then i = s and substring returns an empty string:
  4456. result = str.Substring(s, i - s);
  4457. // moves behind the substring:
  4458. s = i;
  4459. } break;
  4460. case 'C': // character
  4461. case 'c':
  4462. {
  4463. result = str[s++].ToString();
  4464. break;
  4465. }
  4466. case 'X': // hexadecimal integer: [0-9A-Fa-f]*
  4467. case 'x':
  4468. result = Core.Convert.SubstringToLongStrict(str, width, 16, Int32.MaxValue, ref s);
  4469. break;
  4470. case 'o': // octal integer: [0-7]*
  4471. result = Core.Convert.SubstringToLongStrict(str, width, 8, Int32.MaxValue, ref s);
  4472. break;
  4473. case 'd': // decimal integer: [+-]?[0-9]*
  4474. result = Core.Convert.SubstringToLongStrict(str, width, 10, Int32.MaxValue, ref s);
  4475. break;
  4476. case 'u': // unsigned decimal integer [+-]?[1-9][0-9]*
  4477. result = unchecked((uint)Core.Convert.SubstringToLongStrict(str, width, 10, Int32.MaxValue, ref s));
  4478. break;
  4479. case 'i': // decimal (no prefix), hexadecimal (0[xX]...), or octal (0...) integer
  4480. {
  4481. // sign:
  4482. int sign = 0;
  4483. if (str[s] == '-') { sign = -1; s++; }
  4484. else
  4485. if (str[s] == '+') { sign = +1; s++; }
  4486. // string ends
  4487. if (s == limit)
  4488. {
  4489. result = 0;
  4490. break;
  4491. }
  4492. if (str[s] != '0')
  4493. {
  4494. if (sign != 0) s--;
  4495. result = (int)Core.Convert.SubstringToLongStrict(str, width, 10, Int32.MaxValue, ref s);
  4496. break;
  4497. }
  4498. s++;
  4499. // string ends
  4500. if (s == limit)
  4501. {
  4502. result = 0;
  4503. break;
  4504. }
  4505. int number = 0;
  4506. if (str[s] == 'x' || str[s] == 'X')
  4507. {
  4508. s++;
  4509. // reads unsigned hexadecimal number starting from the next position:
  4510. if (s < limit && str[s] != '+' && str[s] != '-')
  4511. number = (int)Core.Convert.SubstringToLongStrict(str, width, 16, Int32.MaxValue, ref s);
  4512. }
  4513. else
  4514. {
  4515. // reads unsigned octal number starting from the current position:
  4516. if (str[s] != '+' && str[s] != '-')
  4517. number = (int)Core.Convert.SubstringToLongStrict(str, width, 8, Int32.MaxValue, ref s);
  4518. }
  4519. // minus sign has been stated:
  4520. result = (sign >= 0) ? +number : -number;
  4521. break;
  4522. }
  4523. case 'e': // float
  4524. case 'E':
  4525. case 'g':
  4526. case 'G':
  4527. case 'f':
  4528. result = Core.Convert.SubstringToDouble(str, width, ref s);
  4529. break;
  4530. case 'n': // the number of read characters is placed into result:
  4531. result = s;
  4532. break;
  4533. default:
  4534. result = null;
  4535. break;
  4536. }
  4537. return result;
  4538. }
  4539. #endregion
  4540. #region wordwrap
  4541. /// <summary>
  4542. /// Wraps a string to 75 characters using new line as the break character.
  4543. /// </summary>
  4544. /// <param name="str">The string to word-wrap.</param>
  4545. /// <returns>The word-wrapped string.</returns>
  4546. /// <remarks>The only "break-point" character is space (' '). If a word is longer than 75 characers
  4547. /// it will stay uncut.</remarks>
  4548. [ImplementsFunction("wordwrap")]
  4549. [return: CastToFalse]
  4550. public static string WordWrap(string str)
  4551. {
  4552. return WordWrap(str, 75, "\n", false);
  4553. }
  4554. /// <summary>
  4555. /// Wraps a string to a specified number of characters using new line as the break character.
  4556. /// </summary>
  4557. /// <param name="str">The string to word-wrap.</param>
  4558. /// <param name="width">The desired line length.</param>
  4559. /// <returns>The word-wrapped string.</returns>
  4560. /// <remarks>The only "break-point" character is space (' '). If a word is longer than <paramref name="width"/>
  4561. /// characers it will stay uncut.</remarks>
  4562. [ImplementsFunction("wordwrap")]
  4563. [return: CastToFalse]
  4564. public static string WordWrap(string str, int width)
  4565. {
  4566. return WordWrap(str, width, "\n", false);
  4567. }
  4568. /// <summary>
  4569. /// Wraps a string to a specified number of characters using a specified string as the break string.
  4570. /// </summary>
  4571. /// <param name="str">The string to word-wrap.</param>
  4572. /// <param name="width">The desired line length.</param>
  4573. /// <param name="lineBreak">The break string.</param>
  4574. /// <returns>The word-wrapped string.</returns>
  4575. /// <remarks>The only "break-point" character is space (' '). If a word is longer than <paramref name="width"/>
  4576. /// characers it will stay uncut.</remarks>
  4577. [ImplementsFunction("wordwrap")]
  4578. [return: CastToFalse]
  4579. public static string WordWrap(string str, int width, string lineBreak)
  4580. {
  4581. return WordWrap(str, width, lineBreak, false);
  4582. }
  4583. /// <summary>
  4584. /// Wraps a string to a specified number of characters using a specified string as the break string.
  4585. /// </summary>
  4586. /// <param name="str">The string to word-wrap.</param>
  4587. /// <param name="width">The desired line length.</param>
  4588. /// <param name="lineBreak">The break string.</param>
  4589. /// <param name="cut">If true, words longer than <paramref name="width"/> will be cut so that no line is longer
  4590. /// than <paramref name="width"/>.</param>
  4591. /// <returns>The word-wrapped string.</returns>
  4592. /// <remarks>The only "break-point" character is space (' ').</remarks>
  4593. /// <exception cref="PhpException">Thrown if the combination of <paramref name="width"/> and <paramref name="cut"/> is invalid.</exception>
  4594. [ImplementsFunction("wordwrap")]
  4595. [return: CastToFalse]
  4596. public static string WordWrap(string str, int width, string lineBreak, bool cut)
  4597. {
  4598. if (width == 0 && cut)
  4599. {
  4600. PhpException.Throw(PhpError.Warning, LibResources.GetString("cut_forced_with_zero_width"));
  4601. return null;
  4602. }
  4603. if (str == null) return null;
  4604. int length = str.Length;
  4605. StringBuilder result = new StringBuilder(length);
  4606. // mimic the strange PHP behaviour when width < 0 and cut is true
  4607. if (width < 0 && cut)
  4608. {
  4609. result.Append(lineBreak);
  4610. width = 1;
  4611. }
  4612. int lastSpace = -1, lineStart = 0;
  4613. for (int i = 0; i < length; i++)
  4614. {
  4615. if (str[i] == ' ')
  4616. {
  4617. lastSpace = i;
  4618. if (i - lineStart >= width + 1)
  4619. {
  4620. // cut is false if we get here
  4621. if (lineStart == 0)
  4622. {
  4623. result.Append(str, 0, i);
  4624. }
  4625. else
  4626. {
  4627. result.Append(lineBreak);
  4628. result.Append(str, lineStart, i - lineStart);
  4629. }
  4630. lineStart = i + 1;
  4631. continue;
  4632. }
  4633. }
  4634. if (i - lineStart >= width)
  4635. {
  4636. // we reached the specified width
  4637. if (lastSpace > lineStart) // obsolete: >=
  4638. {
  4639. if (lineStart > 0) result.Append(lineBreak);
  4640. result.Append(str, lineStart, lastSpace - lineStart);
  4641. lineStart = lastSpace + 1;
  4642. }
  4643. else if (cut)
  4644. {
  4645. if (lineStart > 0) result.Append(lineBreak);
  4646. result.Append(str, lineStart, width);
  4647. lineStart = i;
  4648. }
  4649. }
  4650. }
  4651. // process the rest of str
  4652. if (lineStart < length || lastSpace == length - 1)
  4653. {
  4654. if (lineStart > 0) result.Append(lineBreak);
  4655. result.Append(str, lineStart, length - lineStart);
  4656. }
  4657. return result.ToString();
  4658. }
  4659. #endregion
  4660. #region number_format, NS: money_format
  4661. /// <summary>
  4662. /// Formats a number with grouped thousands.
  4663. /// </summary>
  4664. /// <param name="number">The number to format.</param>
  4665. /// <returns>String representation of the number without decimals (rounded) with comma between every group
  4666. /// of thousands.</returns>
  4667. [ImplementsFunction("number_format")]
  4668. public static string FormatNumber(double number)
  4669. {
  4670. return FormatNumber(number, 0, ".", ",");
  4671. }
  4672. /// <summary>
  4673. /// Formats a number with grouped thousands and with given number of decimals.
  4674. /// </summary>
  4675. /// <param name="number">The number to format.</param>
  4676. /// <param name="decimals">The number of decimals.</param>
  4677. /// <returns>String representation of the number with <paramref name="decimals"/> decimals with a dot in front, and with
  4678. /// comma between every group of thousands.</returns>
  4679. [ImplementsFunction("number_format")]
  4680. public static string FormatNumber(double number, int decimals)
  4681. {
  4682. return FormatNumber(number, decimals, ".", ",");
  4683. }
  4684. /// <summary>
  4685. /// Formats a number with grouped thousands, with given number of decimals, with given decimal point string
  4686. /// and with given thousand separator.
  4687. /// </summary>
  4688. /// <param name="number">The number to format.</param>
  4689. /// <param name="decimals">The number of decimals within range 0 to 99.</param>
  4690. /// <param name="decimalPoint">The string to separate integer part and decimals.</param>
  4691. /// <param name="thousandsSeparator">The character to separate groups of thousands. Only the first character
  4692. /// of <paramref name="thousandsSeparator"/> is used.</param>
  4693. /// <returns>
  4694. /// String representation of the number with <paramref name="decimals"/> decimals with <paramref name="decimalPoint"/> in
  4695. /// front, and with <paramref name="thousandsSeparator"/> between every group of thousands.
  4696. /// </returns>
  4697. /// <remarks>
  4698. /// The <b>number_format</b> (<see cref="FormatNumber"/>) PHP function requires <paramref name="decimalPoint"/> and <paramref name="thousandsSeparator"/>
  4699. /// to be of length 1 otherwise it uses default values (dot and comma respectively). As this behavior does
  4700. /// not make much sense, this method has no such limitation except for <paramref name="thousandsSeparator"/> of which
  4701. /// only the first character is used (documented feature).
  4702. /// </remarks>
  4703. [ImplementsFunction("number_format")]
  4704. public static string FormatNumber(double number, int decimals, string decimalPoint, string thousandsSeparator)
  4705. {
  4706. System.Globalization.NumberFormatInfo format = new System.Globalization.NumberFormatInfo();
  4707. if ((decimals >= 0) && (decimals <= 99))
  4708. {
  4709. format.NumberDecimalDigits = decimals;
  4710. }
  4711. else
  4712. {
  4713. PhpException.InvalidArgument("decimals", LibResources.GetString("arg:out_of_bounds", decimals));
  4714. }
  4715. if (!String.IsNullOrEmpty(decimalPoint))
  4716. {
  4717. format.NumberDecimalSeparator = decimalPoint;
  4718. }
  4719. if (thousandsSeparator == null) thousandsSeparator = String.Empty;
  4720. switch (thousandsSeparator.Length)
  4721. {
  4722. case 0: format.NumberGroupSeparator = String.Empty; break;
  4723. case 1: format.NumberGroupSeparator = thousandsSeparator; break;
  4724. default: format.NumberGroupSeparator = thousandsSeparator.Substring(0, 1); break;
  4725. }
  4726. return number.ToString("N", format);
  4727. }
  4728. /// <summary>
  4729. /// Not supported.
  4730. /// </summary>
  4731. [ImplementsFunction("money_format", FunctionImplOptions.NotSupported)]
  4732. [EditorBrowsable(EditorBrowsableState.Never)]
  4733. public static string FormatMoney(string format, double number)
  4734. {
  4735. PhpException.FunctionNotSupported();
  4736. return null;
  4737. }
  4738. #endregion
  4739. #region hebrev, hebrevc
  4740. /// <summary>
  4741. /// Indicates whether a character is recognized as Hebrew letter.
  4742. /// </summary>
  4743. /// <param name="c">The character.</param>
  4744. /// <returns>
  4745. /// Whether the <paramref name="c"/> is a Hebrew letter according to
  4746. /// the <A href="http://www.unicode.org/charts/PDF/U0590.pdf">Unicode 4.0 standard</A>.
  4747. /// </returns>
  4748. public static bool IsHebrew(char c)
  4749. {
  4750. return c >= '\u05d0' && c <= '\u05ea';
  4751. }
  4752. /// <summary>
  4753. /// Indicates whether a character is a space or tab.
  4754. /// </summary>
  4755. /// <param name="c">The character.</param>
  4756. /// <returns>True iff space or tab.</returns>
  4757. internal static bool IsBlank(char c)
  4758. {
  4759. return c == ' ' || c == '\t';
  4760. }
  4761. /// <summary>
  4762. /// Indicates whether a character is new line or carriage return.
  4763. /// </summary>
  4764. /// <param name="c">The character.</param>
  4765. /// <returns>True iff new line or carriage return.</returns>
  4766. internal static bool IsNewLine(char c)
  4767. {
  4768. return c == '\n' || c == '\r';
  4769. }
  4770. /// <summary>
  4771. /// Converts logical Hebrew text to visual text.
  4772. /// </summary>
  4773. /// <param name="str">The string to convert.</param>
  4774. /// <param name="maxCharactersPerLine">If &gt;0, maximum number of characters per line. If 0,
  4775. /// there is no maximum.</param>
  4776. /// <param name="convertNewLines">Whether to convert new lines '\n' to "&lt;br/&gt;".</param>
  4777. /// <returns>The converted string.</returns>
  4778. internal static string HebrewReverseInternal(string str, int maxCharactersPerLine, bool convertNewLines)
  4779. {
  4780. if (str == null || str == String.Empty) return str;
  4781. int length = str.Length, blockLength = 0, blockStart = 0, blockEnd = 0;
  4782. StringBuilder hebStr = new StringBuilder(length);
  4783. hebStr.Length = length;
  4784. bool blockTypeHeb = IsHebrew(str[0]);
  4785. int source = 0, target = length - 1;
  4786. do
  4787. {
  4788. if (blockTypeHeb)
  4789. {
  4790. while (source + 1 < length && (IsHebrew(str[source + 1]) || IsBlank(str[source + 1]) ||
  4791. Char.IsPunctuation(str[source + 1]) || str[source + 1] == '\n') && blockEnd < length - 1)
  4792. {
  4793. source++;
  4794. blockEnd++;
  4795. blockLength++;
  4796. }
  4797. for (int i = blockStart; i <= blockEnd; i++)
  4798. {
  4799. switch (str[i])
  4800. {
  4801. case '(': hebStr[target] = ')'; break;
  4802. case ')': hebStr[target] = '('; break;
  4803. case '[': hebStr[target] = ']'; break;
  4804. case ']': hebStr[target] = '['; break;
  4805. case '{': hebStr[target] = '}'; break;
  4806. case '}': hebStr[target] = '{'; break;
  4807. case '<': hebStr[target] = '>'; break;
  4808. case '>': hebStr[target] = '<'; break;
  4809. case '\\': hebStr[target] = '/'; break;
  4810. case '/': hebStr[target] = '\\'; break;
  4811. default: hebStr[target] = str[i]; break;
  4812. }
  4813. target--;
  4814. }
  4815. blockTypeHeb = false;
  4816. }
  4817. else
  4818. {
  4819. // blockTypeHeb == false
  4820. while (source + 1 < length && !IsHebrew(str[source + 1]) && str[source + 1] != '\n' &&
  4821. blockEnd < length - 1)
  4822. {
  4823. source++;
  4824. blockEnd++;
  4825. blockLength++;
  4826. }
  4827. while ((IsBlank(str[source]) || Char.IsPunctuation(str[source])) && str[source] != '/' &&
  4828. str[source] != '-' && blockEnd > blockStart)
  4829. {
  4830. source--;
  4831. blockEnd--;
  4832. }
  4833. for (int i = blockEnd; i >= blockStart; i--)
  4834. {
  4835. hebStr[target] = str[i];
  4836. target--;
  4837. }
  4838. blockTypeHeb = true;
  4839. }
  4840. blockStart = blockEnd + 1;
  4841. } while (blockEnd < length - 1);
  4842. StringBuilder brokenStr = new StringBuilder(length);
  4843. brokenStr.Length = length;
  4844. int begin = length - 1, end = begin, charCount, origBegin;
  4845. target = 0;
  4846. while (true)
  4847. {
  4848. charCount = 0;
  4849. while ((maxCharactersPerLine == 0 || charCount < maxCharactersPerLine) && begin > 0)
  4850. {
  4851. charCount++;
  4852. begin--;
  4853. if (begin <= 0 || IsNewLine(hebStr[begin]))
  4854. {
  4855. while (begin > 0 && IsNewLine(hebStr[begin - 1]))
  4856. {
  4857. begin--;
  4858. charCount++;
  4859. }
  4860. break;
  4861. }
  4862. }
  4863. if (charCount == maxCharactersPerLine)
  4864. {
  4865. // try to avoid breaking words
  4866. int newCharCount = charCount, newBegin = begin;
  4867. while (newCharCount > 0)
  4868. {
  4869. if (IsBlank(hebStr[newBegin]) || IsNewLine(hebStr[newBegin])) break;
  4870. newBegin++;
  4871. newCharCount--;
  4872. }
  4873. if (newCharCount > 0)
  4874. {
  4875. charCount = newCharCount;
  4876. begin = newBegin;
  4877. }
  4878. }
  4879. origBegin = begin;
  4880. if (IsBlank(hebStr[begin])) hebStr[begin] = '\n';
  4881. while (begin <= end && IsNewLine(hebStr[begin]))
  4882. {
  4883. // skip leading newlines
  4884. begin++;
  4885. }
  4886. for (int i = begin; i <= end; i++)
  4887. {
  4888. // copy content
  4889. brokenStr[target] = hebStr[i];
  4890. target++;
  4891. }
  4892. for (int i = origBegin; i <= end && IsNewLine(hebStr[i]); i++)
  4893. {
  4894. brokenStr[target] = hebStr[i];
  4895. target++;
  4896. }
  4897. begin = origBegin;
  4898. if (begin <= 0) break;
  4899. begin--;
  4900. end = begin;
  4901. }
  4902. if (convertNewLines) brokenStr.Replace("\n", "<br/>\n");
  4903. return brokenStr.ToString();
  4904. }
  4905. /// <summary>
  4906. /// Converts logical Hebrew text to visual text.
  4907. /// </summary>
  4908. /// <param name="str">The string to convert.</param>
  4909. /// <returns>The comverted string.</returns>
  4910. /// <remarks>Although PHP returns false if <paramref name="str"/> is null or empty there is no reason to do so.</remarks>
  4911. [ImplementsFunction("hebrev")]
  4912. public static string HebrewReverse(string str)
  4913. {
  4914. return HebrewReverseInternal(str, 0, false);
  4915. }
  4916. /// <summary>
  4917. /// Converts logical Hebrew text to visual text.
  4918. /// </summary>
  4919. /// <param name="str">The string to convert.</param>
  4920. /// <param name="maxCharactersPerLine">Maximum number of characters per line.</param>
  4921. /// <returns>The comverted string.</returns>
  4922. /// <remarks>Although PHP returns false if <paramref name="str"/> is null or empty there is no reason to do so.</remarks>
  4923. [ImplementsFunction("hebrev")]
  4924. public static string HebrewReverse(string str, int maxCharactersPerLine)
  4925. {
  4926. return HebrewReverseInternal(str, maxCharactersPerLine, false);
  4927. }
  4928. /// <summary>
  4929. /// Converts logical Hebrew text to visual text and also converts new lines '\n' to "&lt;br/&gt;".
  4930. /// </summary>
  4931. /// <param name="str">The string to convert.</param>
  4932. /// <returns>The converted string.</returns>
  4933. /// <remarks>Although PHP returns false if <paramref name="str"/> is null or empty there is no reason to do so.</remarks>
  4934. [ImplementsFunction("hebrevc")]
  4935. public static string HebrewReverseWithNewLines(string str)
  4936. {
  4937. return HebrewReverseInternal(str, 0, true);
  4938. }
  4939. /// <summary>
  4940. /// Converts logical Hebrew text to visual text and also converts new lines '\n' to "&lt;br/&gt;".
  4941. /// </summary>
  4942. /// <param name="str">The string to convert.</param>
  4943. /// <param name="maxCharactersPerLine">Maximum number of characters per line.</param>
  4944. /// <returns>The comverted string.</returns>
  4945. /// <remarks>Although PHP returns false if <paramref name="str"/> is null or empty there is no reason to do so.</remarks>
  4946. [ImplementsFunction("hebrevc")]
  4947. public static string HebrewReverseWithNewLines(string str, int maxCharactersPerLine)
  4948. {
  4949. return HebrewReverseInternal(str, maxCharactersPerLine, true);
  4950. }
  4951. #endregion
  4952. #region strnatcmp, strnatcasecmp
  4953. /// <summary>
  4954. /// Compares two strings using the natural ordering.
  4955. /// </summary>
  4956. /// <example>NaturalCompare("page155", "page16") returns 1.</example>
  4957. /// <include file='Doc/../../Core/Doc/Common.xml' path='docs/method[@name="Compare(x,y)"]/*'/>
  4958. [ImplementsFunction("strnatcmp")]
  4959. public static int NaturalCompare(string x, string y)
  4960. {
  4961. return PhpNaturalComparer.Default.Compare(x, y);
  4962. }
  4963. /// <summary>
  4964. /// Compares two strings using the natural ordering. Ignores the case.
  4965. /// </summary>
  4966. /// <include file='Doc/../../Core/Doc/Common.xml' path='docs/method[@name="Compare(x,y)"]/*'/>
  4967. [ImplementsFunction("strnatcasecmp")]
  4968. public static int NaturalCompareIgnoringCase(string x, string y)
  4969. {
  4970. return PhpNaturalComparer.CaseInsensitive.Compare(x, y);
  4971. }
  4972. #endregion
  4973. #region str_pad
  4974. /// <summary>
  4975. /// Pads a string to a certain length with spaces.
  4976. /// </summary>
  4977. /// <param name="str">The string to pad.</param>
  4978. /// <param name="totalWidth">Desired length of the returned string.</param>
  4979. /// <returns><paramref name="str"/> padded on the right with spaces.</returns>
  4980. [ImplementsFunction("str_pad")]
  4981. public static object Pad(object str, int totalWidth)
  4982. {
  4983. if (str is PhpBytes) return Pad(str, totalWidth, new PhpBytes(32));
  4984. else return Pad(str, totalWidth, " ");
  4985. }
  4986. /// <summary>
  4987. /// Pads a string to certain length with another string.
  4988. /// </summary>
  4989. /// <param name="str">The string to pad.</param>
  4990. /// <param name="totalWidth">Desired length of the returned string.</param>
  4991. /// <param name="paddingString">The string to use as the pad.</param>
  4992. /// <returns><paramref name="str"/> padded on the right with <paramref name="paddingString"/>.</returns>
  4993. /// <exception cref="PhpException">Thrown if <paramref name="paddingString"/> is null or empty.</exception>
  4994. [ImplementsFunction("str_pad")]
  4995. public static object Pad(object str, int totalWidth, object paddingString)
  4996. {
  4997. return Pad(str, totalWidth, paddingString, PaddingType.Right);
  4998. }
  4999. /// <summary>
  5000. /// Pads a string to certain length with another string.
  5001. /// </summary>
  5002. /// <param name="str">The string to pad.</param>
  5003. /// <param name="totalWidth">Desired length of the returned string.</param>
  5004. /// <param name="paddingString">The string to use as the pad.</param>
  5005. /// <param name="paddingType">Specifies whether the padding should be done on the left, on the right,
  5006. /// or on both sides of <paramref name="str"/>.</param>
  5007. /// <returns><paramref name="str"/> padded with <paramref name="paddingString"/>.</returns>
  5008. /// <exception cref="PhpException">Thrown if <paramref name="paddingType"/> is invalid or <paramref name="paddingString"/> is null or empty.</exception>
  5009. [ImplementsFunction("str_pad")]
  5010. public static object Pad(object str, int totalWidth, object paddingString, PaddingType paddingType)
  5011. {
  5012. PhpBytes binstr = str as PhpBytes;
  5013. if (str is PhpBytes)
  5014. {
  5015. PhpBytes binPaddingString = Core.Convert.ObjectToPhpBytes(paddingString);
  5016. if (binPaddingString == null || binPaddingString.Length == 0)
  5017. {
  5018. PhpException.InvalidArgument("paddingString", LibResources.GetString("arg:null_or_empty"));
  5019. return null;
  5020. }
  5021. if (binstr == null) binstr = PhpBytes.Empty;
  5022. int length = binstr.Length;
  5023. if (totalWidth <= length) return binstr;
  5024. int pad = totalWidth - length, padLeft = 0, padRight = 0;
  5025. switch (paddingType)
  5026. {
  5027. case PaddingType.Left: padLeft = pad; break;
  5028. case PaddingType.Right: padRight = pad; break;
  5029. case PaddingType.Both:
  5030. padLeft = pad / 2;
  5031. padRight = pad - padLeft;
  5032. break;
  5033. default:
  5034. PhpException.InvalidArgument("paddingType");
  5035. break;
  5036. }
  5037. // if paddingString has length 1, use String.PadLeft and String.PadRight
  5038. int padStrLength = binPaddingString.Length;
  5039. // else build the resulting string manually
  5040. byte[] result = new byte[totalWidth];
  5041. int position = 0;
  5042. // pad left
  5043. while (padLeft > padStrLength)
  5044. {
  5045. Buffer.BlockCopy(binPaddingString.ReadonlyData, 0, result, position, padStrLength);
  5046. padLeft -= padStrLength;
  5047. position += padStrLength;
  5048. }
  5049. if (padLeft > 0)
  5050. {
  5051. Buffer.BlockCopy(binPaddingString.ReadonlyData, 0, result, position, padLeft);
  5052. position += padLeft;
  5053. }
  5054. Buffer.BlockCopy(binstr.ReadonlyData, 0, result, position, binstr.Length);
  5055. position += binstr.Length;
  5056. // pad right
  5057. while (padRight > padStrLength)
  5058. {
  5059. Buffer.BlockCopy(binPaddingString.ReadonlyData, 0, result, position, padStrLength);
  5060. padRight -= padStrLength;
  5061. position += padStrLength;
  5062. }
  5063. if (padRight > 0)
  5064. {
  5065. Buffer.BlockCopy(binPaddingString.ReadonlyData, 0, result, position, padRight);
  5066. position += padRight;
  5067. }
  5068. return new PhpBytes(result);
  5069. }
  5070. string unistr = Core.Convert.ObjectToString(str);
  5071. if (unistr != null)
  5072. {
  5073. string uniPaddingString = Core.Convert.ObjectToString(paddingString);
  5074. if (String.IsNullOrEmpty(uniPaddingString))
  5075. {
  5076. PhpException.InvalidArgument("paddingString", LibResources.GetString("arg:null_or_empty"));
  5077. return null;
  5078. }
  5079. if (unistr == null) unistr = String.Empty;
  5080. int length = unistr.Length;
  5081. if (totalWidth <= length) return unistr;
  5082. int pad = totalWidth - length, padLeft = 0, padRight = 0;
  5083. switch (paddingType)
  5084. {
  5085. case PaddingType.Left: padLeft = pad; break;
  5086. case PaddingType.Right: padRight = pad; break;
  5087. case PaddingType.Both:
  5088. padLeft = pad / 2;
  5089. padRight = pad - padLeft;
  5090. break;
  5091. default:
  5092. PhpException.InvalidArgument("paddingType");
  5093. break;
  5094. }
  5095. // if paddingString has length 1, use String.PadLeft and String.PadRight
  5096. int padStrLength = uniPaddingString.Length;
  5097. if (padStrLength == 1)
  5098. {
  5099. char c = uniPaddingString[0];
  5100. if (padLeft > 0) unistr = unistr.PadLeft(length + padLeft, c);
  5101. if (padRight > 0) unistr = unistr.PadRight(totalWidth, c);
  5102. return unistr;
  5103. }
  5104. // else build the resulting string manually
  5105. StringBuilder result = new StringBuilder(totalWidth);
  5106. // pad left
  5107. while (padLeft > padStrLength)
  5108. {
  5109. result.Append(uniPaddingString);
  5110. padLeft -= padStrLength;
  5111. }
  5112. if (padLeft > 0) result.Append(uniPaddingString.Substring(0, padLeft));
  5113. result.Append(unistr);
  5114. // pad right
  5115. while (padRight > padStrLength)
  5116. {
  5117. result.Append(uniPaddingString);
  5118. padRight -= padStrLength;
  5119. }
  5120. if (padRight > 0) result.Append(uniPaddingString.Substring(0, padRight));
  5121. return result.ToString();
  5122. }
  5123. return null;
  5124. }
  5125. #endregion
  5126. #region str_word_count
  5127. /// <summary>
  5128. /// Counts the number of words inside a string.
  5129. /// </summary>
  5130. /// <param name="str">The string containing words to count.</param>
  5131. /// <returns>Then number of words inside <paramref name="str"/>. </returns>
  5132. [ImplementsFunction("str_word_count")]
  5133. public static int CountWords(string str)
  5134. {
  5135. return CountWords(str, WordCountResult.WordCount, null, null);
  5136. }
  5137. /// <summary>
  5138. /// Splits a string into words.
  5139. /// </summary>
  5140. /// <param name="str">The string to split.</param>
  5141. /// <param name="format">If <see cref="WordCountResult.WordsArray"/>, the method returns an array containing all
  5142. /// the words found inside the string. If <see cref="WordCountResult.PositionsToWordsMapping"/>, the method returns
  5143. /// an array, where the key is the numeric position of the word inside the string and the value is the
  5144. /// actual word itself.</param>
  5145. /// <returns>Array of words. Keys are just numbers starting with 0 (when <paramref name="format"/> is
  5146. /// WordCountResult.WordsArray) or positions of the words inside <paramref name="str"/> (when
  5147. /// <paramref name="format"/> is <see cref="WordCountResult.PositionsToWordsMapping"/>).</returns>
  5148. /// <exception cref="PhpException">Thrown if <paramref name="format"/> is invalid.</exception>
  5149. [ImplementsFunction("str_word_count")]
  5150. public static object CountWords(string str, WordCountResult format)
  5151. {
  5152. return CountWords(str, format, null);
  5153. }
  5154. [ImplementsFunction("str_word_count")]
  5155. public static object CountWords(string str, WordCountResult format, string addWordChars)
  5156. {
  5157. PhpArray words = (format != WordCountResult.WordCount) ? new PhpArray() : null;
  5158. int count = CountWords(str, format, addWordChars, words);
  5159. if (count == -1)
  5160. return false;
  5161. if (format == WordCountResult.WordCount)
  5162. return count;
  5163. else
  5164. {
  5165. if (words != null)
  5166. return words;
  5167. else
  5168. return false;
  5169. }
  5170. }
  5171. private static bool IsWordChar(char c, CharMap map)
  5172. {
  5173. return Char.IsLetter(c) || map != null && map.Contains(c);
  5174. }
  5175. public static int CountWords(string str, WordCountResult format, string addWordChars, IDictionary words)
  5176. {
  5177. if (str == null)
  5178. return 0;
  5179. if (format != WordCountResult.WordCount && words == null)
  5180. throw new ArgumentNullException("words");
  5181. CharMap charmap = null;
  5182. if (!String.IsNullOrEmpty(addWordChars))
  5183. {
  5184. charmap = InitializeCharMap();
  5185. charmap.Add(addWordChars);
  5186. }
  5187. // find the end
  5188. int last = str.Length - 1;
  5189. if (last > 0 && str[last] == '-' && !IsWordChar(str[last], charmap)) last--;
  5190. // find the beginning
  5191. int pos = 0;
  5192. if (last >= 0 && (str[0] == '-' || str[0] == '\'') && !IsWordChar(str[0], charmap)) pos++;
  5193. int word_count = 0;
  5194. while (pos <= last)
  5195. {
  5196. if (IsWordChar(str[pos], charmap) || str[pos] == '\'' || str[pos] == '-')
  5197. {
  5198. // word started - read it whole:
  5199. int word_start = pos++;
  5200. while (pos <= last &&
  5201. (IsWordChar(str[pos], charmap) ||
  5202. str[pos] == '\'' || str[pos] == '-'))
  5203. {
  5204. pos++;
  5205. }
  5206. switch (format)
  5207. {
  5208. case WordCountResult.WordCount:
  5209. break;
  5210. case WordCountResult.WordsArray:
  5211. words.Add(word_count, str.Substring(word_start, pos - word_start));
  5212. break;
  5213. case WordCountResult.PositionsToWordsMapping:
  5214. words.Add(word_start, str.Substring(word_start, pos - word_start));
  5215. break;
  5216. default:
  5217. PhpException.InvalidArgument("format");
  5218. return -1;
  5219. }
  5220. word_count++;
  5221. }
  5222. else pos++;
  5223. }
  5224. return word_count;
  5225. }
  5226. #endregion
  5227. #region strcmp, strcasecmp, strncmp, strncasecmp
  5228. /// <summary>
  5229. /// Compares two specified strings, honoring their case, using culture invariant comparison.
  5230. /// </summary>
  5231. /// <param name="str1">A string.</param>
  5232. /// <param name="str2">A string.</param>
  5233. /// <returns>Returns -1 if <paramref name="str1"/> is less than <paramref name="str2"/>; +1 if <paramref name="str1"/> is greater than <paramref name="str2"/>,
  5234. /// and 0 if they are equal.</returns>
  5235. [ImplementsFunction("strcmp")]
  5236. [EditorBrowsable(EditorBrowsableState.Never)]
  5237. public static int Compare(string str1, string str2)
  5238. {
  5239. return String.CompareOrdinal(str1, str2);
  5240. }
  5241. /// <summary>
  5242. /// Compares two specified strings, ignoring their case, using culture invariant comparison.
  5243. /// </summary>
  5244. /// <param name="str1">A string.</param>
  5245. /// <param name="str2">A string.</param>
  5246. /// <returns>Returns -1 if <paramref name="str1"/> is less than <paramref name="str2"/>; +1 if <paramref name="str1"/> is greater than <paramref name="str2"/>,
  5247. /// and 0 if they are equal.</returns>
  5248. [ImplementsFunction("strcasecmp")]
  5249. [EditorBrowsable(EditorBrowsableState.Never)]
  5250. public static int CompareIgnoringCase(string str1, string str2)
  5251. {
  5252. #if SILVERLIGHT
  5253. return String.Compare(str1, str2, System.Globalization.CultureInfo.InvariantCulture,System.Globalization.CompareOptions.IgnoreCase);
  5254. #else
  5255. return String.Compare(str1, str2, true, System.Globalization.CultureInfo.InvariantCulture);
  5256. #endif
  5257. }
  5258. /// <summary>
  5259. /// Compares parts of two specified strings, honoring their case, using culture invariant comparison.
  5260. /// </summary>
  5261. /// <param name="str1">The lesser string.</param>
  5262. /// <param name="str2">The greater string.</param>
  5263. /// <param name="length">The upper limit of the length of parts to be compared.</param>
  5264. /// <returns>Returns -1 if <paramref name="str1"/> is less than <paramref name="str2"/>; +1 if <paramref name="str1"/> is greater than <paramref name="str2"/>,
  5265. /// and 0 if they are equal.</returns>
  5266. [ImplementsFunction("strncmp")]
  5267. [EditorBrowsable(EditorBrowsableState.Never)]
  5268. public static object Compare(string str1, string str2, int length)
  5269. {
  5270. if (length < 0)
  5271. {
  5272. PhpException.Throw(PhpError.Warning, LibResources.GetString("must_be_positive", "Length"));
  5273. return false;
  5274. }
  5275. return String.CompareOrdinal(str1, 0, str2, 0, length);
  5276. }
  5277. /// <summary>
  5278. /// Compares parts of two specified strings, honoring their case, using culture invariant comparison.
  5279. /// </summary>
  5280. /// <param name="str1">A string.</param>
  5281. /// <param name="str2">A string.</param>
  5282. /// <param name="length">The upper limit of the length of parts to be compared.</param>
  5283. /// <returns>Returns -1 if <paramref name="str1"/> is less than <paramref name="str2"/>; +1 if <paramref name="str1"/> is greater than <paramref name="str2"/>,
  5284. /// and 0 if they are equal.</returns>
  5285. [ImplementsFunction("strncasecmp")]
  5286. [EditorBrowsable(EditorBrowsableState.Never)]
  5287. public static object CompareIgnoringCase(string str1, string str2, int length)
  5288. {
  5289. if (length < 0)
  5290. {
  5291. PhpException.Throw(PhpError.Warning, LibResources.GetString("must_be_positive", "Length"));
  5292. return false;
  5293. }
  5294. #if SILVERLIGHT
  5295. return String.Compare(str1, 0, str2, 0, length, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.CompareOptions.IgnoreCase);
  5296. #else
  5297. return String.Compare(str1, 0, str2, 0, length, true, System.Globalization.CultureInfo.InvariantCulture);
  5298. #endif
  5299. }
  5300. #endregion
  5301. #region strpos, strrpos, stripos, strripos
  5302. #region Stubs
  5303. /// <summary>
  5304. /// Retrieves the index of the first occurrence of the <paramref name="needle"/> in the <paramref name="haystack"/>.
  5305. /// </summary>
  5306. /// <remarks>See <see cref="Strpos(string,object,int)"/> for details.</remarks>
  5307. /// <exception cref="PhpException">Thrown if <paramref name="needle"/> is empty string.</exception>
  5308. [ImplementsFunction("strpos"), EditorBrowsable(EditorBrowsableState.Never)]
  5309. [return: CastToFalse]
  5310. public static int Strpos(string haystack, object needle)
  5311. {
  5312. return Strpos(haystack, needle, 0, false);
  5313. }
  5314. /// <summary>
  5315. /// Retrieves the index of the first occurrence of the <paramref name="needle"/> in the <paramref name="haystack"/>.
  5316. /// The search starts at the specified character position.
  5317. /// </summary>
  5318. /// <param name="haystack">The string to search in.</param>
  5319. /// <param name="needle">
  5320. /// The string or the ordinal value of character to search for.
  5321. /// If non-string is passed as a needle then it is converted to an integer (modulo 256) and the character
  5322. /// with such ordinal value (relatively to the current encoding set in the configuration) is searched.</param>
  5323. /// <param name="offset">
  5324. /// The position where to start searching. Should be between 0 and a length of the <paramref name="haystack"/> including.
  5325. /// </param>
  5326. /// <returns>Non-negative integer on success, -1 otherwise.</returns>
  5327. /// <exception cref="PhpException"><paramref name="offset"/> is out of bounds or <paramref name="needle"/> is empty string.</exception>
  5328. [ImplementsFunction("strpos"), EditorBrowsable(EditorBrowsableState.Never)]
  5329. [return: CastToFalse]
  5330. public static int Strpos(string haystack, object needle, int offset)
  5331. {
  5332. return Strpos(haystack, needle, offset, false);
  5333. }
  5334. /// <summary>
  5335. /// Retrieves the index of the first occurrence of the <paramref name="needle"/> in the <paramref name="haystack"/>
  5336. /// (case insensitive).
  5337. /// </summary>
  5338. /// <remarks>See <see cref="Strpos(string,object,int)"/> for details.</remarks>
  5339. /// <exception cref="PhpException">Thrown if <paramref name="needle"/> is empty string.</exception>
  5340. [ImplementsFunction("stripos"), EditorBrowsable(EditorBrowsableState.Never)]
  5341. [return: CastToFalse]
  5342. public static int Stripos(string haystack, object needle)
  5343. {
  5344. return Strpos(haystack, needle, 0, true);
  5345. }
  5346. /// <summary>
  5347. /// Retrieves the index of the first occurrence of the <paramref name="needle"/> in the <paramref name="haystack"/>
  5348. /// (case insensitive).
  5349. /// </summary>
  5350. /// <remarks>See <see cref="Strpos(string,object,int)"/> for details.</remarks>
  5351. /// <exception cref="PhpException">Thrown if <paramref name="offset"/> is out of bounds or <paramref name="needle"/> is empty string.</exception>
  5352. [ImplementsFunction("stripos"), EditorBrowsable(EditorBrowsableState.Never)]
  5353. [return: CastToFalse]
  5354. public static int Stripos(string haystack, object needle, int offset)
  5355. {
  5356. return Strpos(haystack, needle, offset, true);
  5357. }
  5358. /// <summary>
  5359. /// Retrieves the index of the last occurrence of the <paramref name="needle"/> in the <paramref name="haystack"/>.
  5360. /// </summary>
  5361. /// <remarks>See <see cref="Strrpos(string,object,int)"/> for details.</remarks>
  5362. [ImplementsFunction("strrpos"), EditorBrowsable(EditorBrowsableState.Never)]
  5363. [return: CastToFalse]
  5364. public static int Strrpos(string haystack, object needle)
  5365. {
  5366. return Strrpos(haystack, needle, 0, false);
  5367. }
  5368. /// <summary>
  5369. /// Retrieves the index of the last occurrence of the <paramref name="needle"/> in the <paramref name="haystack"/>.
  5370. /// The search starts at the specified character position.
  5371. /// </summary>
  5372. /// <param name="haystack">The string to search in.</param>
  5373. /// <param name="needle">The string or the ordinal value of character to search for.
  5374. /// If non-string is passed as a needle then it is converted to an integer (modulo 256) and the character
  5375. /// with such ordinal value (relatively to the current encoding set in the configuration) is searched.</param>
  5376. /// <param name="offset">
  5377. /// The position where to start searching (is non-negative) or a negative number of characters
  5378. /// prior the end where to stop searching (if negative).
  5379. /// </param>
  5380. /// <returns>Non-negative integer on success, -1 otherwise.</returns>
  5381. /// <exception cref="PhpException">Thrown if <paramref name="offset"/> is out of bounds or <paramref name="needle"/> is empty string.</exception>
  5382. [ImplementsFunction("strrpos"), EditorBrowsable(EditorBrowsableState.Never)]
  5383. [return: CastToFalse]
  5384. public static int Strrpos(string haystack, object needle, int offset)
  5385. {
  5386. return Strrpos(haystack, needle, offset, false);
  5387. }
  5388. /// <summary>
  5389. /// Retrieves the index of the last occurrence of the <paramref name="needle"/> in the <paramref name="haystack"/>
  5390. /// (case insensitive).
  5391. /// </summary>
  5392. /// <remarks>See <see cref="Strrpos(string,object,int)"/> for details.</remarks>
  5393. [ImplementsFunction("strripos"), EditorBrowsable(EditorBrowsableState.Never)]
  5394. [return: CastToFalse]
  5395. public static int Strripos(string haystack, string needle)
  5396. {
  5397. return Strrpos(haystack, needle, 0, true);
  5398. }
  5399. /// <summary>
  5400. /// Retrieves the index of the last occurrence of the <paramref name="needle"/> in the <paramref name="haystack"/>
  5401. /// (case insensitive).
  5402. /// </summary>
  5403. /// <remarks>See <see cref="Strrpos(string,object,int)"/> for details.</remarks>
  5404. /// <exception cref="PhpException">Thrown if <paramref name="offset"/> is out of bounds or <paramref name="needle"/> is empty string.</exception>
  5405. [ImplementsFunction("strripos"), EditorBrowsable(EditorBrowsableState.Never)]
  5406. [return: CastToFalse]
  5407. public static int Strripos(string haystack, object needle, int offset)
  5408. {
  5409. return Strrpos(haystack, needle, offset, true);
  5410. }
  5411. #endregion
  5412. /// <summary>
  5413. /// Implementation of <c>str[i]pos</c> functions.
  5414. /// </summary>
  5415. public static int Strpos(string haystack, object needle, int offset, bool ignoreCase)
  5416. {
  5417. if (String.IsNullOrEmpty(haystack)) return -1;
  5418. if (offset < 0 || offset >= haystack.Length)
  5419. {
  5420. if (offset != haystack.Length)
  5421. PhpException.InvalidArgument("offset", LibResources.GetString("arg:out_of_bounds"));
  5422. return -1;
  5423. }
  5424. string str_needle = PhpVariable.AsString(needle);
  5425. if (str_needle != null)
  5426. {
  5427. if (str_needle == String.Empty)
  5428. {
  5429. PhpException.InvalidArgument("needle", LibResources.GetString("arg:empty"));
  5430. return -1;
  5431. }
  5432. if (ignoreCase)
  5433. return haystack.IndexOf(str_needle, offset, StringComparison.OrdinalIgnoreCase);
  5434. else
  5435. return haystack.IndexOf(str_needle, offset, StringComparison.Ordinal);
  5436. }
  5437. else
  5438. {
  5439. if (ignoreCase)
  5440. return haystack.IndexOf(ChrUnicode(Core.Convert.ObjectToInteger(needle) % 256), offset, StringComparison.OrdinalIgnoreCase);
  5441. else
  5442. return haystack.IndexOf(ChrUnicode(Core.Convert.ObjectToInteger(needle) % 256), offset, StringComparison.Ordinal);
  5443. }
  5444. }
  5445. /// <summary>
  5446. /// Implementation of <c>strr[i]pos</c> functions.
  5447. /// </summary>
  5448. public static int Strrpos(string haystack, object needle, int offset, bool ignoreCase)
  5449. {
  5450. if (String.IsNullOrEmpty(haystack)) return -1;
  5451. int end = haystack.Length - 1;
  5452. if (offset > end || offset < -end - 1)
  5453. {
  5454. PhpException.InvalidArgument("offset", LibResources.GetString("arg:out_of_bounds"));
  5455. return -1;
  5456. }
  5457. string str_needle = PhpVariable.AsString(needle);
  5458. if (offset < 0)
  5459. {
  5460. end += offset + (str_needle != null ? str_needle.Length : 1);
  5461. offset = 0;
  5462. }
  5463. if (str_needle != null)
  5464. {
  5465. if (str_needle.Length == 0)
  5466. {
  5467. PhpException.InvalidArgument("needle", LibResources.GetString("arg:empty"));
  5468. return -1;
  5469. }
  5470. if (ignoreCase)
  5471. return haystack.LastIndexOf(str_needle, end, end - offset + 1, StringComparison.OrdinalIgnoreCase);
  5472. else
  5473. return haystack.LastIndexOf(str_needle, end, end - offset + 1, StringComparison.Ordinal);
  5474. }
  5475. else
  5476. {
  5477. if (ignoreCase)
  5478. return haystack.LastIndexOf(ChrUnicode(Core.Convert.ObjectToInteger(needle) % 256), end, end - offset + 1, StringComparison.OrdinalIgnoreCase);
  5479. else
  5480. return haystack.LastIndexOf(ChrUnicode(Core.Convert.ObjectToInteger(needle) % 256), end, end - offset + 1, StringComparison.Ordinal);
  5481. }
  5482. }
  5483. #endregion
  5484. #region strstr, stristr, strchr, strrchr
  5485. #region Stubs
  5486. /// <summary>
  5487. /// Finds first occurrence of a string.
  5488. /// </summary>
  5489. /// <param name="haystack">The string to search in.</param>
  5490. /// <param name="needle">The substring to search for.</param>
  5491. /// <returns>Part of <paramref name="haystack"/> string from the first occurrence of <paramref name="needle"/> to the end
  5492. /// of <paramref name="haystack"/> or null if <paramref name="needle"/> is not found.</returns>
  5493. /// <exception cref="PhpException">Thrown when <paramref name="needle"/> is empty.</exception>
  5494. [ImplementsFunction("strstr"), EditorBrowsable(EditorBrowsableState.Never)]
  5495. [return: CastToFalse]
  5496. public static string Strstr(string haystack, object needle)
  5497. {
  5498. return StrstrImpl(haystack, needle, false, false);
  5499. }
  5500. /// <summary>
  5501. /// Finds first occurrence of a string.
  5502. /// </summary>
  5503. /// <param name="haystack">The string to search in.</param>
  5504. /// <param name="needle">The substring to search for.</param>
  5505. /// <param name="beforeNeedle">If TRUE, strstr() returns the part of the haystack before the first occurrence of the needle. </param>
  5506. /// <returns>Part of <paramref name="haystack"/> string from the first occurrence of <paramref name="needle"/> to the end
  5507. /// of <paramref name="haystack"/> or null if <paramref name="needle"/> is not found.</returns>
  5508. /// <exception cref="PhpException">Thrown when <paramref name="needle"/> is empty.</exception>
  5509. [ImplementsFunction("strstr"), EditorBrowsable(EditorBrowsableState.Never)]
  5510. [return: CastToFalse]
  5511. public static string Strstr(string haystack, object needle, bool beforeNeedle /*= false*/)
  5512. {
  5513. return StrstrImpl(haystack, needle, false, beforeNeedle);
  5514. }
  5515. /// <summary>
  5516. /// Finds first occurrence of a string. Alias of <see cref="Strstr(string,object)"/>.
  5517. /// </summary>
  5518. /// <remarks>See <see cref="Strstr(string,object)"/> for details.</remarks>
  5519. /// <exception cref="PhpException">Thrown when <paramref name="needle"/> is empty.</exception>
  5520. [ImplementsFunction("strchr"), EditorBrowsable(EditorBrowsableState.Never)]
  5521. public static string Strchr(string haystack, object needle)
  5522. {
  5523. return StrstrImpl(haystack, needle, false, false);
  5524. }
  5525. /// <summary>
  5526. /// Case insensitive version of <see cref="Strstr(string,object)"/>.
  5527. /// </summary>
  5528. /// <exception cref="PhpException">Thrown when <paramref name="needle"/> is empty.</exception>
  5529. [ImplementsFunction("stristr"), EditorBrowsable(EditorBrowsableState.Never)]
  5530. [return: CastToFalse]
  5531. public static string Stristr(string haystack, object needle)
  5532. {
  5533. return StrstrImpl(haystack, needle, true, false);
  5534. }
  5535. /// <summary>
  5536. /// Case insensitive version of <see cref="Strstr(string,object)"/>.
  5537. /// </summary>
  5538. /// <param name="haystack"></param>
  5539. /// <param name="needle"></param>
  5540. /// <param name="beforeNeedle">If TRUE, strstr() returns the part of the haystack before the first occurrence of the needle. </param>
  5541. /// <exception cref="PhpException">Thrown when <paramref name="needle"/> is empty.</exception>
  5542. [ImplementsFunction("stristr"), EditorBrowsable(EditorBrowsableState.Never)]
  5543. [return: CastToFalse]
  5544. public static string Stristr(string haystack, object needle, bool beforeNeedle /*= false*/)
  5545. {
  5546. return StrstrImpl(haystack, needle, true, beforeNeedle);
  5547. }
  5548. #endregion
  5549. /// <summary>
  5550. /// This function returns the portion of haystack which starts at the last occurrence of needle and goes until the end of haystack .
  5551. /// </summary>
  5552. /// <param name="haystack">The string to search in.</param>
  5553. /// <param name="needle">
  5554. /// If needle contains more than one character, only the first is used. This behavior is different from that of strstr().
  5555. /// If needle is not a string, it is converted to an integer and applied as the ordinal value of a character.
  5556. /// </param>
  5557. /// <returns>This function returns the portion of string, or FALSE if needle is not found.</returns>
  5558. /// <exception cref="PhpException">Thrown when <paramref name="needle"/> is empty.</exception>
  5559. [ImplementsFunction("strrchr"), EditorBrowsable(EditorBrowsableState.Never)]
  5560. [return: CastToFalse]
  5561. public static string Strrchr(string haystack, object needle)
  5562. {
  5563. if (haystack == null)
  5564. return null;
  5565. char charToFind;
  5566. string str_needle;
  5567. if ((str_needle = PhpVariable.AsString(needle)) != null)
  5568. {
  5569. if (str_needle.Length == 0)
  5570. {
  5571. PhpException.InvalidArgument("needle", LibResources.GetString("arg:empty"));
  5572. return null;
  5573. }
  5574. charToFind = str_needle[0];
  5575. }
  5576. else
  5577. {
  5578. charToFind = ChrUnicode(Core.Convert.ObjectToInteger(needle) % 256)[0];
  5579. }
  5580. int index = haystack.LastIndexOf(charToFind);
  5581. if (index < 0)
  5582. return null;
  5583. return haystack.Substring(index);
  5584. }
  5585. /// <summary>
  5586. /// Implementation of <c>str[i]{chr|str}</c> functions.
  5587. /// </summary>
  5588. internal static string StrstrImpl(string haystack, object needle, bool ignoreCase, bool beforeNeedle)
  5589. {
  5590. if (haystack == null) return null;
  5591. int index;
  5592. string str_needle = PhpVariable.AsString(needle);
  5593. if (str_needle != null)
  5594. {
  5595. if (str_needle == String.Empty)
  5596. {
  5597. PhpException.InvalidArgument("needle", LibResources.GetString("arg:empty"));
  5598. return null;
  5599. }
  5600. if (ignoreCase)
  5601. index = haystack.ToLower().IndexOf(str_needle.ToLower());
  5602. else
  5603. index = haystack.IndexOf(str_needle);
  5604. }
  5605. else
  5606. {
  5607. if (ignoreCase)
  5608. index = haystack.ToLower().IndexOf(ChrUnicode(Core.Convert.ObjectToInteger(needle) % 256).ToLower());
  5609. else
  5610. index = haystack.IndexOf(ChrUnicode(Core.Convert.ObjectToInteger(needle) % 256));
  5611. }
  5612. return (index == -1) ? null : (beforeNeedle ? haystack.Substring(0, index) : haystack.Substring(index));
  5613. }
  5614. #endregion
  5615. #region strpbrk
  5616. /// <summary>
  5617. /// Finds first occurence of any of given characters.
  5618. /// </summary>
  5619. /// <param name="haystack">The string to search in.</param>
  5620. /// <param name="charList">The characters to search for given as a string.</param>
  5621. /// <returns>Part of <paramref name="haystack"/> string from the first occurrence of any of characters contained
  5622. /// in <paramref name="charList"/> to the end of <paramref name="haystack"/> or <B>null</B> if no character is
  5623. /// found.</returns>
  5624. /// <exception cref="PhpException">Thrown when <paramref name="charList"/> is empty.</exception>
  5625. [ImplementsFunction("strpbrk")]
  5626. [return: CastToFalse]
  5627. public static string Strpbrk(string haystack, string charList)
  5628. {
  5629. if (charList == null)
  5630. {
  5631. PhpException.InvalidArgument("charList", LibResources.GetString("arg:empty"));
  5632. return null;
  5633. }
  5634. if (haystack == null) return null;
  5635. int index = haystack.IndexOfAny(charList.ToCharArray());
  5636. return (index >= 0 ? haystack.Substring(index) : null);
  5637. }
  5638. #endregion
  5639. #region strtolower, strtoupper, strlen
  5640. /// <summary>
  5641. /// Returns string with all alphabetic characters converted to lowercase.
  5642. /// Note that 'alphabetic' is determined by the current culture.
  5643. /// </summary>
  5644. /// <param name="str">The string to convert.</param>
  5645. /// <returns>The lowercased string or empty string if <paramref name="str"/> is null.</returns>
  5646. [ImplementsFunction("strtolower")]
  5647. public static string ToLower(string str)
  5648. {
  5649. return (str == null) ? String.Empty : str.ToLower(Locale.GetCulture(Locale.Category.CType));
  5650. }
  5651. /// <summary>
  5652. /// Returns string with all alphabetic characters converted to lowercase.
  5653. /// Note that 'alphabetic' is determined by the current culture.
  5654. /// </summary>
  5655. /// <param name="str">The string to convert.</param>
  5656. /// <returns>The lowercased string or empty string if <paramref name="str"/> is null.</returns>
  5657. [ImplementsFunction("strtoupper")]
  5658. public static string ToUpper(string str)
  5659. {
  5660. return (str == null) ? String.Empty : str.ToUpper(Locale.GetCulture(Locale.Category.CType));
  5661. }
  5662. /// <summary>
  5663. /// Returns the length of a string.
  5664. /// </summary>
  5665. /// <param name="x">The string (either <see cref="string"/> or <see cref="PhpBytes"/>).</param>
  5666. /// <returns>The length of the string.</returns>
  5667. [ImplementsFunction("strlen"/*, FunctionImplOptions.Special*/)]
  5668. [PureFunction]
  5669. [EditorBrowsable(EditorBrowsableState.Never)]
  5670. public static int Length(object x)
  5671. {
  5672. string str = x as string;
  5673. if (str != null) return str.Length;
  5674. PhpBytes bytes = x as PhpBytes;
  5675. if (bytes != null) return bytes.Length;
  5676. PhpString phpstr = x as PhpString;
  5677. if (phpstr != null) return phpstr.Length;
  5678. return Core.Convert.ObjectToString(x).Length;
  5679. }
  5680. #endregion
  5681. #region Helpers
  5682. /// <summary>
  5683. /// Converts object <paramref name="obj"/> to <see cref="System.String"/>.
  5684. /// In case if bunary string, the conversion routine respects given <paramref name="charSet"/>.
  5685. /// </summary>
  5686. /// <param name="obj">Object to be converted.</param>
  5687. /// <param name="charSet">Character set used to encode binary string to <see cref="System.String"/>.</param>
  5688. /// <returns>String representation of <paramref name="obj"/>.</returns>
  5689. internal static string ObjectToString(object obj, string charSet)
  5690. {
  5691. if (obj != null && obj.GetType() == typeof(PhpBytes))
  5692. {
  5693. var encoding = Encoding.GetEncoding(charSet);
  5694. if (encoding == null)
  5695. throw new ArgumentException(string.Format(Strings.arg_invalid_value, "charSet", charSet), "charSet");
  5696. return encoding.GetString(((PhpBytes)obj).ReadonlyData);
  5697. }
  5698. else
  5699. {
  5700. return Core.Convert.ObjectToString(obj);
  5701. }
  5702. }
  5703. #endregion
  5704. }
  5705. }