Exporing Documents from Teamroom/NSF files using C#

There are no words for the horror.
Jan 29 2009 by James Craig

I just want to say, before I get into this in any depth, that IBM sucks when it comes to documentation. I use to think that Microsoft was bad, but IBM is now the king of sucky documentation (or tutorials, or anything else). If it wasn't for Gupta's article here, I'd still be ripping my hair out. Anyway, at work we have a product called Teamroom. It's basically a Lotus/IBM equivalent to SharePoint... Sort of anyway... That being said, I had the honor of being tasked with taking the information from there and exporting it out so that we could do a migration to a different system.

Now if IBM had built the system using databases or really anything that made sense, we'd be fine. Instead they use huffman encoding on all documents (well almost all anyway from what we can tell) without sharing the algorithm (no idea if there is a header or not) and they use NSF files. For those that don't know, NSF files are basically Lotus Notes files (I'm not talking about the NES sound format files). The way you get into them is by using the API that IBM provides. However, without the Interop.Domino.dll file (which you can get from the article I link to above), you wont be able to use it in C#... So first, go to the link above and download his example. It contains the DLL that you need. Once you have that, add the reference, etc. and then finally the code below should work:

 public static void GetDocuments(string StartDirectory,string OutputDirecotry)
{
List<System.IO.FileInfo> Files=Utilities.FileManager.FileList(StartDirectory);
List<System.IO.DirectoryInfo> Directories = Utilities.FileManager.DirectoryList(StartDirectory);
foreach (System.IO.FileInfo File in Files)
{
if (File.Extension.Equals(".nsf", StringComparison.CurrentCultureIgnoreCase))
{
XmlDocument TempDoc = GetFromNSFdb(File.FullName);
string DocumentName = "";
List<Values> Docs = ParseKeys(TempDoc, File.FullName, out DocumentName);
if (File.Name.Equals("Main.nsf"))
{
GetDocuments(Docs, OutputDirecotry, File.FullName);
}
else
{
if (Docs.Count > 0)
{
Utilities.FileManager.CreateDirectory(OutputDirecotry + "/" + DocumentName);
GetDocuments(Docs, OutputDirecotry + "/" + DocumentName, File.FullName);
}
}
}
}
foreach (System.IO.DirectoryInfo Directory in Directories)
{
if (!Directory.Name.Equals("Search.ft", StringComparison.CurrentCultureIgnoreCase))
{
Utilities.FileManager.CreateDirectory(OutputDirecotry + "/" + Directory.Name);
GetDocuments(Directory.FullName, OutputDirecotry + "/" + Directory.Name);
}
}
}

public static XmlDocument GetFromNSFdb(string strNSFdbName)
{

try
{
Domino.ISession session = new NotesSessionClass();
session.Initialize(YOURPASSWORD);
Domino.NotesDatabase database = session.GetDatabase("", strNSFdbName, false);
Domino.NotesNoteCollection noteCollecton = database.CreateNoteCollection(true);
noteCollecton.SelectAllAdminNotes(true);
noteCollecton.SelectAllCodeElements(true);
noteCollecton.SelectAllDataNotes(true);
noteCollecton.SelectAllDesignElements(true);
noteCollecton.SelectAllFormatElements(true);
noteCollecton.SelectAllIndexElements(true);
noteCollecton.SelectForms = true;
noteCollecton.BuildCollection();
Domino.NotesDXLExporter exporter = (Domino.NotesDXLExporter)session.CreateDXLExporter();
string strNsfXML = exporter.Export(noteCollecton);
string strDocrmv = strNsfXML.Replace("<!DOCTYPE database SYSTEM 'xmlschemas/domino_6_5_3.dtd'>", "").Replace("xmlns='http://www.lotus.com/dxl'", ""); XmlDocument xDocLoad = new XmlDocument();
xDocLoad.LoadXml(strDocrmv);
return xDocLoad;
}
catch { return null; }
}

public static List<Values> ParseKeys(XmlDocument TempDoc,string strNSFdbName,out string DocumentName)
{
XmlNode TempNode = TempDoc["database"];
DocumentName = TempNode.Attributes["title"].Value;
List<Values> TempDic = new List<Values>();
string CurrentKey = "";
string FileName = "";
foreach (XmlNode Child in TempNode.ChildNodes)
{
if (Child.HasChildNodes)
{
foreach (XmlNode Child2 in Child.ChildNodes)
{
if (Child2.Name.Equals("noteinfo", StringComparison.CurrentCultureIgnoreCase)
&& Child2.Attributes["noteid"]!=null)
{
CurrentKey = Child2.Attributes["noteid"].Value;
break;
}
}
foreach (XmlNode Child2 in Child.ChildNodes)
{
if (Child2.HasChildNodes)
{
if (Child2.Name.Equals("item", StringComparison.CurrentCultureIgnoreCase)
&& Child2.Attributes["name"]!=null
&& Child2.Attributes["name"].Value.Equals("$FILE", StringComparison.CurrentCultureIgnoreCase))
{
foreach (XmlNode Child3 in Child2.ChildNodes)
{
if (Child3.Name.Equals("object", StringComparison.CurrentCultureIgnoreCase))
{
foreach (XmlNode Child4 in Child3.ChildNodes)
{
if (Child4.Name.Equals("file", StringComparison.CurrentCultureIgnoreCase))
{
FileName = Child4.Attributes["name"].Value;
Values TempValue = new Values();
TempValue.Key = FileName;
TempValue.Value = CurrentKey;
TempDic.Add(TempValue);
}
}
}
}
}
}
}
}
}
return TempDic;
}

public static void GetDocuments(List<Values> Docs, string OutputDirectory, string strNSFdbName)
{
Domino.ISession session = new NotesSessionClass();
session.Initialize(YOURPASSWORD);
Domino.NotesDatabase database = session.GetDatabase("", strNSFdbName, false);
foreach (Values Key in Docs)
{
Domino.NotesDocumentClass TempDoc = (Domino.NotesDocumentClass)database.GetDocumentByID(Key.Value);
Domino.NotesEmbeddedObjectClass Object = (Domino.NotesEmbeddedObjectClass)TempDoc.GetAttachment(Key.Key);
Object.ExtractFile(OutputDirectory + "/" + Key.Key);
}
}

public class Values
{
public string Key;
public string Value;
}

In the code above, the function you want to call is GetDocuments(string StartDirectory, string OutputDirectory). That function searches the input directory for nsf files, loads each one, finds the documents, and exports them to the output directory using the same directory structure (although sub rooms/pages are going to be in their own directory based off of the title of the sub room/page so you can tell what they are). The class above is simply used as a holder for information between the functions. Also note that this uses my utility library a bit and on top of this, you're going to have to have Lotus Domino/Notes installed on the computer and set the lines that say session.Initialize(YOURPASSWORD) to actually use your password (or potentially a blank string, "", depending on how you have Lotus set up). However, all of that taken into consideration, it does indeed work. The code is different from the version we're using at work but the functions have been tested. Anyway, I hope this helps someone out there so that they aren't ripping their hair out... So try it out (or heavily modify it for your own uses), leave feedback, and happy coding.