API
@-Formulas
JavaScript
LotusScript
Reg Exp
Web Design
Notes Client
XPages
 
Find Added To File (Part 2)
Last week I started talking about how to find the "added to file" value for documents in a database. This week I will finish the tip. You should read last week's tip first before starting with this one.

Last time I went over all the code in the Initialize subroutine. So I'll start with the WalkTree subroutine.

Sub WalkTree(db As NotesDatabase, node As NotesDOMNode, xmlHierarchy As Variant, _
doc As NotesDocument)
   Dim child As NotesDOMNode
   Dim elt As NotesDOMNode
   Dim attrs As NotesDOMNamedNodeMap
   Dim a As NotesDOMAttributeNode
   Dim i As Integer
   Dim xmlHierLevel As Integer
   Dim unid As String
   Dim addedToFile As Variant
   Dim numAttributes As Integer
   Dim numChildren As Integer

I define several types of variables that aren't "familiar" to every day programming. These are all used to go through the DOM hierarchy. Remember that we're looking at a tree with branches.

   If Not node.IsNull Then
      Select Case node.NodeType
      Case DOMNODETYPE_DOCUMENT_NODE:
         Set child = node.FirstChild
         Dim numChildNodes As Integer
         numChildNodes = node.NumberOfChildNodes
         While numChildNodes > 0
            Set child = child.NextSibling
            numChildNodes = numChildNodes - 1
            xmlHierLevel = Ubound(xmlHierarchy)
            If xmlHierLevel <> 0 Or xmlHierarchy(0) <> "" Then
               xmlHierLevel = xmlHierLevel+1
            End If
            Redim Preserve xmlHierarchy(xmlHierLevel)
            xmlHierarchy(xmlHierLevel) = node.NodeName
            Call walkTree(db, child, xmlHierarchy, doc)
            If xmlHierLevel = 0 Then xmlHierarchy(0) = "" _
            Else Redim Preserve xmlHierarchy(xmlHierLevel-1)
         Wend

The first thing the subroutine does is make sure the passed-in node is something. If it's a null value, then nothing is going to happen and the function will exit normally. Assuming the node is not null, then I check the type of node. In the Domino Designer Help database, you can see all the different types of nodes possible. My code only deals with three types - the others are ignored. The first type I look at is the document node. This node has the potential to have the information we want. So I want to look at all the children nodes.

What I do is keep track of the hierarchy by expanding the xmlHierarchy variable and putting in the name of the node in the last position. This is passed along during the recursion so the recursive call knows "where it came from". The recursion happens on every child node for the document node.

How did I know what I'm looking for? What I actually did is started out with the example from the Domino Designer Help database. I used that example and printed out everything to a text file. I was then able to look at the file and find out what I was after and trace the hierarchy back through the text file and see what nodes I needed and what nodes I could ignore.

      Case DOMNODETYPE_TEXT_NODE:
         If Not doc Is Nothing Then
            If Join(xmlHierarchy, "~") = "#document~database~document~noteinfo~addedtofile~datetime" Then
               addedToFile = ConvertDateTime(node.NodeValue)
               If Not doc Is Nothing Then
                  Call doc.ReplaceItemValue("AddedToFile", addedToFile)
                  Call doc.Save(True, False, True)
               End If
               Set doc = Nothing
            End If
         End If

The next type of node to look at is a plain text node. This is the type of node that contains the "Added To File" information that we want. But it's also used for other types of nodes. So the code looks at the hierarchy - if it started with a document, then went to the database, then to a document (which is actually going to happen all the time in this example because our initial collection only has documents), then to the note information (ie document properties), then to the "added to file" property, and lastly to the date/time value of that property, then that's the value we want to save. See how keeping track of the hierarchy helps out? There are a lot of times that the text node will be called, but there's only one specific instance where this code cares about it.

The value of the node is a standards-based date time format ("Coordinated Universal Time" or "UTC"). That needs to be converted to something Notes-specific. So the ConvertUniversalDateTime function (discussed here) will do that. That value is stored in the document object that has been passed around during the recursion. You haven't yet seen the code where that object is set - that will happen soon. The document object is cleared after the value has been stored in the document.

      Case DOMNODETYPE_ELEMENT_NODE:
         Set elt = node
         numAttributes = elt.attributes.numberofentries
         Set attrs = elt.Attributes
         For i = 1 To numAttributes
            Set a = attrs.GetItem(i)
            If a.NodeName = "unid" Then
               If Join(xmlHierarchy, "~") = "#document~database~document" Then
                  If node.NodeName = "noteinfo" Then
                     unid = a.NodeValue
                     On Error Resume Next
                     Set doc = db.GetDocumentByUNID(unid)
                     On Error Goto 0
                     If Err <> 0 Then
                        Err = 0
                        Set doc = Nothing
                     End If
                  End If
               End If
            End If
         Next

Most nodes are element nodes, so this part of the Select statement will be called a lot. Inside here, we care about the UNID attribute. When that is found, the first thing I do is assure that the right path was used to get to this UNID attribute - that should be the main document, then the database, then a document. Because of how the Initialize subroutine was set up, this check could actually be skipped, but it's a good idea to include it. This attribute should be on the "noteInfo" node, so that check is made. Assuming the right path was used to get here, then I set the doc variable to that document (the one for this UNID). That document is passed through the recursion until the "added to file" value is found (the code block above) and set in the document.

         numChildren = elt.NumberOfChildNodes
         Set child = elt.FirstChild
         While numChildren > 0
            xmlHierLevel = Ubound(xmlHierarchy)
            If xmlHierLevel <> 0 Or xmlHierarchy(0) <> "" Then
               xmlHierLevel = xmlHierLevel+1
            End If
            Redim Preserve xmlHierarchy(xmlHierLevel)
            xmlHierarchy(xmlHierLevel) = node.NodeName
            Call walkTree(db, child, xmlHierarchy, doc)
            If xmlHierLevel = 0 Then xmlHierarchy(0) = "" _
            Else Redim Preserve xmlHierarchy(xmlHierLevel-1)
            Set child = child.NextSibling
            numChildren = numChildren - 1
         Wend

Since the element node is the most popular, the children need to be processed. This starts the recursion in the same manner as was discussed earlier with the document node.

      End Select
   End If
End Sub

That ends the WalkTree subroutine. The End Select statement ends the choices for the type of node, and the End If statements ends the check for the node being null.

So, that's everything. You should now be able to run this agent against a certain database and every document will be updated with a field called AddedToFile with the date and time when the individual document was added to the current file. Obviously, if you're running this on multiple replicas you'll get different answers and could cause replication conflicts. So it might be better to simply report the information instead of actually updating the documents. It would be easy to modify this agent to write all the results to an agent log.