Skip to content

Reading Documents

Chapter 5-3
Reading Documents

To read documents, first find the document or documents you want in the database and then read fields from the document.


Finding Documents

NSFSearch and NIFReadEntries

The HCL C API for Domino and Notes provides two ways to find documents in databases: NSFSearch and NIFReadEntries. This chapter explains how to find documents using NSFSearch. It also explains how to open the documents and how to read fields from documents. The "Writing Documents" chapter in this guide shows how to create new notes and write fields. The "Views" section explains how to use NIFReadEntries.

We use the sample program RSIMPLE (short for ReadSIMPLE) to explain how to use NSFSearch and how to read fields. RSIMPLE resides in the directory samples\basic\rsimple. Load rsimple.c in an editor or print it so you can refer to it while you read this chapter.

NSFSearch is a powerful routine for finding database notes, including documents, which are a special class of note called NOTE_CLASS_DOCUMENT. However, you can use NSFSearch to find any class of note in a database, including design notes. You can also use NSFSearch to find databases in directories or databases on a server. For information about finding databases with NSFSearch, see the "Server and Database Listings" chapter.


Using NSFSearch to Find Documents

NSFSearch searches the specified database sequentially. For each note found that matches the selection criteria, the action routine you specify is called to process the note.

Your program must specify the selection criteria and provide a subroutine -- called an "action" or "call-back" routine -- capable of processing notes that match the selection criteria. Specify this action routine when you call NSFSearch.

NOTE: Your program does not call your action routine directly. Instead, your program calls NSFSearch, and NSFSearch calls the action routine.


NSFSearch Parameters

The Reference contains a detailed description of NSFSearch. However, to facilitate this discussion, we reproduce the function prototype for NSFSearch below.

NSFSearch: Syntax

STATUS LNPUBLIC NSFSearch (
DBHANDLE hDB,
FORMULAHANDLE hFormula,
char far ViewTitle,
WORD SearchFlags,
WORD NoteClassMask,
TIMEDATE far
Since,
NSFSEARCHPROC action_routine,
void far EnumRoutineParameter,
TIMEDATE far
retUntil);



The first argument (input) to NSFSearch is the handle to the database that will be searched.

The next five arguments to NSFSearch specify the selection criteria.

The second argument (input) to NSFSearch is an optional formula argument, which can greatly improve program efficiency when you only want to process a subset of the notes in the database. Selection formulas consist of the same Notes @ functions, field names, and logical operators as view selection formulas and conform to the same syntax. Any formula that works in view selection will work in NSFSearch, and you may omit the keyword SELECT. Specifying the formula argument as NULLHANDLE is the same as specifying @All.

This selection formula is initially expressed as a character string. Before calling NSFSearch, you must compile the character string using NSFFormulaCompile, which yields a formula handle. Since @All selects all documents in the database, RSIMPLE could have been implemented without NSFFormulaCompile by specifying NULLHANDLE as the second parameter to NSFSearch. RSIMPLE uses NSFFormulaCompile to demonstrate how to compile selection formulas to get handles.

The fifth argument (input) to NSFSearch is a mask of "note classes" that restricts the search to basic classes of notes, such as "documents," "views," or "forms." To find documents, specify NOTE_CLASS_DOCUMENT.

You can use the sixth argument (input) to NSFSearch to limit the search to only those notes modified since the given time/date, thus allowing you to see only the newly-updated notes in a database. The corresponding ninth argument (output) returns the "end time/date" so you can later perform another incremental search.

The search can be abnormally terminated if the supplied action routine returns an error. The error is returned to the caller of NSFSearch.


Example Using NSFSearch

The MAIN_PROC routine of rsimple.c (below) opens the Domino database, prepares the selection formula, and calls NSFSearch. The action routine in RSIMPLE performs all the per-document work. Once the search is complete, the program cleans up and returns.

rsimple.c: Finding all Documents in a Database


MAIN_PROC
{

  char           db_filename;   / pathname of source database /
 DBHANDLE       db_handle;      /
handle of source database /
 char          
formula;       / a character text selection formula /
 FORMULAHANDLE  formula_handle; / a compiled selection formula /
 WORD           wdc;            / a word we don't care about /

  STATUS         error;          / return status from API calls /

   db_filename = database_name;
   ProcessArgs(argc, argv, db_filename);
   OS_INITIALIZE(error);
   if (error)
     NOTES_INIT_ERROR;

if (error = NSFDbOpen (db_filename, &db_handle))
API_RETURN (ERR(error));


/ Write a character text selection formula. /

formula = "@All";

/ Compile the selection formula. /

if (error = NSFFormulaCompile (
NULL, / name of formula (none) /
0, / length of name /
formula, / the character text formula /
strlen(formula), / length of character text formula /
&formula_handle, / handle to compiled formula /
&wdc, / compiled formula length (don't care) /
&wdc, / return code from compile (don't care) /
&wdc, &wdc, &wdc, &wdc)) / compile error info (don't care) /

{
NSFDbClose (db_handle);
API_RETURN (ERR(error));
}


/ Call NSFSearch to find the notes that match the selection criteria. For
each note found, the routine print_fields is called. (If you always want
to find all the documents in the database, you can set the 2nd argument
to NULLHANDLE and eliminate the formula compilation.)
/


if (error = NSFSearch (
db_handle, / database handle /
formula_handle, / selection formula /
NULL, / title of view in selection formula /
0, / search flags /
NOTE_CLASS_DOCUMENT, / note class to find /
NULL, / starting date (unused) /
print_fields, / call for each note found /
&db_handle, / argument to print_fields /
NULL)) / returned ending date (unused) /


{
NSFDbClose (db_handle);
API_RETURN (ERR(error));
}


/ Free the memory allocated to the compiled formula. /

OSMemFree (formula_handle);

/ Close the database. /

if (error = NSFDbClose (db_handle))
API_RETURN (ERR(error));


/ End of main routine. /

API_RETURN (NOERROR);
}


RSIMPLE calls NSFSearch only once to find all the documents in the database. By specifying the selection formula @All, the note class NOTE_CLASS_DOCUMENT, and no starting date, the action routine print_fields is called once for every document in the database.


The Action Routine

The seventh parameter to NSFSearch is the name of the action routine (in this case, print_fields). The action routine may be given any name, but it must conform to the calling syntax of action routines as specified in the Reference for NSFSearch.

The eighth parameter to NSFSearch is an optional pass-through argument that is passed to the action routine when it is called. This pass-through argument allows NotesMain to provide context to the action routine, even though NotesMain does not call the action routine directly.

The action, or call-back, routine specified to NSFSearch must conform to the following calling syntax:

    STATUS (LNCALLBACKPTR action_routine)
    (void far * action_param,
    SEARCH_MATCH far * search_match,
    ITEM_TABLE far * summary_buffer)


NSFSearch calls the action routine once for every note it finds that matches the search criteria. When NSFSearch calls the action routine, it specifies, in the first parameter to the action routine, the pass-through parameter that was specified as the eighth parameter to NSFSearch.

The second and third parameters to the action routine (search_match and summary_buffer) provide information the action routine needs to process the document. search_match contains assorted information about the found note, including the Note ID. summary_buffer contains the values of some of the fields in the note.

Under Windows, the action routine must be exported via an EXPORTS statement in a .def file so that it may be called from Domino or Notes.

Under Windows, you must create a procedure instance for the action routine before calling NSFSearch. Assign the address returned from MakeProcInstance to a FARPROC pointer and specify that value as the seventh argument to NSFSearch. When the call to NSFSearch returns, you must free the procedure instance associated with the FARPROC pointer.

The sequential nature of NSFSearch means that NSFSearch finds notes in no particular order from the point of view of the HCL C API for Domino and Notes or the user. In fact, it finds notes in the order they appear in the .nsf file, but Domino or Notes may store newer notes closer to the beginning of an .nsf file than older notes if file space becomes available there. Your C API program must not assume that notes will be delivered to the action routine in any predictable order.

NOTE: If the search is not time-constrained (that is, if the "since" argument is NULL or specifies the TIMEDATE_WILDCARD ANYDAY/ALLDAY), NSFSearch may find a given note more than once during the same search. If a non-time-constrained search passes a certain note to the action routine, and that note is subsequently updated, NSFSearch may find the note again and pass it to the action routine a second time during the same search. This may happen if Domino or Notes relocates the updated note to a position farther down in the file.

If your algorithm requires that you process each note only once, use time-constrained searches. Alternatively, build an ID table as you search, avoid updating notes in the action routine, and process the ID table after the search completes. ID tables are guaranteed not to contain a given note ID more than once. In the action routine, add the ID of each note found to the ID table. When the search is complete, process each ID in the table.


Reading Documents Using the Action Routine

In RSIMPLE, the print_fields action routine reads and prints four fields in the document. To access the fields, print_fields uses NSFNoteOpen to open the document in the database. NSFNoteOpen requires a NOTEID as input to specify the note. The print_fields routine gets the NOTEID from the second parameter, the SEARCH_MATCH structure.

The print_fields routine checks the SE_FMATCH flag in the SEARCH_MATCH structure, gets the note ID and prints it, and then passes the note ID to NSFNoteOpen. NSFNoteOpen returns a handle that is used to access fields in the note and to close the note.

rsimple.c: Opening a Note
    STATUS LNCALLBACK print_fields
    (void far *db_handle,
    SEARCH_MATCH far *search_info,
    ITEM_TABLE far *summary_info)
    {

    NOTEHANDLE note_handle;
    STATUS error;



    /* Skip this note if it does not really match the search criteria (it is
    now deleted or modified). This is not necessary for full searches,
    but is shown here in case a starting date was used in the search. */


    /* if (search_info->MatchesFormula != SE_FMATCH)   V3 */
     if (!(search_info->SERetFlags & SE_FMATCH))   /* V4 */

        return (NOERROR);

    /* Print the note ID. */

    printf ("\nNote ID is: %lX.\n", search_info->ID.NoteID);

    /* Open the note. */

    if (error = NSFNoteOpen (
    *(DBHANDLE far *)db_handle, /* database handle */
    search_info->ID.NoteID, /* note ID */
    0, /* open flags */
    &note_handle)) /* note handle (return) */

    return (ERR(error));


      .
     
    . Operations on Documents
     
    .

    /* Close the note. */

    if (error = NSFNoteClose (note_handle))
    return (ERR(error));


    /* End of subroutine. */

      return (NOERROR);

    }

The action routine should always check the SERetFlags member of the SEARCH_MATCH structure and only process the note if MatchesFormula equals SE_FMATCH. On time-delimited searches, NSFSearch may call the action routine on a document that does not match the selection criteria. If it does, SERetFlags will not be set to SE_FMATCH. In this case, the action routine should not process the note; instead, it should simply return NOERROR.


Reading Documents

RSIMPLE calls NSFNoteOpen to open the note before accessing it. NSFNoteOpen yields a note handle. RSIMPLE needs this handle when it calls C API functions such as NSFItemGetText to read fields in the document.


Reading Fields in a Document

A document may contain any number of fields. Each field has a Field Name, a Data Type, a Data Length, a Data Value, and a flag word. Many different data types are supported. RSIMPLE demonstrates reading four basic data types: text, number, time/date, and text list.

The C API provides specific functions, such as NSFItemGetText, to read fields of each of the four basic data types. The C API also provides general functions, such as NSFItemInfo, to read any field of any data type. Generally, all functions that read fields in notes require as input the note handle and the name of the field. They return the field values in different ways, depending on the data type.


Reading Text Fields

RSIMPLE uses a two-step process to read a text field named PLAIN_TEXT. First it asks if the field is present in the document. If it is present, RSIMPLE gets the value and prints it out. Otherwise, it prints "PLAIN_TEXT field not found."

rsimple.c: Reading a Text Field

  NOTEHANDLE note_handle;
 BOOL       field_found;
 char       field_text[500];
 STATUS     error;


  / Look for the PLAIN_TEXT field within this note. /
 field_found = NSFItemIsPresent (note_handle,
               "PLAIN_TEXT",
               strlen ("PLAIN_TEXT"));


  / If PLAIN_TEXT field is there, get contents and print it.
  If the PLAIN_TEXT field is not there, print a message.
/
 if (field_found)
  {
  field_len = NSFItemGetText (note_handle,
               "PLAIN_TEXT",
               field_text,
               sizeof (field_text));
  printf ("PLAIN_TEXT field is: %s\n", field_text);
  }
 else
  printf ("PLAIN_TEXT field not found.\n");


NSFItemIsPresent returns a boolean value that is TRUE if the field is in the document. RSIMPLE stores this return value in the variable field_found, then calls NSFItemGetText to get the contents of the field named PLAIN_TEXT and store it in the variable field_text as a NULL-terminated string, suitable for print-out using printf.


Reading Number Fields

RSIMPLE uses the C API function NSFItemGetNumber to get a field named NUMBER that is of data type TYPE_NUMBER.

rsimple.c: Reading a Number from a Document

  NOTEHANDLE note_handle;
 BOOL       field_found;
 NUMBER     number_field;
 STATUS     error;


  / Look for (and get if it's there) the NUMBER field within
  this note.
/
 field_found = NSFItemGetNumber (note_handle,
               "NUMBER",
               &number_field);

    /* If the NUMBER field was found, print it. */

    if (field_found)
    printf ("NUMBER field is: %g\n", number_field);


    /* If the NUMBER field was not found, print a message. */

    else
    printf ("NUMBER field not found.\n");


NSFItemGetNumber returns the value of the field to the variable number_field. The function does not require NSFItemIsPresent because it returns a boolean indicating whether NUMBER is in the document.


Reading Time/Date Fields

RSIMPLE uses a two-step process to read a time/date field named TIME_DATE. First it asks if the field is present in the document. If it is there, RSIMPLE converts the value to text and prints it out. Otherwise, it prints "TIME_DATE field not found."

rsimple.c: Reading a Time/Date Field from a Document

  NOTEHANDLE note_handle;
 BOOL       field_found;


  / Look for the TIME_DATE field within this note. /
 field_found = NSFItemIsPresent (note_handle,
               "TIME_DATE",
               strlen ("TIME_DATE"));


  / If the TIME_DATE field is there, get the contents of the
  field as a character string and print it out.
  If the TIME_DATE field is not there, print a message.
 
/
 if (field_found)
  {
  field_len = NSFItemConvertToText (note_handle,
              "TIME_DATE",
              field_text,
              sizeof (field_text),
              ';'); / multi-value separator /

printf ("TIME_DATE field is: %s\n", field_text);

}

/ If the TIME_DATE field is not there, print a message. /

else
printf ("TIME_DATE field not found.\n");


The return value from NSFItemIsPresent is stored in the variable field_found. If field_found is TRUE, RSIMPLE calls NSFItemConvertToText to convert the contents of the field named PLAIN_TEXT to text format and store it in the variable field_text as a NULL-terminated string, suitable for print-out using printf.

NSFItemConvertToText returns the actual length of the text string. The caller must specify the size of the buffer where the converted text will be stored. The last argument is the separator that Notes should use to separate any multi-valued fields. A semicolon is the default value for the last argument; if you enter zero, the result is the same.


Reading Text-List Fields

A text-list field is a multi-valued text field.

RSIMPLE uses a three-step process to read the last value of a text-list field named TEXT_LIST. First it asks if the field is present. If it is, RSIMPLE gets the number of values in the text-list, gets the value of the last item on the list, and prints it out. If the field is not present, RSIMPLE prints "TEXT_LIST field not found."

rsimple.c: Reading a Text-List Field from a Document

  NOTEHANDLE note_handle;     / note handle         /
 BOOL       field_found;     / indicate field presence   /
 WORD       field_len;       / actual length of field   /
 WORD       list_entries;    / count of entries      /
 char       field_text[500]; / buffer for result      /
 STATUS      error;          / return code from API calls /


  / Look for the TEXT_LIST field within this note. /
 field_found = NSFItemIsPresent ( note_handle,
       "TEXT_LIST",
       strlen ("TEXT_LIST"));


  / If the TEXT_LIST field is present, do the next few
    sections of code.
  If it is not there, print a message.
/
 if (field_found)
  {
  / Find the number of entries in TEXT_LIST /
  list_entries = NSFItemGetTextListEntries(note_handle,
                   "TEXT_LIST");


   / Get the last entry in TEXT_LIST.
    The fields are numbered from 0 to n-1.
    So we subtract one from the number returned above.
/


   field_len = NSFItemGetTextListEntry ( note_handle,
            "TEXT_LIST",
            list_entries - 1, / field to get/
            field_text,
            sizeof (field_text) -1 );

    /* Print out the last entry in TEXT_LIST. */

    printf ("The last entry in TEXT_LIST is: %s\n", field_text);

    }

    /* If the TEXT_LIST field is not there, print a message. */

    else
    printf ("TEXT_LIST field not found.\n");



The return value from NSFItemIsPresent is stored in the variable field_found. If field_found is TRUE, RSIMPLE calls NSFItemGetTextListEntries to get the number of entries (values) in the list. It stores this number in the variable list_entries.

To get the last entry on the list, RSIMPLE calls NSFItemGetTextListEntry. NSFItemGetTextListEntry requires as input the ordinal position of the desired text list entry. This position is zero-based (that is, the first entry is number 0, the second is number 1, the third 2, and so on). To get the last entry, RSIMPLE specifies the number of entries minus one.

NSFItemGetTextListEntry stores a NULL-terminated string in field_text, suitable for print-out using printf.

NSFItemGetTextListEntry returns the actual length of the text string. The caller must specify the size of the storage buffer for the text entry.