Getting path relative to the current working directory? [duplicate] - c#

This question already has answers here:
How to get relative path from absolute path
(24 answers)
Closed 7 years ago.
I'm writing a console utility to do some processing on files specified on the commandline, but I've run into a problem I can't solve through Google/Stack Overflow. If a full path, including drive letter, is specified, how do I reformat that path to be relative to the current working directory?
There must be something similar to the VirtualPathUtility.MakeRelative function, but if there is, it eludes me.

If you don't mind the slashes being switched, you could [ab]use Uri:
Uri file = new Uri(#"c:\foo\bar\blop\blap.txt");
// Must end in a slash to indicate folder
Uri folder = new Uri(#"c:\foo\bar\");
string relativePath =
Uri.UnescapeDataString(
folder.MakeRelativeUri(file)
.ToString()
.Replace('/', Path.DirectorySeparatorChar)
);
As a function/method:
string GetRelativePath(string filespec, string folder)
{
Uri pathUri = new Uri(filespec);
// Folders must end in a slash
if (!folder.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
folder += Path.DirectorySeparatorChar;
}
Uri folderUri = new Uri(folder);
return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString().Replace('/', Path.DirectorySeparatorChar));
}

You can use Environment.CurrentDirectory to get the current directory, and FileSystemInfo.FullPath to get the full path to any location. So, fully qualify both the current directory and the file in question, and then check whether the full file name starts with the directory name - if it does, just take the appropriate substring based on the directory name's length.
Here's some sample code:
using System;
using System.IO;
class Program
{
public static void Main(string[] args)
{
string currentDir = Environment.CurrentDirectory;
DirectoryInfo directory = new DirectoryInfo(currentDir);
FileInfo file = new FileInfo(args[0]);
string fullDirectory = directory.FullName;
string fullFile = file.FullName;
if (!fullFile.StartsWith(fullDirectory))
{
Console.WriteLine("Unable to make relative path");
}
else
{
// The +1 is to avoid the directory separator
Console.WriteLine("Relative path: {0}",
fullFile.Substring(fullDirectory.Length+1));
}
}
}
I'm not saying it's the most robust thing in the world (symlinks could probably confuse it) but it's probably okay if this is just a tool you'll be using occasionally.

public string MakeRelativePath(string workingDirectory, string fullPath)
{
string result = string.Empty;
int offset;
// this is the easy case. The file is inside of the working directory.
if( fullPath.StartsWith(workingDirectory) )
{
return fullPath.Substring(workingDirectory.Length + 1);
}
// the hard case has to back out of the working directory
string[] baseDirs = workingDirectory.Split(new char[] { ':', '\\', '/' });
string[] fileDirs = fullPath.Split(new char[] { ':', '\\', '/' });
// if we failed to split (empty strings?) or the drive letter does not match
if( baseDirs.Length <= 0 || fileDirs.Length <= 0 || baseDirs[0] != fileDirs[0] )
{
// can't create a relative path between separate harddrives/partitions.
return fullPath;
}
// skip all leading directories that match
for (offset = 1; offset < baseDirs.Length; offset++)
{
if (baseDirs[offset] != fileDirs[offset])
break;
}
// back out of the working directory
for (int i = 0; i < (baseDirs.Length - offset); i++)
{
result += "..\\";
}
// step into the file path
for (int i = offset; i < fileDirs.Length-1; i++)
{
result += fileDirs[i] + "\\";
}
// append the file
result += fileDirs[fileDirs.Length - 1];
return result;
}
This code is probably not bullet-proof but this is what I came up with. It's a little more robust. It takes two paths and returns path B as relative to path A.
example:
MakeRelativePath("c:\\dev\\foo\\bar", "c:\\dev\\junk\\readme.txt")
//returns: "..\\..\\junk\\readme.txt"
MakeRelativePath("c:\\dev\\foo\\bar", "c:\\dev\\foo\\bar\\docs\\readme.txt")
//returns: "docs\\readme.txt"

Thanks to the other answers here and after some experimentation I've created some very useful extension methods:
public static string GetRelativePathFrom(this FileSystemInfo to, FileSystemInfo from)
{
return from.GetRelativePathTo(to);
}
public static string GetRelativePathTo(this FileSystemInfo from, FileSystemInfo to)
{
Func<FileSystemInfo, string> getPath = fsi =>
{
var d = fsi as DirectoryInfo;
return d == null ? fsi.FullName : d.FullName.TrimEnd('\\') + "\\";
};
var fromPath = getPath(from);
var toPath = getPath(to);
var fromUri = new Uri(fromPath);
var toUri = new Uri(toPath);
var relativeUri = fromUri.MakeRelativeUri(toUri);
var relativePath = Uri.UnescapeDataString(relativeUri.ToString());
return relativePath.Replace('/', Path.DirectorySeparatorChar);
}
Important points:
Use FileInfo and DirectoryInfo as method parameters so there is no ambiguity as to what is being worked with. Uri.MakeRelativeUri expects directories to end with a trailing slash.
DirectoryInfo.FullName doesn't normalize the trailing slash. It outputs whatever path was used in the constructor. This extension method takes care of that for you.

There is also a way to do this with some restrictions. This is the code from the article:
public string RelativePath(string absPath, string relTo)
{
string[] absDirs = absPath.Split('\\');
string[] relDirs = relTo.Split('\\');
// Get the shortest of the two paths
int len = absDirs.Length < relDirs.Length ? absDirs.Length : relDirs.Length;
// Use to determine where in the loop we exited
int lastCommonRoot = -1; int index;
// Find common root
for (index = 0; index < len; index++)
{
if (absDirs[index] == relDirs[index])
lastCommonRoot = index;
else break;
}
// If we didn't find a common prefix then throw
if (lastCommonRoot == -1)
{
throw new ArgumentException("Paths do not have a common base");
}
// Build up the relative path
StringBuilder relativePath = new StringBuilder();
// Add on the ..
for (index = lastCommonRoot + 1; index < absDirs.Length; index++)
{
if (absDirs[index].Length > 0) relativePath.Append("..\\");
}
// Add on the folders
for (index = lastCommonRoot + 1; index < relDirs.Length - 1; index++)
{
relativePath.Append(relDirs[index] + "\\");
}
relativePath.Append(relDirs[relDirs.Length - 1]);
return relativePath.ToString();
}
When executing this piece of code:
string path1 = #"C:\Inetpub\wwwroot\Project1\Master\Dev\SubDir1";
string path2 = #"C:\Inetpub\wwwroot\Project1\Master\Dev\SubDir2\SubDirIWant";
System.Console.WriteLine (RelativePath(path1, path2));
System.Console.WriteLine (RelativePath(path2, path1));
it prints out:
..\SubDir2\SubDirIWant
..\..\SubDir1

Related

C# Extract json object from mixed data text/js file

I need to parse reactjs file in main.451e57c9.js to retrieve version number with C#.
This file contains mixed data, here is little part of it:
.....inally{if(s)throw i}}return a}}(e,t)||xe(e,t)||we()}var Se=
JSON.parse('{"shortVersion":"v3.1.56"}')
,Ne="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA
AASAAAAAqCAYAAAATb4ZSAAAACXBIWXMAAAsTAAALEw.....
I need to extract json data of {"shortVersion":"v3.1.56"}
The last time I tried to simply find the string shortVersion and return a certain number of characters after, but it seems like I'm trying to create the bicycle from scratch. Is there proper way to identify and extract json from the mixed text?
public static void findVersion()
{
var partialName = "main.*.js";
string[] filesInDir = Directory.GetFiles(#pathToFile, partialName);
var lines = File.ReadLines(filesInDir[0]);
foreach (var line in File.ReadLines(filesInDir[0]))
{
string keyword = "shortVersion";
int indx = line.IndexOf(keyword);
if (indx != -1)
{
string code = line.Substring(indx + keyword.Length);
Console.WriteLine(code);
}
}
}
RESULT
":"v3.1.56"}'),Ne="data:image/png;base64,iVBORw0KGgoAA.....
string findJson(string input, string keyword) {
int startIndex = input.IndexOf(keyword) - 2; //Find the starting point of shortversion then subtract 2 to start at the { bracket
input = input.Substring(startIndex); //Grab everything after the start index
int endIndex = 0;
for (int i = 0; i < input.Length; i++) {
char letter = input[i];
if (letter == '}') {
endIndex = i; //Capture the first instance of the closing bracket in the new trimmed input string.
break;
}
}
return input.Remove(endIndex+1);
}
Console.WriteLine(findJson("fwekjfwkejwe{'shortVersion':'v3.1.56'}wekjrlklkj23klj23jkl234kjlk", "shortVersion"));
You will recieve {'shortVersion':'v3.1.56'} as output
Note you may have to use line.Replace('"', "'");
Try below method -
public static object ExtractJsonFromText(string mixedStrng)
{
for (var i = mixedStrng.IndexOf('{'); i > -1; i = mixedStrng.IndexOf('{', i + 1))
{
for (var j = mixedStrng.LastIndexOf('}'); j > -1; j = mixedStrng.LastIndexOf("}", j -1))
{
var jsonProbe = mixedStrng.Substring(i, j - i + 1);
try
{
return JsonConvert.DeserializeObject(jsonProbe);
}
catch
{
}
}
}
return null;
}
Fiddle
https://dotnetfiddle.net/N1jiWH
You should not use GetFiles() since you only need one and that returns all before you can do anything. This should give your something you can work with here and it should be as fast as it likely can be with big files and/or lots of files in a folder (to be fair I have not tested this on such a large file system or file)
using System;
using System.IO;
using System.Linq;
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
var path = $#"c:\SomePath";
var jsonString = GetFileVersion(path);
if (!string.IsNullOrWhiteSpace(jsonString))
{
// do something with string; deserialize or whatever.
var result=JsonConvert.DeserializeObject<List<Version>>(jsonString);
var vers = result.shortVersion;
}
}
private static string GetFileVersion(string path)
{
var partialName = "main.*.js";
// JSON string fragment to find: doubled up braces and quotes for the $# string
string matchString = $#"{{""shortVersion"":";
string matchEndString = $#" ""}}'";
// we can later stop on the first match
DirectoryInfo dir = new DirectoryInfo(path);
if (!dir.Exists)
{
throw new DirectoryNotFoundException("The directory does not exist.");
}
// Call the GetFileSystemInfos method and grab the first one
FileSystemInfo info = dir.GetFileSystemInfos(partialName).FirstOrDefault();
if (info.Exists)
{
// walk the file contents looking for a match (assumptions made here there IS a match and it has that string noted)
var line = File.ReadLines(info.FullName).SkipWhile(line => !line.Contains(matchString)).Take(1).First();
var indexStart = line.IndexOf(matchString);
var indexEnd = line.IndexOf(matchEndString, indexStart);
var jsonString = line.Substring(indexStart, indexEnd + matchEndString.Length);
return jsonString;
}
return string.Empty;
}
public class Version
{
public string shortVersion { get; set; }
}
}
Use this this should be faster - https://dotnetfiddle.net/sYFvYj
public static object ExtractJsonFromText(string mixedStrng)
{
string pattern = #"\(\'\{.*}\'\)";
string str = null;
foreach (Match match in Regex.Matches(mixedStrng, pattern, RegexOptions.Multiline))
{
if (match.Success)
{
str = str + Environment.NewLine + match;
}
}
return str;
}

Get directory path n deph long of a given root path

I am looking for a better/safer/more elegant generic method that could give me an n depth long directory from a given path.
I've created something that works but it based on string parsing so I hope you could find a better solution. The method could use a directory info for passed/returned values as well.
public static string GetDirectoryNDepth(string root, string target, int depth)
{
string[] splittedRoot = root.Split('\\');
string[] splittedTarget = target.Split('\\');
StringBuilder sb = new StringBuilder();
for (int i = 0; i < splittedTarget.Length; i++)
if (i < splittedRoot.Count() + depth)
sb.Append(String.Format("{0}\\", splittedTarget[i]));
else
break;
return sb.ToString();
}
Sample values:
//For 3 depth long parametr it should return expected value
//First case filepath
string root = #"C\Desktop\temp\MSC\IH";
string target = #"C:\Desktop\temp\MSC\IH\FirstLevel\SecondLevel\ThirdLevel\dsf - Copy (2).xml";
string expected = #"C:\Desktop\temp\MSC\IH\FirstLevel\SecondLevel\ThirdLevel";
//Second case target shorter then depth
string root = #"C\Desktop\temp\MSC\IH";
string target = #"C:\Desktop\temp\MSC\IH\FirstLevel\SecondLevel";
string expected = #"C:\Desktop\temp\MSC\IH\FirstLevel\SecondLevel";
From your comment, instead of using \ as the separator, you can use Path.DirectorySeparatorChar instead like this
public string GetDirectoryNDepth(string root, string target, int depth)
{
string[] splittedRoot = root.Split(Path.DirectorySeparatorChar);
string[] splittedTarget = target.Split(Path.DirectorySeparatorChar);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < splittedTarget.Length; i++)
if (i < splittedRoot.Length + depth)
sb.Append(String.Format("{0}{1}", splittedTarget[i], Path.DirectorySeparatorChar));
else
break;
return sb.ToString();
}
From the Path.DirectorySeparatorChar doc:
Provides a platform-specific character used to separate directory
levels in a path string that reflects a hierarchical file system
organization...
...The value of this field is a slash ("/") on UNIX, and a backslash
("\") on the Windows and Macintosh operating systems.
I found input set very particular that root doesn't start with C: it is C\
don't know whether it is purposefully like that
string root = #"C\Desktop\temp\MSC\IH";
string target = #"C:\Desktop\temp\MSC\IH\FirstLevel\SecondLevel\ThirdLevel\dsf - Copy (2).xml";
string expected = #"C:\Desktop\temp\MSC\IH\FirstLevel\SecondLevel\ThirdLevel";
Based upon input sample & code this would be efficient code
public static string GetDirectoryNDepth(string root, string target, int depth)
{
var separatorChar = Path.DirectorySeparatorChar;
int rootSlashCount = root.Split(separatorChar).Length;
int totalSlashCount = rootSlashCount + depth;
var iCnt = root.Length;
while(iCnt < target.Length && rootSlashCount <= totalSlashCount)
{
if (target[iCnt] == separatorChar)
rootSlashCount++;
iCnt++;
}
var retVal = target.Substring(0, iCnt);
if (retVal.EndsWith(separatorChar+"") == false)
retVal += separatorChar;
return retVal;
}

Auto Incrementing file names?

I have a list of files like so
abc.txt
pas.txt
tempr.txt
What I would like to do is to append english alphabets to theese file names ..
the result should look like this
abc_a.txt
pas_b.txt
tempr_c.txt
This process should continue till the last character (i.e 'z'). if there are more files then the file names would become
abc_a.txt
pas_b.txt
tempr_c.txt
.................
filename_z.txt
anotherfilename_a001.txt
Notice that the counter was again reset to the first character except an integer was attached to it.
This is the code that i have right now. Please note that it is NOT working ..
string alphabets= "abcdefghijklmnopqrstuvwxyz";
List<string> filenames = new List<string>();
filenames.Add("test1.txt");
filenames.Add("newfile.cs");
filenames.Add("test2.txt");
filenames.Add("newfile2.cs");
string currentFileNmae = string.Empty;
foreach(string s in filenames) {
char usedAlphabet = new char();
for(int i = 0;i<=alphabets.Length-1;i+=11) {
usedAlphabet.Dump();
alphabets[i].Dump();
if(usedAlphabet != alphabets[i] )
{
if(currentFileNmae!= s)
{
string.Format("{0}--{1}",s,alphabets[i]).Dump();
usedAlphabet = alphabets[i];
currentFileNmae = s;
}
}
break;
}
}
I am part of a team that's building a file renamer tool for our internal purposes and hence i need this code. This is part of the our enumertation functionality that we have planned.
Please suggest.
thanks
Try starting here:
using System.Diagnostics;
using System.IO;
string filename = #"C:\Foo\Bar.txt";
for (int count = 0; count < 100; count++)
{
char letter = (char)((int)'a' + count % 26);
string numeric = (count / 26) == 0 ? "" : (count / 26).ToString("000");
Debug.Print(Path.GetFileNameWithoutExtension(filename) + "_" + letter + numeric + Path.GetExtension(filename));
}
Substitute your own loop to go through the filenames and use Path to manipulate the pieces/parts of the names.
The renaming, IIRC, can be handled by File.Move. Surround it with a try/catch to implement the name collision logic.
Had no coffee yet, but this should do.
List<string> files = new List<string>();
int charIndex = 0;
int numericIndex = -1;
foreach (var file in files.Select(path => new FileInfo(path)))
{
// Create new Filename - This may needs some tuning
// to really remove only the extension ad the end
// It doesnt take care of things like
// file.bmp.bmp.bmp ...
string newFileName = String.Format("{0}_{1}{2}.{3}",
file.FullName.Replace(file.Extension,String.Empty),
(char)(charIndex++ + 97),
(numericIndex > -1 ? String.Format("{0:D4}", numericIndex) : String.Empty),
file.Extension);
// Rename the File
file.MoveTo(newFileName);
// Increment Counters.
if (charIndex > 25)
{
charIndex = 0;
numericIndex++;
}
}
You can try something like this
const string directory = #"C:\\wherever";
string[] fiNames = new string[]{ "abc", "pas", "etc",};
char[] alphabet = "abcdefghijklmnopqrstuvwxyz".ToCharArray();
int x = 0;
string ending = "";
for(int i = fiNames.Count()-1; i>=0; i--)
{
if(x%26==0)
{
x=0
if( ending=="")
ending="1";
else
ending=(System.Convert.ToInt32(ending)+1).ToString();
}
System.IO.File.Move(directory+fiNames[i], fiNames[i]+alphabet[x].ToString()+ending);
x++;
}

How can i get from a directory string only the last subdirectory name and the file name?

I have this line and how can I get only the last subdirectory name and the file name ?
label13.Text = Path.GetFileName(file1);
Im getting only the file name: test.avi
If im not using the Path.GetFileName only file1 I will get something like:
http://users/test/test/program/test/test.avi
What I want to get is the last subdirectory name wich is: test and the file name: test.avi
So in label13 I will see: test/test.avi
How can I do it ?
Just using Path:
Path.Combine(Path.GetFileName(Path.GetDirectoryName(path)), Path.GetFileName(path))
You can also split the string and grab the last 2 elements of the resulting array:
string path = "http://users/test/test/program/test/test.avi";
var elements = path.Split('/');
string result = elements[elements.Length-1] + "/" + elements[elements.Length];
System.Console.WriteLine(result);
You can use the following extension method to retrieve the character index to the nth-last index of the path separator and the return the correct substring:
using System;
using System.Linq;
public static class StringExtensions
{
public static int NthLastIndexOf(this string value, char c, int n = 1)
{
if (count < 1)
{
throw new ArgumentOutOfRangeException("count must be greater than 0.");
}
var index = 1;
for (int i = value.Length - 1; i >= 0; i--)
{
if (value[i] == c)
{
if (index == n)
{
return i;
}
index++;
}
}
return -1;
}
}
class Program
{
public static string GetEndOfPath(string path)
{
var idx = path.NthLastIndexOf('/', 2);
if (idx == -1)
{
throw new ArgumentException("path does not contain two separators.");
}
return path.Substring(idx + 1);
}
static void Main()
{
var result = GetEndOfPath("http://users/test/test/program/test/test.avi");
Console.WriteLine(result);
}
}
The extension method NthLastIndexOf returns the zero-based index position of the nth-last occurrence of a specified Unicode character. The method returns -1 if the character is not found at least n times in the string.

C# Sequential file output

I have a C# winform application that is outputting to excel files.
Let's say the name format of the file name is: Output1.xlsl
I would like to have the output saved to another sequential file on each button click/execution.
So next it would be Output2.xlsl, Output3.xlsl... etc.
How to check that, I know of checking if the file exists, but how to check for the numbering?
FileInfo newExcelFile = new FileInfo(#"Output1.xlsx");
if (newExcelFile.Exists)
{
...
}
You could use this loop and File.Exists with Path.Combine:
string directory = #"C:\SomeDirectory";
string fileName = #"Output{0}.xlsx";
int num = 1;
while (File.Exists(Path.Combine(directory, string.Format(fileName, num))))
num++;
var newExcelFile = new FileInfo(Path.Combine(directory, string.Format(fileName, num)));
In general the static File methods are more efficient than always creating a FileInfo instance.
We use a method similar to this to achieve this:
/// <param name="strNewPath">ex: c:\</param>
/// <param name="strFileName">ex: Output.xlsx</param>
/// <returns>Next available filename, ex: Output3.xlsx</returns>
public static string GetValidFileName(string strNewPath, string strFileName)
{
var strFileNameNoExt = Path.GetFileNameWithoutExtension(strFileName);
var strExtension = Path.GetExtension(strFileName);
var intCount = 1;
while (File.Exists(Path.Combine(strNewPath, strFileNameNoExt + intCount + strExtension)))
intCount++;
return Path.Combine(strNewPath, strFileNameNoExt + intCount + strExtension);
}
Just wrap it in a while loop
int num = 1;
FileInfo newExcelFile = new FileInfo("Output1.xlsx");
while(newExcelFile.Exists)
{
newExcelFile = new FileInfo("Output" + num + ".xlsx");
num++;
}
I would find the newest file in the folder and use its number as a basis to start from. If there are no other programs to write there, this should be sufficient.
DirectoryInfo di = new DirectoryInfo("Some folder");
FileInfo fi = di.GetFiles().OrderByDescending(s => s.CreationTime).First();
string fileName = fi.Name;
//....
You can do a simple loop:
FileInfo newExcelFile = null;
for (int i = 0; i < int.MaxValue; i++)
{
newExcelFile = new FileInfo(string.Format(#"Output{0}.xlsx", i));
if (!newExcelFile.Exists)
{
break;
}
newExcelFile = null;
}
if (newExcelFile == null)
{
// do you want to try 2147483647
// or show an error message
// or throw an exception?
}
else
{
// save your file
}
It may not be most efficient one but I can suggest following solution
split the file name with "."
Remove substring "Output" from it
Now sort to get the maximum number.
It depends on the logic. What should happen if you had Output1.xlsx Output2.xlsx Output3.xlsx and removed Output2.xlsx, should the new file be Output2.xlsx or Output4.xlsx?
If you want to have always the highest number for the new files, you can use similar code
int lastNum = 0;
string[] files = Directory.GetFiles("c:\\myDir", "Output*.xlsx");
if (files.Length > 0)
{
Array.Sort(files);
lastNum = Convert.ToInt32(Regex.Match(files[files.Length - 1], "Output[\\d](*).xlsx").Result("$1"));
lastNum++;
}
FileInfo newExcelFile = new FileInfo("Output" + lastNum + ".xlsx");
Of course you can loop, but it's not a good idea if you have thousands of files. For small amount of files it could be fine
int i = 0;
for (; i < Int32.MaxValue; i++)
{
if (File.Exists("Output" + i + ".xlsx"))
break;
}

Categories