Response Documents
Chapter 12-3
Response Documents
This chapter explains how to create a response (or child) document in a database and how to find and read the responses to a main (parent) document.
Response Reference List Item
Response documents contain a response reference list item, which is an item with field name "$REF" and data type TYPE_NOTEREF_LIST. A response reference list consists of a LIST structure followed a UNID that identifies the parent document.
The presence of a "$REF" field in a document makes the document a response to the parent document identified by the UNID.
Characteristics of a Response Reference List item:
- The field name is always "$REF" (programs normally specify FIELD_LINK).
- At the API level, the data type is TYPE_NOTEREF_LIST.
- The item consists of a LIST header structure, followed by a UNIVERSALNOTEID (UNID).
- The UNID identifies the parent document.
Creating Response Documents
Create the Response Reference List
To create a response document, first obtain the UNID of the parent document and initialize a UNID data structure with this UNID. Also initialize a LIST data structure, setting the ListEntries member to 1. Pack the LIST structure and the UNID structure into a memory buffer so that the first byte of the UNID starts immediately after the last byte of the LIST. The resulting buffer contains the data value of the response reference list item.
Creating the Response Reference List |
if (error = NSFDbGetNoteInfo (db_handle, main_nid, &main_oid,
&lastmod_td, ¬e_class))
{
return ERR(error);
}
/ Initialize the LIST header part of the response reference list /
ListHdr.ListEntries = (USHORT) 1;
/ Initialize the UNID part of the response reference list /
NoteUNID.File = main_oid.File; / user-unique identifier /
NoteUNID.Note = main_oid.Note; / time/date when the note was created /
/ Pack the LIST and the UNID members of the Noteref list into
a memory block.
/
memcpy(buf, (char)&ListHdr, sizeof(LIST));
memcpy((buf+sizeof(LIST)), (char)&NoteUNID, sizeof(UNID));
Append the Response Reference List Item
Next, create or open the document that is to become the response document. Call NSFItemAppend to append the response reference list to this document. For the item name, specify FIELD_LINK. For the data type, specify TYPE_NOTEREF_LIST. For the data value, specify the buffer containing the packed LIST and UNID.
Finally, update and close the response note.
Appending the Response Reference List Item |
/ Open the second note - the one to make into a response document. /
if (error = NSFNoteOpen(db_handle, resp_nid, 0, ¬e_handle))
{
return ERR(error);
}
/ Append the completed response reference list to the second note
as an item of type TYPE_NOTEREF_LIST.
/
if (error = NSFItemAppend ( note_handle,
ITEM_SUMMARY,
FIELD_LINK, / $REF /
strlen(FIELD_LINK),
TYPE_NOTEREF_LIST, / data type /
buf, / resp ref list /
(DWORD)(sizeof(LIST) + sizeof(UNID)) ))
{
NSFNoteClose(note_handle);
return ERR(error);
}
Reading Response Documents
Hierarchical views display response documents indented below the corresponding main document.
Since a COLLECTION object, obtained by NIFOpenCollection, preserves the ordering and indenting of the view, the best way to find response documents in a database is to use a hierarchical view COLLECTION and NIFReadEntries.
A response reference list links a child document to its parent. However, the parent document does not contain a link to the child. Given a parent document, then, the only practical way to find the corresponding child documents is to navigate to the child using a hierarchical view.
Example Using NIFReadEntries
The example source code below processes documents in a hierarchical view. It loops over main documents in the view. Each time through the loop, it reads one main document and all the responses to that main document. It calls NIFReadEntries three times: first to read one main document, then to skip to the child of the main document and read all the children, and then to navigate to the next main document.
Example Reading Response Documents |
/ set coll_pos to first doc in view /
coll_pos.Level = 0;
coll_pos.Tumbler[0] = 1;
while(TRUE) / loop over all main docs in view /
{
err = NIFReadEntries( coll_hdl,
&coll_pos, / where the match begins /
NAVIGATE_CURRENT, / order to skip entries /
0, / no. of entries to skip /
NAVIGATE_NEXT_PEER, / navigate at this level /
1, / just get one note id /
READ_MASK_NOTEID, / get the note ID /
&buff_hdl, / Note ID return buffer /
NULL, / length of return buffer /
NULL, / no. entries skipped /
¬es_found, / entries read /
NULL); / share warning /
if (err)
{
printf("Error: unable to read next entry note in view.\n");
if (buff_hdl != NULLHANDLE)
{
OSMemFree(buff_hdl);
}
goto EXIT5;
}
/ If note is a category note, skip to next main document /
if (err = SkipToMainDocument(&buff_hdl, ¬es_found, &coll_hdl,
&coll_pos))
{
goto EXIT5;
}
/ process the main doc /
err = ProcessNotes(buff_hdl, notes_found, maindoc_label);
OSMemFree(buff_hdl);
if (err)
goto EXIT5;
/ read all response docs /
err = NIFReadEntries( coll_hdl,
&coll_pos, / where the match begins /
NAVIGATE_CHILD, / skip main document /
1, / skip 1 to responses /
NAVIGATE_NEXT_PEER, / navigate at this level /
0xFFFF, / return ALL responses /
READ_MASK_NOTEID, / get the note IDs /
&buff_hdl, / Note ID return buffer /
NULL, / length of return buffer /
NULL, / no. entries skipped /
¬es_found, / entries read /
NULL); / share warning /
if (err)
{
printf("Error: unable to read response documents.\n");
if (buff_hdl != NULLHANDLE)
{
OSMemFree(buff_hdl);
}
goto EXIT5;
}
/ process response docs /
err = ProcessNotes(buff_hdl, notes_found, response_label);
OSMemFree(buff_hdl);
if (err)
goto EXIT5;
/ print footer and EOF /
write (evar.outfile_hdl, evar.footer, strlen(evar.footer));
if (evar.endoffile)
{
write (evar.outfile_hdl, endchar, strlen(endchar));
}
/
* Skip to next main document in view. If no more documents, break
* out of loop. To detect that there are no more main docs, save
* the current position, then skip 1 to next main document
* but read 0 entries. Then compare new position to saved value. If
* position didn't change, have reached end of view. Else go back
* to top of loop to process next main document.
/
SaveCollPos = coll_pos ;
/ PrintIndexPos(&coll_pos); /
err = NIFReadEntries (coll_hdl, &coll_pos,
NAVIGATE_NEXT_MAIN, 1,
0, 0, 0, NULL, NULL, NULL, NULL, NULL);
if (err)
{
printf("Error: unable to skip from response documents in view.\n");
goto EXIT5;
}
/ done when no change in coll pos /
for(i = 0, CollPosTumblerChanged = FALSE;
i <= (int)min(SaveCollPos.Level, coll_pos.Level);
i++)
{
if (SaveCollPos.Tumbler[i] != coll_pos.Tumbler[i])
{
CollPosTumblerChanged = TRUE;
}
}
if (!CollPosTumblerChanged && (SaveCollPos.Level == coll_pos.Level))
break; / coll pos unchanged: done! /
} / end of loop over main docs /
The first call to NIFReadEntries reads just the main document. It reads only one document by specifying ReturnCount = 1 in the fifth argument.
The second call to NIFReadEntries navigates to the child of the main document, skipping one, and reading the maximum number of documents possible. Note that the call specifies NAVIGATE_NEXT_PEER as the ReturnNavigator argument, ensuring that NIFReadEntries reads only child documents. NAVIGATE_NEXT_PEER causes NIFReadEntries to stop before reading the next main document.
The third call to NIFReadEntries specifies NAVIGATE_NEXT_MAIN as the Skip Navigator argument and a skip count of 1. This moves the collection position up from the last response document to the next main document. Note that no documents are read in this third call to NIFReadEntries. Only the COLLECTIONPOSITION is affected.
The remaining code in the loop checks that the COLLECTIONPOSITION did indeed change. If the COLLECTIONPOSITION did not change, there are no more entries in the collection. This condition ends the loop.
Example Using Indent Level
The sample program ONECAT reads the main documents in a category, deliberately skipping over the response notes. It uses the indent level information to distinguish main documents from response documents.
After opening the database and the collection, ONECAT calls NIFFindByName to position the COLLECTIONPOSITION to the first main document in the specified category.
ONECAT then calls NIFReadEntries in a loop, starting at the first document in the category, to get information about the remaining entries in the collection. ONECAT specifies the read mask as READ_MASK_NOTEID + READ_MASK_INDENTLEVELS + READ_MASK_INDEXPOSITION to get the note ID, the indent level, and the collection position of each document read.
ONECAT loops over the entries in the buffer returned by NIFReadEntries. For each entry, it compares the indent level against the indent level of main documents (MAIN_TOPIC_INDENT). For category entries, the indent level is only used in multi-level columns (multiple levels of categories in one column). For note entries, the indent level tells whether the note is a main topic, response, or response to response. Main topics have an indent level of 0. Response documents have an indent level of 1.
ONECAT: Identifying Main Documents |
if (error = NIFReadEntries(
coll_handle, / handle to this collection /
&cat_index, / where to start in collection /
FirstTime ? NAVIGATE_CURRENT : NAVIGATE_NEXT,
/ order to use when skipping /
FirstTime ? 0L : 1L, / number to skip /
NAVIGATE_NEXT, / order to use when reading /
0xFFFFFFFF, / max number to read /
READ_MASK_NOTEID + / info we want /
READ_MASK_INDENTLEVELS +
READ_MASK_INDEXPOSITION,
&buffer_handle, / handle to info buffer (return) /
NULL, / length of info buffer (return) /
NULL, / entries skipped (return) /
&entries_found, / entries read (return) /
&signal_flag)) / signal and share warnings (return) /
{
NIFCloseCollection (coll_handle);
NSFDbClose (db_handle);
return (ERR(error));
}
/ Lock down (freeze the location) of the information buffer. Cast
the resulting pointer to the type we need. /
buff_ptr = (BYTE ) OSLockObject (buffer_handle);
/ Start a loop that extracts the info about each collection entry from
the information buffer. /
printf ("\n");
for (i = 1; i <= entries_found; i++)
{
/ Get the ID of this entry. /
entry_id = (NOTEID) buff_ptr;
buff_ptr += sizeof (NOTEID);
/ Get the "indent level" of this entry. For category entries, the indent
level is only used in multi-level columns (multiple levels of categories
in one column). For note entries, the indent level tells whether the
note is a main topic, response, response to response, and so on. /
entry_indent = (WORD) buff_ptr;
buff_ptr += sizeof (WORD);
/ Find the size of this entry's index information. Each entry's index
info may be a different length, since it is truncated just to the length
needed. /
entry_index_size = COLLECTIONPOSITIONSIZE
((COLLECTIONPOSITION) buff_ptr);
/ Get the COLLECTIONPOSITION of the entry. /
memcpy (&entry_index, buff_ptr, entry_index_size);
buff_ptr += entry_index_size;
/ If this entry is a top-level category, then we have found all the notes
in the category we want. (We will have reached the next major category.) /
if (entry_index.Level == CATEGORY_LEVEL_BEING_SEARCHED)
break;
/ If this entry is a lower-level category, skip over it. /
if (NOTEID_CATEGORY & entry_id) continue;
/ If this entry is a response doc (or response to response, and so on),
skip over it. /
if (entry_indent != MAIN_TOPIC_INDENT) continue;
/ We have found a main-topic note. Print out its note ID. /
printf ("Main topic count is: %lu. \tNote ID is: %lX.\n",
++main_topics_found, entry_id);
/ End of loop that gets info about each entry. /
}