Other Posts in Lotus

  1. Exporting Documents from Teamroom/NSF files using C#

Exporting Documents from Teamroom/NSF files using C#


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:

   1: public static void GetDocuments(string StartDirectory,string OutputDirecotry)
   2: {
   3:     List<System.IO.FileInfo> Files=Utilities.FileManager.FileList(StartDirectory);
   4:     List<System.IO.DirectoryInfo> Directories = Utilities.FileManager.DirectoryList(StartDirectory);
   5:     foreach (System.IO.FileInfo File in Files)
   6:     {
   7:         if (File.Extension.Equals(".nsf", StringComparison.CurrentCultureIgnoreCase))
   8:         {
   9:             XmlDocument TempDoc = GetFromNSFdb(File.FullName);
  10:             string DocumentName = "";
  11:             List<Values> Docs = ParseKeys(TempDoc, File.FullName, out DocumentName);
  12:             if (File.Name.Equals("Main.nsf"))
  13:             {
  14:                 GetDocuments(Docs, OutputDirecotry, File.FullName);
  15:             }
  16:             else
  17:             {
  18:                 if (Docs.Count > 0)
  19:                 {
  20:                     Utilities.FileManager.CreateDirectory(OutputDirecotry + "/" + DocumentName);
  21:                     GetDocuments(Docs, OutputDirecotry + "/" + DocumentName, File.FullName);
  22:                 }
  23:             }
  24:         }
  25:     }
  26:     foreach (System.IO.DirectoryInfo Directory in Directories)
  27:     {
  28:         if (!Directory.Name.Equals("Search.ft", StringComparison.CurrentCultureIgnoreCase))
  29:         {
  30:             Utilities.FileManager.CreateDirectory(OutputDirecotry + "/" + Directory.Name);
  31:             GetDocuments(Directory.FullName, OutputDirecotry + "/" + Directory.Name);
  32:         }
  33:     }
  34: }
  36: public static XmlDocument GetFromNSFdb(string strNSFdbName)
  37: {
  39:     try
  40:     {
  41:         Domino.ISession session = new NotesSessionClass();
  42:         session.Initialize(YOURPASSWORD);
  43:         Domino.NotesDatabase database = session.GetDatabase("", strNSFdbName, false);
  44:         Domino.NotesNoteCollection noteCollecton = database.CreateNoteCollection(true);
  45:         noteCollecton.SelectAllAdminNotes(true);
  46:         noteCollecton.SelectAllCodeElements(true);
  47:         noteCollecton.SelectAllDataNotes(true);
  48:         noteCollecton.SelectAllDesignElements(true);
  49:         noteCollecton.SelectAllFormatElements(true);
  50:         noteCollecton.SelectAllIndexElements(true);
  51:         noteCollecton.SelectForms = true;
  52:         noteCollecton.BuildCollection();
  53:         Domino.NotesDXLExporter exporter = (Domino.NotesDXLExporter)session.CreateDXLExporter();
  54:         string strNsfXML = exporter.Export(noteCollecton);
  55:         string strDocrmv = strNsfXML.Replace("<!DOCTYPE database SYSTEM 'xmlschemas/domino_6_5_3.dtd'>", "").Replace("xmlns='http://www.lotus.com/dxl'", ""); XmlDocument xDocLoad = new XmlDocument();
  56:         xDocLoad.LoadXml(strDocrmv);
  57:         return xDocLoad;
  58:     }
  59:     catch { return null; }
  60: }
  62: public static List<Values> ParseKeys(XmlDocument TempDoc,string strNSFdbName,out string DocumentName)
  63: {
  64:     XmlNode TempNode = TempDoc["database"];
  65:     DocumentName = TempNode.Attributes["title"].Value;
  66:     List<Values> TempDic = new List<Values>();
  67:     string CurrentKey = "";
  68:     string FileName = "";
  69:     foreach (XmlNode Child in TempNode.ChildNodes)
  70:     {
  71:         if (Child.HasChildNodes)
  72:         {
  73:             foreach (XmlNode Child2 in Child.ChildNodes)
  74:             {
  75:                 if (Child2.Name.Equals("noteinfo", StringComparison.CurrentCultureIgnoreCase)
  76:                     && Child2.Attributes["noteid"]!=null)
  77:                 {
  78:                     CurrentKey = Child2.Attributes["noteid"].Value;
  79:                     break;
  80:                 }
  81:             }
  82:             foreach (XmlNode Child2 in Child.ChildNodes)
  83:             {
  84:                 if (Child2.HasChildNodes)
  85:                 {
  86:                     if (Child2.Name.Equals("item", StringComparison.CurrentCultureIgnoreCase)
  87:                         && Child2.Attributes["name"]!=null
  88:                         && Child2.Attributes["name"].Value.Equals("$FILE", StringComparison.CurrentCultureIgnoreCase))
  89:                     {
  90:                         foreach (XmlNode Child3 in Child2.ChildNodes)
  91:                         {
  92:                             if (Child3.Name.Equals("object", StringComparison.CurrentCultureIgnoreCase))
  93:                             {
  94:                                 foreach (XmlNode Child4 in Child3.ChildNodes)
  95:                                 {
  96:                                     if (Child4.Name.Equals("file", StringComparison.CurrentCultureIgnoreCase))
  97:                                     {
  98:                                             FileName = Child4.Attributes["name"].Value;
  99:                                             Values TempValue = new Values();
 100:                                             TempValue.Key = FileName;
 101:                                             TempValue.Value = CurrentKey;
 102:                                             TempDic.Add(TempValue);
 103:                                     }
 104:                                 }
 105:                             }
 106:                         }
 107:                     }
 108:                 }
 109:             }
 110:         }
 111:     }
 112:     return TempDic;
 113: }
 115: public static void GetDocuments(List<Values> Docs, string OutputDirectory, string strNSFdbName)
 116: {
 117:     Domino.ISession session = new NotesSessionClass();
 118:     session.Initialize(YOURPASSWORD);
 119:     Domino.NotesDatabase database = session.GetDatabase("", strNSFdbName, false);
 120:     foreach (Values Key in Docs)
 121:     {
 122:         Domino.NotesDocumentClass TempDoc = (Domino.NotesDocumentClass)database.GetDocumentByID(Key.Value);
 123:         Domino.NotesEmbeddedObjectClass Object = (Domino.NotesEmbeddedObjectClass)TempDoc.GetAttachment(Key.Key);
 124:         Object.ExtractFile(OutputDirectory + "/" + Key.Key);
 125:     }
 126: }
 128: public class Values
 129: {
 130:     public string Key;
 131:     public string Value;
 132: }

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.


Stan Rogers
August 11, 2010 5:18 PM

I'll grant that IBM is really, really good at hiding things on their web site. (The doco is there, but you need to consult the Keeper of the URLs personally -- I swear they must have a no-index, no-follow robots.txt on the support site. Before Big Blue Branding took over the Lotus sites, everything was exceptionally easy to find.) And if you got this going in three hours, I salute you -- the first exposure to Notes is sort of like being parachuted into a foreign country with no map. No matter where you're coming from, it ain't what you're used to.I spent wa-a-ay too much time fixing other people's Notes and Domino web crapplications. In that way, Notes is a lot more like Access than it is like Sharepoint -- the bar to entry (creating something that works, however badly) is so low that a good half of the people working on the platform haven't the slightest clue what they're doing. And it doesn't help that certification is a joke (although that was true is the MS world as well until

James Craig
August 11, 2010 9:35 AM

Sorry, I should have said a relational database. For me, when I say database, I mean relational database (force of habit). I did find the documentation about 6 months after this post, but finding anything online at the time was a pain. For me, that's where it should be. I should be able to enter into Google, NSF, Lotus, TeamRoom, etc. and C# and get back something that puts me down the path of figuring out what the heck I'm suppose to do. That wasn't the case back when I posted this. Most of the time I can find stuff in about 10 minutes, but in this case about 2 hours to find the basics.As far as the "like SharePoint" comment. I still stand by that. The way people used TeamRoom and Lotus in general was the same as the way people are using SharePoint now... Things it wasn't designed for... Most installations that I've seen are poorly implemented and poorly maintained (like SharePoint). And while the out of the box feature set was limited, I've seen people do some pretty am

Stan Rogers
August 11, 2010 8:37 AM

For those of you who don't know, the NSF (Notes Storage Facility) *is* a database. Don't confuse the term "database" with "relational database"; you may be familiar with RDBMSs, but they aren't the only game in town. The NSF is a schemaless, document database. It's also secure at the machine, database, document, document section and field level by design, so yes, accessing the data through the Notes EXE using an ID with sufficient access rights is part of the deal. And if you include a reference to the Lotus.Domino namespace using the Lotus Domino Objects tlb, the correct interop DLL should be created for you automatically when you build the app (and the DLL changes version to version -- they're usually backwards-compatible, but make sure that you aren't using a later version than your user will have).That being said, the available documentation is excellent, and if you don't already have it installed locally (that is, you don't have the Lotus Domino Designer cl

June 11, 2010 12:54 AM

Excellent Article !!!