Recursion issue while generating XML out of a directory tree - c#

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.

Related

How to write XML file using for loop to append elements

I got a listbox of files. so I want to add this list's item to element of XML.
However, I have a problem with dealing a loop for adding items to XML.
//My goal:
//<project>
<drawing>
// file1
// file2
// <drawing/>
//<project>
//I tried to add items to element
//however it looks like
<drawing> file1file2 </drawing>
List<string> drawingList = new List<string>();
drawingList.Add(listBox1.Items[i].ToString());
new XDocument(
new XElement("Project",
new XElement("Name", project.name),
new XElement("Path", project.path),
new XElement("Drawing", drawingList)
)
)
.Save(#"C:\Users\for\Desktop\abc1.cadiprj");
);
See code below :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication120
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
List<string> drawingList = new List<string>() { "file1", "file2", "file3", "file4"};
XDocument doc = new XDocument(
new XElement("Project",
new XElement("Name", "abc"),
new XElement("Path", #"c:\temp\"),
new XElement("Drawing")
)
);
XElement xDrawing = doc.Descendants("Drawing").FirstOrDefault();
foreach (string drawing in drawingList)
{
xDrawing.Add(new XElement("Drawing", drawing));
}
}
}
}
To achieve the files separated in different lines you can string.Join() with a nuew line as separator
List<string> drawingList = new List<string>() { "file1", "file2"};
XDocument doc = new XDocument(
new XElement("Project",
new XElement("Name", "abc"),
new XElement("Path", #"c:\temp\"),
new XElement("Drawing", string.Join("\n", drawingList.ToArray()))
)
);
This would give you this output
<Project>
<Name>abd</Name>
<Path>your path</Path>
<Drawing>
file1
file2
</Drawing>
</Project>
As an advice, I would suggest a different format if it is not fixed, this one would make mucho more sense IMHO:
<Project>
<Name>abd</Name>
<Path>your path</Path>
<Drawing>
<file>file1</file>
<file>file2</file>
</Drawing>
</Project>

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;
}

Find new file in two folders with a cross check

I am trying to sort two folders in to a patched folder, finding which file is new in the new folder and marking it as new, so i can transfer that file only. i dont care about dates or hash changes. just what file is in the new folder that is not in the old folder.
somehow the line
pf.NFile = !( oldPatch.FindAll(s => s.Equals(f)).Count() == 0);
is always returning false. is there something wrong with my logic of cross checking?
List<string> newPatch = DirectorySearch(_newFolder);
List<string> oldPatch = DirectorySearch(_oldFolder);
foreach (string f in newPatch)
{
string filename = Path.GetFileName(f);
string Dir = (Path.GetDirectoryName(f).Replace(_newFolder, "") + #"\");
PatchFile pf = new PatchFile();
pf.Dir = Dir;
pf.FName = filename;
pf.NFile = !( oldPatch.FindAll(s => s.Equals(f)).Count() == 0);
nPatch.Files.Add(pf);
}
foreach (string f in oldPatch)
{
string filename = Path.GetFileName(f);
string Dir = (Path.GetDirectoryName(f).Replace(_oldFolder, "") + #"\");
PatchFile pf = new PatchFile();
pf.Dir = Dir;
pf.FName = filename;
if (!nPatch.Files.Exists(item => item.Dir == pf.Dir &&
item.FName == pf.FName))
{
nPatch.removeFiles.Add(pf);
}
}
I don't have the classes you are using (like DirectorySearch and PatchFile), so i can't compile your code, but IMO the line _oldPatch.FindAll(... doesn't return anything because you are comparing the full path (c:\oldpatch\filea.txt is not c:\newpatch\filea.txt) and not the file name only. IMO your algorithm could be simplified, something like this pseudocode (using List.Contains instead of List.FindAll):
var _newFolder = "d:\\temp\\xml\\b";
var _oldFolder = "d:\\temp\\xml\\a";
List<FileInfo> missing = new List<FileInfo>();
List<FileInfo> nPatch = new List<FileInfo>();
List<FileInfo> newPatch = new DirectoryInfo(_newFolder).GetFiles().ToList();
List<FileInfo> oldPatch = new DirectoryInfo(_oldFolder).GetFiles().ToList();
// take all files in new patch
foreach (var f in newPatch)
{
nPatch.Add(f);
}
// search for hits in old patch
foreach (var f in oldPatch)
{
if (!nPatch.Select (p => p.Name.ToLower()).Contains(f.Name.ToLower()))
{
missing.Add(f);
}
}
// new files are in missing
One possible solution with less code would be to select the file names, put them into a list an use the predefined List.Except or if needed List.Intersect methods. This way a solution to which file is in A but not in B could be solved fast like this:
var locationA = "d:\\temp\\xml\\a";
var locationB = "d:\\temp\\xml\\b";
// takes file names from A and B and put them into lists
var filesInA = new DirectoryInfo(locationA).GetFiles().Select (n => n.Name).ToList();
var filesInB = new DirectoryInfo(locationB).GetFiles().Select (n => n.Name).ToList();
// Except retrieves all files that are in A but not in B
foreach (var file in filesInA.Except(filesInB).ToList())
{
Console.WriteLine(file);
}
I have 1.xml, 2.xml, 3.xml in A and 1.xml, 3.xml in B. The output is 2.xml - missing in B.

Creating xml from directory structure

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;

How to add another top level node

I have my linq code formatted like:
<Deck>
<Treasure>
<card>
.....
</card>
......
</treasure>
<Door>
<card>
.....
</card>
......
</Door>
In the following code how do I add another Door that is the same "level" as treasure? Everything I have tried keeps adding it as the same level as card. Here is what I have:
public void SaveXml(string path)
{
XElement xml;
XElement root = new XElement("Treasure");
foreach (var item in TreasureCards)
{
xml = new XElement("Card",
new XAttribute("name", item.Name),
new XElement("Type", item.Type),
new XElement("Image",
new XAttribute("path", item.Image)),
new XElement("Usage", item.Usage),
new XElement("Quantity", item.Quantity),
new XElement("Sell", item.Sell)
);
root.Add(xml);
}
root.Add(new XElement("Door"));
foreach (var item in DoorCards)
{
xml = new XElement("Card",
new XAttribute("name", item.Name),
new XElement("Type", item.Type),
new XElement("Image",
new XAttribute("path", item.Image)),
new XElement("Usage", item.Usage),
new XElement("Quantity", item.Quantity));
root.Add(xml);
}
You need to create the Deck element first:
XElement deck = new XElement("Deck");
Then add both the treasure (which i've taken the liberty of renaming from root to treasure) and the door to it:
XElement treasure = new XElement("Treasure")
...
deck.Add(treasure)
...
XElement door = new XElement("Door")
...
deck.Add(door)

Categories

Resources