PageRenderTime 59ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/2.0/Source/ClassLibrary/FileSystem.Path.cs

#
C# | 874 lines | 594 code | 87 blank | 193 comment | 98 complexity | d75b991a3d8c9c63219e99c6aefe0cbe 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 Jan Benda and Tomas Matousek.
  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. TODO:
  8. - Fixed tempnam() 2nd parameter to be checked against path components. (PHP 5.1.3)
  9. */
  10. using System;
  11. using System.IO;
  12. using System.Text;
  13. using System.Collections;
  14. using System.Threading;
  15. using System.ComponentModel;
  16. using PHP.Core;
  17. using System.Collections.Generic;
  18. using System.Text.RegularExpressions;
  19. namespace PHP.Library
  20. {
  21. /// <summary>
  22. /// Provides path strings manipulation.
  23. /// </summary>
  24. /// <threadsafety static="true"/>
  25. public static class PhpPath
  26. {
  27. #region Enums: GlobOptions
  28. /// <summary>
  29. /// Flags used in call to <c>glob()</c>.
  30. /// </summary>
  31. [Flags]
  32. public enum GlobOptions
  33. {
  34. /// <summary>
  35. /// No flags.
  36. /// </summary>
  37. None = 0,
  38. /// <summary>
  39. /// Append / to matching directories.
  40. /// </summary>
  41. [ImplementsConstant("GLOB_MARK")]
  42. Mark = 0x0008,
  43. /// <summary>
  44. /// Return pattern itself if nothing matches.
  45. /// </summary>
  46. [ImplementsConstant("GLOB_NOCHECK")]
  47. NoCheck = 0x0010,
  48. /// <summary>
  49. /// Don't sort.
  50. /// </summary>
  51. [ImplementsConstant("GLOB_NOSORT")]
  52. NoSort = 0x0020,
  53. /// <summary>
  54. /// Expand braces ala csh.
  55. /// </summary>
  56. [ImplementsConstant("GLOB_BRACE")]
  57. Brace = 0x0080,
  58. /// <summary>
  59. /// Disable backslash escaping.
  60. /// </summary>
  61. [ImplementsConstant("GLOB_NOESCAPE")]
  62. NoEscape = 0x1000,
  63. /// <summary>
  64. /// List directories only.
  65. /// </summary>
  66. [ImplementsConstant("GLOB_ONLYDIR")]
  67. OnlyDir = 0x40000000,
  68. /// <summary>
  69. /// List directories only.
  70. /// </summary>
  71. [ImplementsConstant("GLOB_ERR")]
  72. StopOnError = 0x4
  73. }
  74. #endregion
  75. #region Scheme, Url, Absolute Path
  76. /// <summary>
  77. /// Wrapper-safe method of getting the schema portion from an URL.
  78. /// </summary>
  79. /// <param name="path">A <see cref="string"/> containing an URL or a local filesystem path.</param>
  80. /// <returns>
  81. /// The schema portion of the given <paramref name="url"/> or <c>"file"</c>
  82. /// for a local filesystem path.
  83. /// </returns>
  84. /// <exception cref="ArgumentException">Invalid path.</exception>
  85. internal static string GetScheme(string/*!*/ path)
  86. {
  87. int colon_index = path.IndexOf(':');
  88. // When there is not scheme present (or it's a local path) return "file".
  89. if (colon_index == -1 || Path.IsPathRooted(path))
  90. return "file";
  91. // Otherwise assume that it's the string before first ':'.
  92. return path.Substring(0, colon_index);
  93. }
  94. /// <summary>
  95. /// Concatenates a scheme with the given absolute path if necessary.
  96. /// </summary>
  97. /// <param name="absolutePath">Absolute path.</param>
  98. /// <returns>The given url or absolute path preceded by a <c>file://</c>.</returns>
  99. /// <exception cref="ArgumentException">Invalid path.</exception>
  100. internal static string GetUrl(string/*!*/ absolutePath)
  101. {
  102. // Assert that the path is absolute
  103. Debug.Assert(absolutePath.IndexOf(':') > 0);
  104. if (Path.IsPathRooted(absolutePath))
  105. return String.Concat("file://", absolutePath);
  106. // Otherwise assume that it's the string before first ':'.
  107. return absolutePath;
  108. }
  109. /// <summary>
  110. /// Returns the given filesystem url without the scheme.
  111. /// </summary>
  112. /// <param name="path">A path or url of a local filesystem file.</param>
  113. /// <returns>The filesystem path or <b>null</b> if the <paramref name="url"/> is not a local file.</returns>
  114. /// <exception cref="ArgumentException">Invalid path.</exception>
  115. internal static string GetFilename(string/*!*/ path)
  116. {
  117. if (path.IndexOf(':') == -1 || Path.IsPathRooted(path)) return path;
  118. if (path.IndexOf("file://") == 0) return path.Substring("file://".Length);
  119. return null;
  120. }
  121. /// <summary>
  122. /// Check if the given path is a path to a local file.
  123. /// </summary>
  124. /// <param name="url">The path to test.</param>
  125. /// <returns><c>true</c> if it's not a fully qualified name of a remote resource.</returns>
  126. /// <exception cref="ArgumentException">Invalid path.</exception>
  127. internal static bool IsLocalFile(string/*!*/ url)
  128. {
  129. return GetScheme(url) == "file";
  130. }
  131. /// <summary>
  132. /// Check if the given path is a remote url.
  133. /// </summary>
  134. /// <param name="url">The path to test.</param>
  135. /// <returns><c>true</c> if it's a fully qualified name of a remote resource.</returns>
  136. /// <exception cref="ArgumentException">Invalid path.</exception>
  137. internal static bool IsRemoteFile(string/*!*/ url)
  138. {
  139. return GetScheme(url) != "file";
  140. }
  141. /// <summary>
  142. /// Merges the path with the current working directory
  143. /// to get a canonicalized absolute pathname representing the same path
  144. /// (local files only). If the provided <paramref name="path"/>
  145. /// is absolute (rooted local path or an URL) it is returned unchanged.
  146. /// </summary>
  147. /// <param name="path">An absolute or relative path to a directory or an URL.</param>
  148. /// <returns>Canonicalized absolute path in case of a local directory or the original
  149. /// <paramref name="path"/> in case of an URL.</returns>
  150. internal static string AbsolutePath(string path)
  151. {
  152. // Don't combine remote file paths with CWD.
  153. try
  154. {
  155. if (IsRemoteFile(path))
  156. return path;
  157. // Remove the file:// schema if any.
  158. path = GetFilename(path);
  159. // Combine the path and simplify it.
  160. string combinedPath = Path.Combine(PhpDirectory.GetWorking(), path);
  161. // Note: GetFullPath handles "C:" incorrectly
  162. if (combinedPath[combinedPath.Length - 1] == ':') combinedPath += '\\';
  163. return Path.GetFullPath(combinedPath);
  164. }
  165. catch (Exception)
  166. {
  167. PhpException.Throw(PhpError.Notice,
  168. LibResources.GetString("invalid_path", FileSystemUtils.StripPassword(path)));
  169. return null;
  170. }
  171. }
  172. #endregion
  173. #region basename, dirname, pathinfo
  174. /// <summary>
  175. /// Returns path component of path.
  176. /// </summary>
  177. /// <param name="path">A <see cref="string"/> containing a path to a file.</param>
  178. /// <returns>The path conponent of the given <paramref name="path"/>.</returns>
  179. /// <exception cref="ArgumentException">Invalid path.</exception>
  180. [ImplementsFunction("basename")]
  181. [PureFunction]
  182. public static string GetBase(string path)
  183. {
  184. return GetBase(path, "");
  185. }
  186. /// <summary>
  187. /// Returns path component of path.
  188. /// </summary>
  189. /// <remarks>
  190. /// Given a <see cref="string"/> containing a path to a file, this function will return the base name of the file.
  191. /// If the path ends in this will also be cut off.
  192. /// On Windows, both slash (/) and backslash (\) are used as path separator character.
  193. /// In other environments, it is the forward slash (/).
  194. /// </remarks>
  195. /// <param name="path">A <see cref="string"/> containing a path to a file.</param>
  196. /// <param name="suffix">A <see cref="string"/> containing suffix to be cut off the path if present.</param>
  197. /// <returns>The path conponent of the given <paramref name="path"/>.</returns>
  198. [ImplementsFunction("basename")]
  199. [PureFunction]
  200. public static string GetBase(string path, string suffix)
  201. {
  202. if (String.IsNullOrEmpty(path)) return "";
  203. if (suffix == null) suffix = "";
  204. int end = path.Length - 1;
  205. while (end >= 0 && IsDirectorySeparator(path[end])) end--;
  206. int start = end;
  207. while (start >= 0 && !IsDirectorySeparator(path[start])) start--;
  208. start++;
  209. int name_length = end - start + 1;
  210. if (suffix.Length < name_length &&
  211. String.Compare(path, end - suffix.Length + 1, suffix, 0, suffix.Length, StringComparison.CurrentCultureIgnoreCase) == 0)
  212. {
  213. name_length -= suffix.Length;
  214. }
  215. return path.Substring(start, name_length);
  216. }
  217. private static bool IsDirectorySeparator(char c)
  218. {
  219. return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
  220. }
  221. /// <summary>
  222. /// Returns directory name component of path.
  223. /// </summary>
  224. /// <param name="path">The full path.</param>
  225. /// <returns>The directory portion of the given path.</returns>
  226. [ImplementsFunction("dirname")]
  227. [PureFunction]
  228. public static string GetDirectory(string path)
  229. {
  230. if (String.IsNullOrEmpty(path)) return null;
  231. int start = 0;
  232. int end = path.Length - 1;
  233. // advance start position beyond drive specifier:
  234. if (path.Length >= 2 && path[1] == ':' && (path[0] >= 'a' && path[0] <= 'z' || path[0] >= 'A' && path[0] <= 'Z'))
  235. {
  236. start = 2;
  237. if (path.Length == 2)
  238. return path;
  239. }
  240. // strip slashes from the end:
  241. while (end >= start && IsDirectorySeparator(path[end])) end--;
  242. if (end < start)
  243. return path.Substring(0, end + 1) + Path.DirectorySeparatorChar;
  244. // strip file name:
  245. while (end >= start && !IsDirectorySeparator(path[end])) end--;
  246. if (end < start)
  247. return path.Substring(0, end + 1) + '.';
  248. // strip slashes from the end:
  249. while (end >= start && IsDirectorySeparator(path[end])) end--;
  250. if (end < start)
  251. return path.Substring(0, end + 1) + Path.DirectorySeparatorChar;
  252. return path.Substring(0, end + 1);
  253. }
  254. /// <summary>
  255. /// Extracts parts from a specified path.
  256. /// </summary>
  257. /// <param name="path">The path to be parsed.</param>
  258. /// <returns>Array keyed by <c>"dirname"</c>, <c>"basename"</c>, and <c>"extension"</c>.
  259. /// </returns>
  260. [ImplementsFunction("pathinfo")]
  261. public static object GetInfo(string path)
  262. {
  263. return GetInfo(path, PathInfoOptions.All);
  264. }
  265. /// <summary>
  266. /// Extracts part(s) from a specified path.
  267. /// </summary>
  268. /// <param name="path">The path to be parsed.</param>
  269. /// <param name="options">Flags determining the result.</param>
  270. /// <returns>
  271. /// If <paramref name="options"/> is <see cref="PathInfoOptions.All"/> then returns array
  272. /// keyed by <c>"dirname"</c>, <c>"basename"</c>, and <c>"extension"</c>. Otherwise,
  273. /// it returns string value containing a single part of the path.
  274. /// </returns>
  275. [ImplementsFunction("pathinfo")]
  276. public static object GetInfo(string path, PathInfoOptions options)
  277. {
  278. // collect strings
  279. string dirname = null, basename = null, extension = null, filename = null;
  280. if ((options & PathInfoOptions.BaseName) != 0 ||
  281. (options & PathInfoOptions.Extension) != 0 ||
  282. (options & PathInfoOptions.FileName) != 0 )
  283. basename = GetBase(path);
  284. if ((options & PathInfoOptions.DirName) != 0)
  285. dirname = GetDirectory(path);
  286. if ((options & PathInfoOptions.Extension) != 0)
  287. {
  288. int last_dot = basename.LastIndexOf('.');
  289. if (last_dot >= 0)
  290. extension = basename.Substring(last_dot + 1);
  291. }
  292. if ((options & PathInfoOptions.FileName) != 0)
  293. {
  294. int last_dot = basename.LastIndexOf('.');
  295. if (last_dot >= 0)
  296. filename = basename.Substring(0, last_dot);
  297. else
  298. filename = basename;
  299. }
  300. // return requested value or all of them in an associative array
  301. if (options == PathInfoOptions.All)
  302. {
  303. PhpArray result = new PhpArray(0, 4);
  304. result.Add("dirname", dirname);
  305. result.Add("basename", basename);
  306. result.Add("extension", extension);
  307. result.Add("filename", filename);
  308. return result;
  309. }
  310. if ((options & PathInfoOptions.DirName) != 0)
  311. return dirname;
  312. if ((options & PathInfoOptions.BaseName) != 0)
  313. return basename;
  314. if ((options & PathInfoOptions.Extension) != 0)
  315. return extension;
  316. if ((options & PathInfoOptions.FileName) != 0)
  317. return filename;
  318. return null;
  319. }
  320. #endregion
  321. #region tempnam, realpath, sys_get_temp_dir
  322. /// <summary>
  323. /// Creates a file with a unique path in the specified directory.
  324. /// If the directory does not exist, <c>tempnam()</c> may generate
  325. /// a file in the system's temporary directory, and return the name of that.
  326. /// </summary>
  327. /// <param name="dir">The directory where the temporary file should be created.</param>
  328. /// <param name="prefix">The prefix of the unique path.</param>
  329. /// <returns>A unique path for a temporary file
  330. /// in the given <paramref name="directory"/>.</returns>
  331. [ImplementsFunction("tempnam")]
  332. public static string GetTemporaryFilename(string dir, string prefix)
  333. {
  334. // makes "dir" a valid directory:
  335. if (string.IsNullOrEmpty(dir) || !System.IO.Directory.Exists(dir))
  336. dir = Path.GetTempPath();
  337. // makes "prefix" a valid file prefix:
  338. if (prefix == null || prefix.Length == 0 || prefix.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
  339. prefix = "tmp_";
  340. string path = Path.Combine(dir, prefix);
  341. string result;
  342. for (; ; )
  343. {
  344. result = String.Concat(path, Interlocked.Increment(ref tempCounter), ".tmp");
  345. if (!File.Exists(result))
  346. {
  347. try
  348. {
  349. File.Open(result, FileMode.CreateNew).Close();
  350. break;
  351. }
  352. catch (UnauthorizedAccessException)
  353. {
  354. // try system temp directory:
  355. dir = Path.GetTempPath();
  356. path = Path.Combine(dir, prefix);
  357. }
  358. catch (PathTooLongException e)
  359. {
  360. PhpException.Throw(PhpError.Notice, PhpException.ToErrorMessage(e.Message));
  361. return Path.GetTempFileName();
  362. }
  363. catch (Exception)
  364. {
  365. }
  366. }
  367. }
  368. return result;
  369. }
  370. /// <summary>
  371. /// Returns the path of the directory PHP stores temporary files in by default.
  372. /// </summary>
  373. /// <returns>Returns the path of the temporary directory.</returns>
  374. /// <remarks>Path ends with "\"</remarks>
  375. [ImplementsFunction("sys_get_temp_dir")]
  376. public static string GetTempDirectoryName()
  377. {
  378. return Path.GetTempPath();
  379. }
  380. /// <summary>
  381. /// A counter used to generate unique filenames for <see cref="GetTemporaryFilename"/>.
  382. /// </summary>
  383. private static int tempCounter = 0;
  384. /// <summary>
  385. /// Returns canonicalized absolute path name.
  386. /// </summary>
  387. /// <param name="path">Arbitrary path.</param>
  388. /// <returns>
  389. /// The given <paramref name="path"/> combined with the current working directory or
  390. /// <B>null</B> (<B>false</B> in PHP) if the path is invalid or doesn't exists.
  391. /// </returns>
  392. [ImplementsFunction("realpath")]
  393. [return: CastToFalse]
  394. public static string RealPath(string path)
  395. {
  396. if (String.IsNullOrEmpty(path)) return null;
  397. // string ending slash
  398. if (IsDirectorySeparator(path[path.Length - 1]))
  399. path = path.Substring(0, path.Length - 1);
  400. string realpath = PhpPath.AbsolutePath(path);
  401. if (!File.Exists(realpath) && !System.IO.Directory.Exists(realpath))
  402. {
  403. return null;
  404. }
  405. return realpath;
  406. }
  407. #endregion
  408. #region NS: fnmatch, glob
  409. /// <summary>
  410. /// Matches the given path against a pattern. Not supported.
  411. /// </summary>
  412. /// <param name="pattern">A <see cref="string"/> containing a wildcard.</param>
  413. /// <param name="path">The <see cref="string"/> to be matched.</param>
  414. /// <returns><c>true</c> if the <paramref name="path"/> matches with the given
  415. /// wildcard <paramref name="pattern"/>.</returns>
  416. [ImplementsFunction("fnmatch")]
  417. public static bool Match(string pattern, string path)
  418. {
  419. return Match(pattern, path, 0);
  420. }
  421. /// <summary>
  422. /// Matches the given path against a pattern. Not supported.
  423. /// </summary>
  424. /// <param name="pattern">A <see cref="string"/> containing a wildcard.</param>
  425. /// <param name="path">The <see cref="string"/> to be matched.</param>
  426. /// <param name="flags">Additional flags (undocumented in PHP).</param>
  427. /// <returns><c>true</c> if the <paramref name="path"/> matches with the given
  428. /// wildcard <paramref name="pattern"/>.</returns>
  429. [ImplementsFunction("fnmatch")]
  430. public static bool Match(string pattern, string path, int flags)
  431. {
  432. PhpException.FunctionNotSupported();
  433. return false;
  434. }
  435. /// <summary>
  436. /// Find pathnames matching a pattern.
  437. /// </summary>
  438. [ImplementsFunction("glob")]
  439. public static PhpArray Glob(string pattern)
  440. {
  441. return Glob(pattern, GlobOptions.None);
  442. }
  443. /// <summary>
  444. /// Find pathnames matching a pattern.
  445. /// </summary>
  446. [ImplementsFunction("glob")]
  447. public static PhpArray Glob(string pattern, GlobOptions flags)
  448. {
  449. if (pattern == null)
  450. return new PhpArray(0, 0);
  451. PhpArray result = new PhpArray();
  452. if (pattern == "")
  453. {
  454. if ((flags & GlobOptions.NoCheck) != 0) result.Add(pattern);
  455. return result;
  456. }
  457. // drive specification is present:
  458. string root;
  459. int colon = pattern.IndexOf(':');
  460. if (colon != -1)
  461. {
  462. root = pattern.Substring(0, colon + 1);
  463. pattern = pattern.Substring(colon + 1);
  464. }
  465. else
  466. {
  467. root = null;
  468. }
  469. List<string> components = GlobSplit(pattern, flags);
  470. components.Add(((flags & GlobOptions.Mark) != 0) ? Path.DirectorySeparatorChar.ToString() : "");
  471. Debug.Assert(components.Count % 2 == 0);
  472. try
  473. {
  474. if (components[0] == "")
  475. {
  476. if (root == null)
  477. {
  478. try { root = Path.GetPathRoot(ScriptContext.CurrentContext.WorkingDirectory); }
  479. catch { }
  480. if (String.IsNullOrEmpty(root))
  481. root = Path.GetPathRoot(Environment.CurrentDirectory);
  482. // remove ending slash:
  483. root = root.Substring(0, root.Length - 1);
  484. }
  485. // start with root dir:
  486. GlobRecursive(components, 2, root + "\\", root + components[1], flags, result);
  487. }
  488. else
  489. {
  490. GlobRecursive(components, 0, ScriptContext.CurrentContext.WorkingDirectory, "", flags, result);
  491. }
  492. }
  493. catch (ArgumentNullException)
  494. {
  495. throw;
  496. }
  497. catch (Exception)
  498. {
  499. }
  500. if (result.Count == 0 && (flags & GlobOptions.NoCheck) != 0) result.Add(pattern);
  501. return result;
  502. }
  503. private static void GlobRecursive(List<string>/*!*/ components, int index, string/*!*/ currentDir, string/*!*/ prefix,
  504. GlobOptions flags, PhpArray/*!*/ result)
  505. {
  506. bool is_last_component = index == components.Count - 2;
  507. string pattern = components[index];
  508. string separator = components[index + 1];
  509. Debug.Assert(!String.IsNullOrEmpty(pattern) && separator != null);
  510. string[] items;
  511. bool[] is_dir;
  512. try
  513. {
  514. GlobReadDirectoryContent(currentDir, flags, out items, out is_dir);
  515. }
  516. catch (ArgumentNullException)
  517. {
  518. throw;
  519. }
  520. catch (Exception)
  521. {
  522. if ((flags & GlobOptions.StopOnError) != 0) throw;
  523. return;
  524. }
  525. Regex regex = new Regex(pattern, RegexOptions.Singleline);
  526. for (int i = 0; i < items.Length; i++)
  527. {
  528. Match match = regex.Match(items[i]);
  529. if (match.Success && match.Index == 0 && match.Length == items[i].Length)
  530. {
  531. if (is_last_component || !is_dir[i])
  532. {
  533. if (is_dir[i])
  534. result.Add(String.Concat(prefix, items[i], separator));
  535. else
  536. result.Add(String.Concat(prefix, items[i]));
  537. }
  538. else
  539. {
  540. GlobRecursive(components, index + 2, Path.Combine(currentDir, items[i]),
  541. String.Concat(prefix, items[i], separator), flags, result);
  542. }
  543. }
  544. }
  545. }
  546. private static void GlobReadDirectoryContent(string/*!*/ currentDir, GlobOptions flags, out string[] items, out bool[] isDir)
  547. {
  548. string[] directories = System.IO.Directory.GetDirectories(currentDir);
  549. string[] files;
  550. if ((flags & GlobOptions.OnlyDir) == 0)
  551. files = System.IO.Directory.GetFiles(currentDir);
  552. else
  553. files = ArrayUtils.EmptyStrings;
  554. items = new string[directories.Length + files.Length];
  555. isDir = new bool[items.Length];
  556. for (int i = 0; i < items.Length; i++)
  557. {
  558. items[i] = Path.GetFileName((i < directories.Length) ? directories[i] : files[i - directories.Length]);
  559. isDir[i] = i < directories.Length;
  560. }
  561. if ((flags & GlobOptions.NoSort) == 0)
  562. Array.Sort<string, bool>(items, isDir);
  563. }
  564. private static List<string> GlobSplit(string/*!*/ pattern, GlobOptions flags)
  565. {
  566. List<string> result = new List<string>();
  567. int component_start = 0;
  568. int i = 0;
  569. while (i < pattern.Length)
  570. {
  571. if (pattern[i] == '\\' || pattern[i] == '/')
  572. {
  573. result.Add(PatternToRegExPattern(pattern.Substring(component_start, i - component_start), flags));
  574. component_start = i;
  575. i++;
  576. while (i < pattern.Length && (pattern[i] == '\\' || pattern[i] == '/')) i++;
  577. result.Add(pattern.Substring(component_start, i - component_start));
  578. component_start = i;
  579. }
  580. else
  581. {
  582. i++;
  583. }
  584. }
  585. if (component_start < pattern.Length)
  586. result.Add(PatternToRegExPattern(pattern.Substring(component_start, i - component_start), flags));
  587. return result;
  588. }
  589. private static string/*!*/ PatternToRegExPattern(string/*!*/ pattern, GlobOptions flags)
  590. {
  591. StringBuilder result = new StringBuilder(pattern.Length);
  592. int i = 0;
  593. while (i < pattern.Length)
  594. {
  595. switch (pattern[i])
  596. {
  597. case '*':
  598. result.Append(".*");
  599. i++;
  600. break;
  601. case '?':
  602. result.Append('.');
  603. i++;
  604. break;
  605. case '{':
  606. if ((flags & GlobOptions.Brace) != 0)
  607. {
  608. int closing = IndexOfClosingBrace(pattern, i, '{', '}');
  609. if (closing > i)
  610. {
  611. while (i <= closing)
  612. {
  613. switch (pattern[i])
  614. {
  615. case '{': result.Append('('); break;
  616. case '}': result.Append(')'); break;
  617. case ',': result.Append('|'); break;
  618. default:
  619. result.Append('[');
  620. result.Append(pattern[i]);
  621. result.Append(']');
  622. break;
  623. }
  624. i++;
  625. }
  626. }
  627. else
  628. {
  629. result.Append("\\{");
  630. i++;
  631. }
  632. }
  633. else
  634. {
  635. result.Append("\\{");
  636. i++;
  637. }
  638. break;
  639. case '[':
  640. {
  641. int closing = pattern.IndexOf(']', i + 1);
  642. if (closing > i + 1)
  643. {
  644. int result_length = result.Length;
  645. result.Append(pattern, i, closing - i + 1);
  646. if (result[result_length + 1] == '!')
  647. {
  648. if (result[result_length + 2] == ']')
  649. {
  650. result[result_length + 1] = '\\';
  651. result[result_length + 2] = '^';
  652. result.Append(']');
  653. }
  654. else
  655. result[result_length + 1] = '^';
  656. }
  657. i = closing + 1;
  658. }
  659. else
  660. {
  661. result.Append("\\[");
  662. i++;
  663. }
  664. break;
  665. }
  666. case ']':
  667. result.Append("\\]");
  668. i++;
  669. break;
  670. case '^':
  671. result.Append("\\^");
  672. i++;
  673. break;
  674. default:
  675. result.Append('[');
  676. result.Append(pattern[i]);
  677. result.Append(']');
  678. i++;
  679. break;
  680. }
  681. }
  682. return result.ToString();
  683. }
  684. private static int IndexOfClosingBrace(string/*!*/ str, int start, char left, char right)
  685. {
  686. int count = 0;
  687. for (int i = start; i < str.Length; i++)
  688. {
  689. if (str[i] == left)
  690. {
  691. count++;
  692. }
  693. else if (str[i] == right)
  694. {
  695. if (count == 1)
  696. return i;
  697. if (count == 0)
  698. return -1;
  699. count--;
  700. }
  701. }
  702. return -1;
  703. }
  704. #region Unit Tests
  705. #if DEBUG
  706. [Test]
  707. private static void TestPatternToRegExPattern()
  708. {
  709. string[] cases = new string[] { "*.*", "?", "{Ne,{Ss,Se},Pr}*", "[a-z][!A][!]", "{Ne,{Ss,Se,Pr}", "[", "[!", "[a", "[]", "[!]" };
  710. string[] results = new string[]
  711. { ".*[.].*", ".", "([N][e]|([S][s]|[S][e])|[P][r]).*",
  712. "[a-z][^A][\\^]", @"\{[N][e][,]([S][s]|[S][e]|[P][r])", @"\[", @"\[[!]", @"\[[a]", "\\[\\]", "[\\^]" };
  713. for (int i = 0; i < cases.Length; i++)
  714. {
  715. string s = PatternToRegExPattern(cases[i], GlobOptions.Brace);
  716. Debug.Assert(results[i] == s);
  717. new Regex(results[i]);
  718. }
  719. }
  720. [Test]
  721. private static void TestGlobSplit()
  722. {
  723. string[] cases = new string[] { @"C:\D\", @"\x\y", @"\/\/\/\x\/\/xx\" };
  724. string[] results = new string[] { @"[C][:];\;[D];\", @";\;[x];\;[y]", @";\/\/\/\;[x];\/\/;[x][x];\" };
  725. for (int i = 0; i < cases.Length; i++)
  726. {
  727. List<string> al = GlobSplit(cases[i], GlobOptions.Brace);
  728. string[] sal = new string[al.Count];
  729. for (int j = 0; j < al.Count; j++)
  730. sal[j] = al[j];
  731. string s = String.Join(";", sal);
  732. Debug.Assert(results[i] == s);
  733. }
  734. }
  735. // [Test(true)]
  736. private static void TestGlob()
  737. {
  738. PhpArray result;
  739. PhpVariable.Dump(result = Glob(@"C:\*"));
  740. PhpVariable.Dump(result = Glob(@"//\*"));
  741. PhpVariable.Dump(result = Glob(@"/[xW]*/*"));
  742. PhpVariable.Dump(result = Glob(@"C:\Web/{E,S}*/???", GlobOptions.Brace | GlobOptions.Mark));
  743. }
  744. #endif
  745. #endregion
  746. #endregion
  747. }
  748. }