Creating xml from directory structure - c#

I have a directory with files and sub directories with files and want to create xml from them. here is my folder structure:
C:\inputdata folder contains:
C:\inputdata\file1.txt
C:\inputdata\picture1.jpg
C:\inputdata\subfolder\picture2.jpg
C:\inputdata\subfolder\file2.txt
C:\inputdata\subfolder\anotherfolder \file3.txt
C:\inputdata\anotherfolder\
and i want to generate this xml file:
<?xml version="1.0" encoding="UTF-8"?>
<serverfiles>
<file name="picture1.jpg"/>
<file name="file1.txt"/>
<folder name="subfolder">
<file name="picture2.jpg"/>
<file name="file2.txt"/>
<folder name="anotherfolder">
<file name="file3.txt"/>
</folder>
</folder>
<folder name="anotherfolder">
</folder>
</serverfiles>
I have written following console app but i have two problems.
this produces the attached screenshot xml which is not exactly as above xml in terms of structure.
is there a way I could sort this with the name attribute with my code.
can someone please point me to right direction of how to do this please:
private const string folderLocation = #"c:\inputdata";
static void Main(string[] args)
{
DirectoryInfo dir = new DirectoryInfo(folderLocation);
var doc = new XDocument(CREATEXML(dir));
Console.WriteLine(doc.ToString());
Console.Read();
}
private static XElement CREATEXML(DirectoryInfo dir)
{
//get directories
var xmlInfo = new XElement("serverfiles", new XAttribute("name", dir.Name));
//get all the files first
foreach(var file in dir.GetFiles())
{
xmlInfo.Add(new XElement("file", new XAttribute("name", file.Name)));
}
//get subdirectories
foreach(var subDir in dir.GetDirectories())
{
xmlInfo.Add(CREATEXML(subDir));
}
return xmlInfo;
}

Nearly there: just some small edits to your code are what you need.
private const string folderLocation = #"c:\inputdata";
static void Main(string[] args)
{
DirectoryInfo dir = new DirectoryInfo(folderLocation);
// makes everything wrapped in an XElement called serverfiles.
// Also a declaration as specified (sorry about the standalone status:
// it's required in the XDeclaration constructor)
var doc = new XDocument(new XDeclaration("1.0", "UTF-8", "yes"),
CREATEXML(dir));
Console.WriteLine(doc.ToString());
Console.Read();
}
private static XElement CREATEXML(DirectoryInfo dir, bool writingServerFiles = true)
{
//get directories
var xmlInfo = new XElement(writingServerFiles ? "serverfiles" : "folder", writingServerFiles ? null : new XAttribute("name", dir.Name)); //fixes your small isue (making the root serverfiles and the rest folder, and serverfiles not having a name XAttribute)
//get all the files first
foreach(var file in dir.GetFiles())
{
xmlInfo.Add(new XElement("file", new XAttribute("name", file.Name)));
}
//get subdirectories
foreach(var subDir in dir.GetDirectories())
{
xmlInfo.Add(CREATEXML(subDir), false);
}
return xmlInfo;
}

You can add one more method that will handle subdirectories
private static XElement CreateXML(DirectoryInfo dir)
{
var xmlInfo = new XElement("serverfiles");
//get all the files first
foreach (var file in dir.GetFiles())
{
xmlInfo.Add(new XElement("file", new XAttribute("name", file.Name)));
}
//get subdirectories
foreach (var subDir in dir.GetDirectories())
{
xmlInfo.Add(CreateSubdirectoryXML(subDir));
}
return xmlInfo;
}
private static XElement CreateSubdirectoryXML(DirectoryInfo dir)
{
//get directories
var xmlInfo = new XElement("folder", new XAttribute("name", dir.Name));
//get all the files first
foreach (var file in dir.GetFiles())
{
xmlInfo.Add(new XElement("file", new XAttribute("name", file.Name)));
}
//get subdirectories
foreach (var subDir in dir.GetDirectories())
{
xmlInfo.Add(CreateSubdirectoryXML(subDir));
}
return xmlInfo;
}
EDIT:
Added sorting:
private static XElement CreateXML(DirectoryInfo dir)
{
var xmlInfo = new XElement("serverfiles");
//get all the files first
foreach (var file in dir.GetFiles())
{
xmlInfo.Add(new XElement("file", new XAttribute("name", file.Name)));
}
//get subdirectories
var subdirectories = dir.GetDirectories().ToList().OrderBy(d => d.Name);
foreach (var subDir in subdirectories)
{
xmlInfo.Add(CreateSubdirectoryXML(subDir));
}
return xmlInfo;
}
private static XElement CreateSubdirectoryXML(DirectoryInfo dir)
{
//get directories
var xmlInfo = new XElement("folder", new XAttribute("name", dir.Name));
//get all the files first
foreach (var file in dir.GetFiles())
{
xmlInfo.Add(new XElement("file", new XAttribute("name", file.Name)));
}
//get subdirectories
var subdirectories = dir.GetDirectories().ToList().OrderBy(d => d.Name);
foreach (var subDir in subdirectories)
{
xmlInfo.Add(CreateSubdirectoryXML(subDir));
}
return xmlInfo;
}

I think this solution can be better
//get directories
var xmlInfo = new XElement("folder",
new XElement("name", dir.Name),
new XElement("lastModify", dir.LastWriteTime),
new XElement("Attributes", dir.Attributes));
//get subdirectories
foreach (var subDir in dir.GetDirectories())
{
xmlInfo.Add(CREATEXML(subDir));
}
//get all the files
foreach (var file in dir.GetFiles())
{
xmlInfo.Add(new XElement("File",
new XElement("name", file.Name),
new XElement("size", file.Length),
new XElement("lastModify", file.LastWriteTime),
new XElement("Attributes", file.Attributes.ToString())));
}
return xmlInfo;

Related

I have a class of FileDetail which contains FileName,how can I return duplicate filenames within that class

I have the following code which will return the filename of all files within my directory and sub-directories of documents. I would now want to compare files which have identical filenames and see if their size is the same. I am not sure on how I would be able to add duplicate file names to a class and to then compare the filesize of these.
Below is the code I have working so far
private static void ListAllDuplicateFiles()
{
string[] files = Directory.GetFiles(rootPath, "*.*", SearchOption.AllDirectories);
List<FileDetail> fileDetails = new List<FileDetail>();
foreach (string file in files)
{
FileInfo fileInfo = new FileInfo(file);
FileDetail fileDetail = new FileDetail(fileInfo);
fileDetails.Add(fileDetail);
}
foreach (FileDetail fileDetail in fileDetails)
{
Console.WriteLine(fileDetail.Filename);
}
Console.ReadLine();
}
You can create a list of string to store the names that are duplicate, then use the list of string to do the comparison that you want
private static void ListAllDuplicateFiles()
{
string[] files = Directory.GetFiles(rootPath, "*.*", SearchOption.AllDirectories);
List<string> duplicates = new List<string>();
List<FileDetail> fileDetails = new List<FileDetail>();
foreach (string file in files)
{
FileInfo fileInfo = new FileInfo(file);
FileDetail fileDetail = new FileDetail(fileInfo);
fileDetails.Add(fileDetail);
if(fileDetails.Select(f=>f.Filename).Contains(file))
{
duplicates.Add(file);
}
}
foreach(string duplicate in duplicates)
{
//Do what you want with the list of duplicated file details
var duplicateFileDetails = fileDetails.Where(f=>f.Filename == duplicate).ToList();
}
foreach (FileDetail fileDetail in fileDetails)
{
Console.WriteLine(fileDetail.Filename);
}
Console.ReadLine();
}

Recursion issue while generating XML out of a directory tree

I have a task to create windows form with functionality to create an XML file of hierarchy of files and folders. I actually managed to do it, but have some trouble. Here is my code:
public static XElement xmlTreeView(DirectoryInfo dir)
{
XDocument xmlDocument = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XComment("Create an XML file containing complete hierarchy of files and folders for a specified folder"));
var info = new XElement("Directory", new XAttribute("name", dir.Name));
foreach (var subDir in dir.GetDirectories())
{
info.Add(new XElement("SubDirectory", new XAttribute("name", subDir.Name),
new XElement("FilesInFolder", subDir.GetFiles().Length)));
foreach (var file in subDir.GetFiles())
{
info.Add(new XElement("File", new XAttribute("name", file.Name),
new XElement("Size", file.Length),
new XElement("CreationTime", file.CreationTime),
new XElement("LastAccess", file.LastAccessTime),
new XElement("LastModified", file.LastWriteTime)));
}
}
foreach (var file in dir.GetFiles())
{
info.Add(new XElement("File", new XAttribute("name", file.Name),
new XElement("Size", file.Length),
new XElement("CreationTime", file.CreationTime),
new XElement("LastAccess", file.LastAccessTime),
new XElement("LastModified", file.LastWriteTime)));
}
return info;
}
the problem is that each folder must have the size and how many files are located in the folder...I tried to calculate the size of the folder, but I couldn't. I was able to show how many files have in subfolder, but all of these files are not shown inside XElement "SubDirectory". If I remove second foreach in subdir, than files not even shown. Help please.
The following program will build an XML tree out of a base directory:
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace ConsoleApplication1
{
internal class Program
{
private static void Main(string[] args)
{
ConvertToXml(#"C:\test");
}
private static void ConvertToXml(string baseDirectory)
{
var root = new XElement("Root");
var queue = new Queue<KeyValuePair<XElement, string>>();
queue.Enqueue(new KeyValuePair<XElement, string>(root, baseDirectory));
while (queue.Any())
{
var pair = queue.Dequeue();
var path = pair.Value;
var element = pair.Key;
var directories = Directory.GetDirectories(path);
var files = Directory.GetFiles(path);
element.Add(
new XAttribute("Files", files.Length.ToString()),
new XAttribute("Directories", directories.Length.ToString()));
foreach (var directory in directories)
{
var directoryInfo = new DirectoryInfo(directory);
var directoryElement = new XElement("Directory",
new XAttribute("Name", directoryInfo.Name),
new XAttribute("Size", GetDirectorySize(directory)));
element.Add(directoryElement);
queue.Enqueue(new KeyValuePair<XElement, string>(directoryElement, directory));
}
foreach (var file in files)
{
var fileInfo = new FileInfo(file);
var fileElement = new XElement("File",
new XAttribute("Name", fileInfo.Name),
new XAttribute("Size", fileInfo.Length));
element.Add(fileElement);
}
}
var xml = root.ToString();
}
private static long GetDirectorySize(string path)
{
long length = 0;
var queue = new Queue<string>(new[] {path});
while (queue.Any())
{
var value = queue.Dequeue();
var files = Directory.GetFiles(value);
length += files.Sum(s => new FileInfo(s).Length);
var directories = Directory.GetDirectories(value);
foreach (var directory in directories)
queue.Enqueue(directory);
}
return length;
}
}
}
Result:
<Root Files="0" Directories="1">
<Directory Name="root" Size="444" Files="1" Directories="2">
<Directory Name="folder1" Size="148" Files="1" Directories="0">
<File Name="document1.txt" Size="148" />
</Directory>
<Directory Name="folder2" Size="185" Files="1" Directories="0">
<File Name="document2.txt" Size="185" />
</Directory>
<File Name="readme.txt" Size="111" />
</Directory>
</Root>
Notes:
no recursion (which is good IMO)
computing a directory size can be long
depending the directory, there might be some ACL preventing you accessing it
just a trivial example -> augment it with what you need
If you were doing this in VB it would look like
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim path As String = Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
Dim di As New IO.DirectoryInfo(path)
Dim dirXML As XElement = XMLTreeView(di)
Dim totalFiles As Integer = (From el In dirXML...<FilesInFolder>
Let tf = Integer.Parse(el.Value)
Select tf).Sum
Dim totalLength As Long = (From el In dirXML...<Size>
Let tl = Long.Parse(el.Value)
Select tl).Sum
End Sub
Private Function XMLTreeView(dir As IO.DirectoryInfo) As XElement
Dim base As XElement = <Directory name=<%= dir.Name %>>
</Directory>
DoSubDirs(dir, base)
Return base
End Function
Private Sub DoSubDirs(dir As IO.DirectoryInfo, info As XElement)
For Each di As IO.DirectoryInfo In dir.GetDirectories
Try
Dim subXE As XElement = <SubDirectory name=<%= di.Name %>>
<FilesInFolder><%= di.GetFiles.Length.ToString %></FilesInFolder>
</SubDirectory>
For Each fi As IO.FileInfo In di.GetFiles
Dim fileXE As XElement = <File name=<%= fi.Name %>>
</File>
IncludeInfo(fi, fileXE)
subXE.Add(fileXE)
Next
DoSubDirs(di, subXE)
info.Add(subXE)
Catch ex As Exception
'todo errors
'probably permissions
End Try
Next
End Sub
Private Sub IncludeInfo(info As IO.FileInfo, xe As XElement)
xe.Add(<Attributes><%= info.Attributes %></Attributes>)
xe.Add(<Size><%= info.Length %></Size>)
xe.Add(<CreationTime><%= info.CreationTime %></CreationTime>)
xe.Add(<LastAccess><%= info.LastAccessTime %></LastAccess>)
xe.Add(<LastModified><%= info.LastWriteTime %></LastModified>)
End Sub
Typically recursion is not an issue with the filesystem because the level of nesting isn't great.
If I were a C# programmer I would use VB modules for XML becuase of the ability to use XML literals.

How to skip the creating the root node on a treeview?

How would you modify this code to skip creating the root node and create only the other nodes?
void ListDirectory(TreeView treeView, string path)
{
treeView.Nodes.Clear();
var rootDirectoryInfo = new DirectoryInfo(path);
treeView.Nodes.Add(CreateDirectoryNode(rootDirectoryInfo));
}
TreeNode CreateDirectoryNode(DirectoryInfo directoryInfo)
{
var directoryNode = new TreeNode(directoryInfo.Name);
foreach (var directory in directoryInfo.GetDirectories())
{
directoryNode.Nodes.Add(CreateDirectoryNode(directory));
}
return directoryNode;
}
Like this:
void ListDirectory(TreeView treeView, string path)
{
treeView.Nodes.Clear();
var rootDirectoryInfo = new DirectoryInfo(path);
foreach (var directory in rootDirectoryInfo.GetDirectories())
{
treeView.Nodes.Add(CreateDirectoryNode(directory));
}
}
TreeNode CreateDirectoryNode(DirectoryInfo directoryInfo)
{
var directoryNode = new TreeNode(directoryInfo.Name);
foreach (var directory in directoryInfo.GetDirectories())
{
directoryNode.Nodes.Add(CreateDirectoryNode(directory));
}
return directoryNode;
}

Populate TreeView with non-Empty Folders

private void ListDirectory(TreeView treeView, string path)
{
treeView.Nodes.Clear();
var rootDirectoryInfo = new DirectoryInfo(path);
treeView.Nodes.Add(CreateDirectoryNode(rootDirectoryInfo));
}
private static TreeNode CreateDirectoryNode(DirectoryInfo directoryInfo)
{
var directoryNode = new TreeNode(directoryInfo.Name);
foreach (var directory in directoryInfo.GetDirectories())
directoryNode.Nodes.Add(CreateDirectoryNode(directory));
foreach (var file in directoryInfo.GetFiles().Where(s => s.Extension == ".jpg"))
directoryNode.Nodes.Add(new TreeNode(file.Name));
return directoryNode;
}
using this code I can list all jpg files and folders from a given directory. But it will list empty folders of jpg files too. How can I avoid it?
The solution is to check whether there are any files on the return from the recursion, then add it if there are.
foreach (var directory in directoryInfo.GetDirectories())
{
TreeNode subNode = CreateDirectoryNode(directory);
if (subNode.Nodes.Count > 0)
directoryNode.Nodes.Add(CreateDirectoryNode(directory));
}
Since you're doing a depth first search the nodes will effectively be culled from the leaf up to the root if there aren't any files.
You need to check if the number of sub directories is zero and the number of files in the directory is zero. If this is the case, you should not create the node.
One way to do this is to return null from the CreateDirectoryNode method if the directory does not meet the specification. And then only add sub nodes that are not null.
Consider this code:
private void ListDirectory(TreeView treeView, string path)
{
treeView.Nodes.Clear();
var rootDirectoryInfo = new DirectoryInfo(path);
var tree_node = CreateDirectoryNode(rootDirectoryInfo);
if (tree_node != null)
treeView.Nodes.Add(tree_node);
}
//This method will return null of the specified directory does not have sub folders or JPG files
private static TreeNode CreateDirectoryNode(DirectoryInfo directoryInfo)
{
//Obtain all sub directories
var sub_directories = directoryInfo.GetDirectories();
//Obtain all JPG files
var jpeg_files =
directoryInfo.GetFiles()
.Where(s => s.Extension.Equals(".jpg", StringComparison.OrdinalIgnoreCase))
.ToArray();
//If the above arrays are empty, return null
if (sub_directories.Length == 0 && jpeg_files.Length == 0)
return null;
var directoryNode = new TreeNode(directoryInfo.Name);
foreach (var directory in sub_directories)
{
var sub_node = CreateDirectoryNode(directory);
if(sub_node != null) //Only add sub nodes if they are not null
directoryNode.Nodes.Add(sub_node);
}
foreach (var file in jpeg_files)
directoryNode.Nodes.Add(new TreeNode(file.Name));
return directoryNode;
}

How do I return a list of my folders with the files inside?

I am wondering how can I return the list of my folders with the files inside.
Because I can't return both of the list...and can't put both types in the list.
public List<DirectoryInfo> GetTopFolders()
{
List<DirectoryInfo> Folders = new List<DirectoryInfo>();
List<FileInfo> Files = new List<FileInfo>();
System.IO.DirectoryInfo di = new System.IO.DirectoryInfo("C://inetpub//wwwroot//Files//");
di.GetDirectories();
System.IO.FileInfo[] fiArr = di.GetFiles();
foreach (DirectoryInfo t in di.GetDirectories())
{
Folders.Add(t);
foreach (FileInfo f in di.GetFiles())
{
Files.Add(f);
}
}
return Folders;
}
This is the code I am actually using.
Well, both FileInfo and DirectoryInfo inherit from FileSystemInfo. So you could maintain a list of that type instead and return the whole lot together:
public List<FileSystemInfo> GetTopFoldersAndFiles()
{
List<FileSystemInfo> FilesAndFolders = new List<FileSystemInfo>();
System.IO.DirectoryInfo di = new System.IO.DirectoryInfo("C://inetpub//wwwroot//Files//");
di.GetDirectories();
System.IO.FileInfo[] fiArr = di.GetFiles();
foreach (DirectoryInfo t in di.GetDirectories())
{
FilesAndFolders.Add(t);
foreach (FileInfo f in di.GetFiles())
{
FilesAndFolders.Add(f);
}
}
return FilesAndFolders;
}
Of course you might need some conditional handling in the code where you make use of this object based on each item's type.
You could use a two-tuple. See Tuple.Create on msdn
public Tuple<List<DirectoryInfo>,List<FileInfo>> GetTopFolders()
{
List<DirectoryInfo> Folders = new List<DirectoryInfo>();
List<FileInfo> Files = new List<FileInfo>();
/* All your other code here */
/* Removed for brevity*/
foreach (DirectoryInfo t in di.GetDirectories())
{
Folders.Add(t);
foreach (FileInfo f in di.GetFiles())
{
Files.Add(f);
}
}
return Tuple.Create(Folders, Files);
}
You use it like this:
var fileAndFolders = GetTopFolders()
fileAndFolders.Item1 //will be your folders of type List<DirectoryInfo>
fileAndFolders.Item2 //will be your folders of type List<FileInfo>
You can use a Dictionary<DirectoryInfo, List<FileInfo>>:
private Dictionary<DirectoryInfo, List<FileInfo>> GetTopFolders()
{
Dictionary<DirectoryInfo, List<FileInfo>> r = new Dictionary<DirectoryInfo, List<FileInfo>>();
DirectoryInfo di = new DirectoryInfo("C://inetpub//wwwroot//Files//");
di.GetDirectories();
r.Add(di, di.GetFiles().ToList());
foreach (DirectoryInfo t in di.GetDirectories())
{
r.Add(t, t.GetFiles().ToList());
}
return r;
}
then you can list your folders and files like this:
StringBuilder sb = new StringBuilder();
foreach (KeyValuePair<DirectoryInfo, List<FileInfo>> item in GetTopFolders())
{
sb.AppendLine(item.Key.FullName);
foreach (FileInfo file in item.Value)
{
sb.AppendLine("\t" + file.Name);
}
}
Console.WriteLine(sb.ToString());

Categories

Resources