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

/2.1/Source/ClassLibrary/FileSystem.Path.cs

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