614 lines
18 KiB
C
614 lines
18 KiB
C
/*
|
|
* CDE - Common Desktop Environment
|
|
*
|
|
* Copyright (c) 1993-2012, The Open Group. All rights reserved.
|
|
*
|
|
* These libraries and programs are free software; you can
|
|
* redistribute them and/or modify them under the terms of the GNU
|
|
* Lesser General Public License as published by the Free Software
|
|
* Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* These libraries and programs are distributed in the hope that
|
|
* they will be useful, but WITHOUT ANY WARRANTY; without even the
|
|
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU Lesser General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with these libraries and programs; if not, write
|
|
* to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
|
|
* Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
/*
|
|
* COMPONENT_NAME: austext
|
|
*
|
|
* FUNCTIONS: DtSearchFormatObjdate
|
|
* DtSearchFreeResults
|
|
* DtSearchGetMaxResults
|
|
* DtSearchMergeResults
|
|
* DtSearchSetMaxResults
|
|
* DtSearchSortResults
|
|
* DtSearchValidDateString
|
|
* aa_check_initialization
|
|
* aa_envars
|
|
* aa_get_passwd
|
|
* ditto_pop
|
|
* ditto_sort
|
|
* ditto_split
|
|
* merge_by_date
|
|
* merge_by_prox
|
|
*
|
|
* ORIGINS: 27
|
|
*
|
|
*
|
|
* (C) COPYRIGHT International Business Machines Corp. 1995, 1996
|
|
* All Rights Reserved
|
|
* Licensed Materials - Property of IBM
|
|
* US Government Users Restricted Rights - Use, duplication or
|
|
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
|
|
*/
|
|
/**************************** DTSRJOINT.C *************************
|
|
* $XConsortium: dtsrjoint.c /main/5 1996/06/23 16:47:42 cde-ibm $
|
|
* February 1995.
|
|
* Split off of ausapi.c when I converted to client/server model.
|
|
* Ausclt.c is virtual copy of ausapi functions in RPC client stub.
|
|
* This module contains ausapi utilities that are common to both
|
|
* sides (ie do not result in RPC call to server or require
|
|
* access to real usrblk). My original version of this program
|
|
* was called "aacom" for "ausapi common" but I renamed it to avoid
|
|
* conflict with aacomm, the rpc/sockets communications module.
|
|
*
|
|
* $Log$
|
|
* Revision 2.5 1996/04/10 19:52:16 miker
|
|
* Added locale independent ISDIGIT macro. Changed function name
|
|
* from aa_merge_dittolists to DtSearchMergeResults.
|
|
*
|
|
* Revision 2.4 1995/12/27 16:11:54 miker
|
|
* Remove save_init_dbxxx globals for DtSearchReinit().
|
|
*
|
|
* Revision 2.3 1995/10/25 22:16:09 miker
|
|
* Renamed from aajoint.c. Added prolog.
|
|
*
|
|
* Log: aajoint.c,v
|
|
* Revision 2.2 1995/10/02 20:14:39 miker
|
|
* DtSearch function name changes.
|
|
* Added const to strdup() prototype for greater portability.
|
|
* Changed to more uniform output format in DtSearchFormatObjdate().
|
|
*
|
|
* Revision 2.1 1995/09/22 15:18:08 miker
|
|
* Freeze DtSearch 0.1, AusText 2.1.8
|
|
*
|
|
* Revision 1.3 1995/08/31 21:31:48 miker
|
|
* Renames and minor changes for DtSearch.
|
|
*
|
|
* Revision 1.2 1995/06/22 18:01:09 miker
|
|
* 2.1.6: RPC version no longer user specifiable. Removed aa_versnum etc.
|
|
* Added aa_sort_dittolist, date sorting of hitlists by any client.
|
|
*/
|
|
#include "SearchE.h"
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#ifndef strdup
|
|
extern char *strdup (const char *s);
|
|
#endif
|
|
|
|
#define PROGNAME "DTSRJOINT"
|
|
#define ISDIGIT(c) ((ascii_charmap [(unsigned int) (c)] & NUMERAL) != 0)
|
|
|
|
/*------------- GLOBALS VISIBLE TO CALLER -------------------*/
|
|
/* see also globals.c */
|
|
char **ausapi_dbnamesv = NULL; /* array of database names */
|
|
int ausapi_dbnamesc = 0; /* size of dbnames array */
|
|
int aa_maxhits = DtSrMAXHITS; /* num hits retnd after
|
|
* srch */
|
|
|
|
/*------------------- PRIVATE GLOBALS ---------------------*/
|
|
int aa_is_initialized = FALSE;
|
|
int aa_getnews_flag = 1;
|
|
long save_init_switches = 0L;
|
|
int ditsort_type = 0;
|
|
|
|
|
|
/************************************************/
|
|
/* */
|
|
/* aa_check_initialization */
|
|
/* */
|
|
/************************************************/
|
|
/* Confirms ausapi_init() was first function called. */
|
|
void aa_check_initialization (void)
|
|
{
|
|
if (aa_is_initialized)
|
|
return;
|
|
fprintf (aa_stderr,
|
|
catgets (dtsearch_catd, 2, 37,
|
|
"%s First API function call must be DtSearchInit().\n"),
|
|
PROGNAME"37");
|
|
DtSearchExit (37);
|
|
} /* aa_check_initialization() */
|
|
|
|
|
|
/************************************************/
|
|
/* */
|
|
/* DtSearchValidDateString */
|
|
/* */
|
|
/************************************************/
|
|
/* Subroutine of aa_both_valid_dates(), called once for each date string,
|
|
* or can be called from user interface to validate a date string.
|
|
* Converts passed date string into valid AusText DtSrObjdate.
|
|
* Date string format: "[yy]yy [mm [dd]]",
|
|
* 1, 2, or 3 numeric tokens separated by one
|
|
* or more nonnumeric chars (whitespace, slashes, etc).
|
|
* The first token is a complete year number integer yyyy
|
|
* in the range 1900 <= yyyy <= 5995. If the first token contains
|
|
* less than 4 digits it is presumed to be the number of years
|
|
* since 1900. The month number is the second token mm,
|
|
* in the range 1 - 12, and the third token dd is the day, 1 - 31.
|
|
* If only two tokens are in the string they are presumed to
|
|
* be year and month; the day is presumed to be to 1.
|
|
* If only one token is in the string it is presumed to
|
|
* be the year; the month is presumed to be 1 and the day
|
|
* is presumed to be 1. NULL and empty strings are always
|
|
* valid. They mean no date exclusion in the search
|
|
* and this function returns objdate 0 for them.
|
|
* Returns objdate on successful parse and conversion.
|
|
* Returns -1 and err msg if date string is invalid.
|
|
*/
|
|
DtSrObjdate DtSearchValidDateString (char *datestr)
|
|
{
|
|
DtSrObjdate objdate = 0L;
|
|
char parsebuf[64];
|
|
char *startp, *endp;
|
|
int yy, mm, dd;
|
|
int stop_parse = FALSE;
|
|
char msgbuf[256];
|
|
|
|
aa_check_initialization();
|
|
if (datestr == NULL) /* null string is valid */
|
|
return 0L;
|
|
if (datestr[0] == 0) /* empty string is valid */
|
|
return 0L;
|
|
|
|
strncpy (parsebuf, datestr, sizeof (parsebuf));
|
|
parsebuf[sizeof (parsebuf) - 1] = 0;
|
|
|
|
/* yyyy */
|
|
for (startp = parsebuf; *startp != 0 && !ISDIGIT(*startp); startp++);
|
|
if (*startp == 0) { /* no numeric digits in string */
|
|
#ifdef DEBUG_VALDATE
|
|
fprintf (aa_stderr, PROGNAME"269 No numeric digits in string\n");
|
|
#endif
|
|
INVALID_DATESTR:
|
|
sprintf (msgbuf,
|
|
catgets (dtsearch_catd, 2, 115,
|
|
"%s '%s' is invalid or incomplete date string.\n"
|
|
"The correct format is '[yy]yy [mm [dd]]'."),
|
|
PROGNAME"115", datestr);
|
|
DtSearchAddMessage (msgbuf);
|
|
#ifdef DEBUG_VALDATE
|
|
fprintf (aa_stderr, PROGNAME"278 Returning objdate -1\n");
|
|
fflush (aa_stderr);
|
|
#endif
|
|
return -1L;
|
|
}
|
|
for (endp = startp; ISDIGIT(*endp); endp++);
|
|
if (*endp == 0) { /* mm and dd both missing */
|
|
mm = 1;
|
|
dd = 1;
|
|
stop_parse = TRUE;
|
|
#ifdef DEBUG_VALDATE
|
|
fprintf (aa_stderr, PROGNAME"286 mm and dd both missing\n");
|
|
#endif
|
|
}
|
|
*endp = 0;
|
|
yy = atoi (startp);
|
|
#ifdef DEBUG_VALDATE
|
|
fprintf (aa_stderr, PROGNAME"293 yystr='%s' yy=%d\n", startp, yy);
|
|
#endif
|
|
if (strlen (startp) < 4)
|
|
yy += 1900;
|
|
if (yy < 1900 || yy > 5995)
|
|
goto INVALID_DATESTR;
|
|
if (stop_parse)
|
|
goto DATESTR_OK;
|
|
|
|
/* mm */
|
|
for (startp = ++endp; *startp != 0 && !ISDIGIT(*startp); startp++);
|
|
if (*startp == 0) { /* no mm in string */
|
|
mm = 1;
|
|
dd = 1;
|
|
#ifdef DEBUG_VALDATE
|
|
fprintf (aa_stderr, PROGNAME"309 No mm in string\n");
|
|
#endif
|
|
goto DATESTR_OK;
|
|
}
|
|
for (endp = startp; ISDIGIT(*endp); endp++);
|
|
if (*endp == 0) { /* no dd in string */
|
|
dd = 1;
|
|
stop_parse = TRUE;
|
|
#ifdef DEBUG_VALDATE
|
|
fprintf (aa_stderr, PROGNAME"318 No dd in string\n");
|
|
#endif
|
|
}
|
|
*endp = 0;
|
|
mm = atoi (startp);
|
|
#ifdef DEBUG_VALDATE
|
|
fprintf (aa_stderr, PROGNAME"293 mmstr='%s' mm=%d\n", startp, mm);
|
|
#endif
|
|
if (mm < 1 || mm > 12)
|
|
goto INVALID_DATESTR;
|
|
if (stop_parse)
|
|
goto DATESTR_OK;
|
|
|
|
/* dd */
|
|
for (startp = ++endp; *startp != 0 && !ISDIGIT(*startp); startp++);
|
|
if (*startp == 0) { /* no dd in string */
|
|
dd = 1;
|
|
#ifdef DEBUG_VALDATE
|
|
fprintf (aa_stderr, PROGNAME"336 No dd in string\n");
|
|
#endif
|
|
goto DATESTR_OK;
|
|
}
|
|
for (endp = startp; ISDIGIT(*endp); endp++);
|
|
*endp = 0;
|
|
dd = atoi (startp);
|
|
#ifdef DEBUG_VALDATE
|
|
fprintf (aa_stderr, PROGNAME"344 ddstr='%s' dd=%d\n", startp, dd);
|
|
#endif
|
|
if (dd < 1 || dd > 31)
|
|
goto INVALID_DATESTR;
|
|
|
|
DATESTR_OK:
|
|
objdate = (yy - 1900) << 20 | (mm-1) << 16 | dd << 11;
|
|
#ifdef DEBUG_VALDATE
|
|
fprintf (aa_stderr,
|
|
PROGNAME"352 Returning objdate %08lx, %d/%d/%d, %s\n",
|
|
objdate, yy, mm, dd, objdate2fzkstr(objdate));
|
|
fflush (aa_stderr);
|
|
#endif
|
|
return objdate;
|
|
} /* DtSearchValidDateString() */
|
|
|
|
|
|
/************************************************/
|
|
/* */
|
|
/* DtSearchFormatObjdate */
|
|
/* */
|
|
/************************************************/
|
|
/* Converts objdate in hitlist to displayable string */
|
|
char *DtSearchFormatObjdate (DtSrObjdate objdate)
|
|
{
|
|
static char datestr_buf[24];
|
|
if (objdate == 0L)
|
|
return "(undated)";
|
|
strftime (datestr_buf, sizeof(datestr_buf), "%Y.%m.%d",
|
|
objdate2tm (objdate));
|
|
return datestr_buf;
|
|
} /* DtSearchFormatObjdate() */
|
|
|
|
|
|
/************************************************/
|
|
/* */
|
|
/* DtSearchGetMaxResults */
|
|
/* DtSearchSetMaxResults */
|
|
/* */
|
|
/************************************************/
|
|
int DtSearchGetMaxResults (void)
|
|
{ return aa_maxhits; }
|
|
|
|
void DtSearchSetMaxResults (int newmax)
|
|
{ aa_maxhits = newmax; }
|
|
|
|
|
|
/************************************************/
|
|
/* */
|
|
/* DtSearchFreeResults */
|
|
/* */
|
|
/************************************************/
|
|
/* Frees storage allocated for the dittolist created
|
|
* by DtSearchQuery(), and sets dittolist pointer to NULL.
|
|
* Always returns DtSrOK.
|
|
*/
|
|
int DtSearchFreeResults (DtSrResult ** dittolist)
|
|
{
|
|
free_llist ((LLIST **) dittolist);
|
|
return DtSrOK;
|
|
} /* DtSearchFreeResults() */
|
|
|
|
|
|
/************************************************/
|
|
/* */
|
|
/* DtSearchMergeResults */
|
|
/* */
|
|
/************************************************/
|
|
/* Merges one dittolist into another using proximity
|
|
* for sort order. Sets 'src' dittolist pointer to NULL.
|
|
* Does not change dittocount of either list, user must do that.
|
|
* Returns DtSrOK on successful merge,
|
|
* else returns DtSrERROR for programming error.
|
|
* Formerly named aa_merge_dittolists.
|
|
*/
|
|
int DtSearchMergeResults (DtSrResult **targ_list, DtSrResult **src_list)
|
|
{
|
|
DtSrResult *targ, *src, *nextsrc, **prevtargl;
|
|
|
|
if (src_list == NULL || targ_list == NULL)
|
|
return DtSrERROR;
|
|
|
|
/* If there's no 'src' list, return with no changes.
|
|
* In other words, merge was successful before we started.
|
|
*/
|
|
if (*src_list == NULL)
|
|
return DtSrOK;
|
|
|
|
/* At this point we know there is a src_list.
|
|
* If there's no targ_list, just swap the pointers.
|
|
*/
|
|
if (*targ_list == NULL) {
|
|
*targ_list = *src_list;
|
|
*src_list = NULL;
|
|
return DtSrOK;
|
|
}
|
|
|
|
/* We have two nonempty lists. Now do a real merge.
|
|
* Insert src node into targ list only if it's
|
|
* proximity is smaller. Otherwise just
|
|
* advance to the next targ node.
|
|
*/
|
|
src = *src_list; /* curr src_list node to compare */
|
|
nextsrc = src->link; /* next src_list node to compare */
|
|
targ = *targ_list; /* curr targ_list node to compare */
|
|
prevtargl = targ_list; /* prev targ_list node link pointer */
|
|
while (src != NULL && targ != NULL) {
|
|
if (src->proximity < targ->proximity) {
|
|
/* Insert src node into targ list */
|
|
*prevtargl = src;
|
|
src->link = targ;
|
|
prevtargl = &src->link;
|
|
src = nextsrc;
|
|
if (src)
|
|
nextsrc = src->link;
|
|
}
|
|
else {
|
|
/* just advance to next targ node */
|
|
prevtargl = &targ->link;
|
|
targ = targ->link;
|
|
}
|
|
}
|
|
|
|
/* If there's any src_list left,
|
|
* just tack it onto the end of the targ_list.
|
|
*/
|
|
if (src) {
|
|
/* advance to end of targ_list */
|
|
while (targ) {
|
|
prevtargl = &targ->link;
|
|
targ = targ->link;
|
|
}
|
|
*prevtargl = src;
|
|
}
|
|
|
|
*src_list = NULL;
|
|
return DtSrOK;
|
|
} /* DtSearchMergeResults() */
|
|
|
|
|
|
/************************************************************
|
|
* Aa_sort_dittolist(): basically a copy of ditto_sort() from engine,
|
|
* but will sort on several different ditto fields
|
|
* (currently only date fields implemented),
|
|
* and is only used as convenience function on client/gui side.
|
|
* The proximity field is always the minor sort key.
|
|
* All functions with similar names to engine functions are static
|
|
* in this module so they won't collide with same function in engine.
|
|
* Performs a recursive split-merge sort on ditto lists.
|
|
************************************************************/
|
|
|
|
/************************************************/
|
|
/* */
|
|
/* ditto_pop */
|
|
/* */
|
|
/************************************************/
|
|
/* Subroutine of DtSearchSortResults().
|
|
* Detaches first node in a list and returns it...
|
|
* If *lst is empty return NULL, else set *lst to the link
|
|
* cell of the first DtSrResult node on *lst and return a pointer to
|
|
* the first DtSrResult node on *lst.
|
|
*/
|
|
static DtSrResult *ditto_pop (DtSrResult ** lst)
|
|
{
|
|
DtSrResult *first_node;
|
|
|
|
first_node = *lst;
|
|
if (first_node != NULL)
|
|
*lst = first_node->link;
|
|
return first_node;
|
|
} /* ditto_pop() */
|
|
|
|
|
|
/************************************************/
|
|
/* */
|
|
/* ditto_split */
|
|
/* */
|
|
/************************************************/
|
|
/* Subroutine of DtSearchSortResults().
|
|
* Find the middle node in lst. Set its 'next' pointer to NULL.
|
|
* Return the remainder of lst, i.e. a pointer to the
|
|
* next node after the middle node.
|
|
*/
|
|
static DtSrResult *ditto_split (DtSrResult * lst)
|
|
{
|
|
DtSrResult *tail = lst->link;
|
|
|
|
if (lst == NULL || tail == NULL)
|
|
return lst;
|
|
|
|
/*
|
|
* Advance 'tail' to end of list, and advance 'lst' only half
|
|
* as often
|
|
*/
|
|
while ((tail != NULL) && ((tail = tail->link) != NULL)) {
|
|
lst = lst->link;
|
|
tail = tail->link;
|
|
}
|
|
tail = lst->link;
|
|
lst->link = NULL;
|
|
return tail;
|
|
} /* ditto_split() */
|
|
|
|
|
|
/************************************************/
|
|
/* */
|
|
/* merge_by_prox */
|
|
/* */
|
|
/************************************************/
|
|
/* Subroutine of DtSearchSortResults().
|
|
* Merges two sorted DtSrResult lists together in proximity order.
|
|
*/
|
|
static DtSrResult *merge_by_prox (DtSrResult * l1, DtSrResult * l2)
|
|
{
|
|
DtSrResult *myqueue = NULL;
|
|
DtSrResult *myend = NULL;
|
|
DtSrResult *mynext;
|
|
|
|
while ((l1 != NULL) && (l2 != NULL)) {
|
|
/*
|
|
* Perform ENQUEUE function. Next item popped off a list is
|
|
* the next one in sorted order. It is added to END of
|
|
* myqueue to maintain order. THIS IS WHERE THE ACTUAL SORT
|
|
* COMPARE FUNCTION IS PERFORMED.
|
|
*/
|
|
mynext = (l1->proximity < l2->proximity) ?
|
|
ditto_pop (&l1) : ditto_pop (&l2);
|
|
|
|
mynext->link = NULL;
|
|
if (myqueue == NULL)
|
|
myqueue = mynext;
|
|
else
|
|
myend->link = mynext;
|
|
myend = mynext;
|
|
}
|
|
|
|
/*
|
|
* Perform JOIN QUEUE function. Append entire list to end of
|
|
* queue.
|
|
*/
|
|
if (l1 != NULL)
|
|
myend->link = l1;
|
|
if (l2 != NULL)
|
|
myend->link = l2;
|
|
return myqueue;
|
|
} /* merge_by_prox() */
|
|
|
|
|
|
/************************************************/
|
|
/* */
|
|
/* merge_by_date */
|
|
/* */
|
|
/************************************************/
|
|
/* Subroutine of DtSearchSortResults().
|
|
* Merges two sorted DtSrResult lists together in objdate order.
|
|
*/
|
|
static DtSrResult *merge_by_date (DtSrResult * l1, DtSrResult * l2)
|
|
{
|
|
DtSrResult *myqueue = NULL;
|
|
DtSrResult *myend = NULL;
|
|
DtSrResult *mynext;
|
|
|
|
while ((l1 != NULL) && (l2 != NULL)) {
|
|
/*
|
|
* Perform ENQUEUE function. Next item popped off a list is
|
|
* the next one in sorted order. It is added to END of
|
|
* myqueue to maintain order. THIS IS WHERE THE ACTUAL SORT
|
|
* COMPARE FUNCTION IS PERFORMED.
|
|
*/
|
|
if (l1->objdate == l2->objdate)
|
|
mynext = (l1->proximity < l2->proximity) ?
|
|
ditto_pop (&l1) : ditto_pop (&l2);
|
|
else
|
|
mynext = (l1->objdate > l2->objdate) ?
|
|
ditto_pop (&l1) : ditto_pop (&l2);
|
|
|
|
mynext->link = NULL;
|
|
if (myqueue == NULL)
|
|
myqueue = mynext;
|
|
else
|
|
myend->link = mynext;
|
|
myend = mynext;
|
|
}
|
|
|
|
/*
|
|
* Perform JOIN QUEUE function. Append entire list to end of
|
|
* queue.
|
|
*/
|
|
if (l1 != NULL)
|
|
myend->link = l1;
|
|
if (l2 != NULL)
|
|
myend->link = l2;
|
|
return myqueue;
|
|
} /* merge_by_date() */
|
|
|
|
|
|
/************************************************/
|
|
/* */
|
|
/* ditto_sort */
|
|
/* */
|
|
/************************************************/
|
|
/* Subroutine of DtSearchSortResults().
|
|
* Sorts a list of DtSrResult structures and returns ptr to sorted list.
|
|
* The basic idea is to sort by recursively splitting a list
|
|
* into two equal halves and sorting each of those. The recursion
|
|
* ends when there are only two small lists which are either
|
|
* already sorted or are swapped. This sort rarely runs out
|
|
* of stack space because each recursion cuts the list length in
|
|
* half so there are at most 1 + log-N-to-the-base-2 items on the stack.
|
|
* (e.g. 64,000 nodes = max stack depth of 16: 2**16 = 64K).
|
|
*/
|
|
static DtSrResult *ditto_sort (DtSrResult * lst)
|
|
{
|
|
DtSrResult *lst2;
|
|
|
|
if ((lst == NULL) || (lst->link == NULL))
|
|
return lst;
|
|
lst2 = ditto_split (lst);
|
|
switch (ditsort_type) {
|
|
case DtSrSORT_PROX:
|
|
return merge_by_prox (ditto_sort (lst), ditto_sort (lst2));
|
|
case DtSrSORT_DATE:
|
|
return merge_by_date (ditto_sort (lst), ditto_sort (lst2));
|
|
default:
|
|
fprintf (aa_stderr, PROGNAME "525 Invalid Sort Type %d.\n",
|
|
ditsort_type);
|
|
DtSearchExit (32);
|
|
}
|
|
} /* ditto_sort() */
|
|
|
|
|
|
/************************************************/
|
|
/* */
|
|
/* DtSearchSortResults */
|
|
/* */
|
|
/************************************************/
|
|
/* Only publicly visible sort function.
|
|
* DtSearchSortResults() was formerly named aa_sort_dittolist.
|
|
*/
|
|
int DtSearchSortResults (DtSrResult ** dittolist, int sort_type)
|
|
{
|
|
switch (sort_type) {
|
|
case DtSrSORT_PROX:
|
|
case DtSrSORT_DATE:
|
|
ditsort_type = sort_type;
|
|
*dittolist = ditto_sort (*dittolist); /* recursive call */
|
|
return DtSrOK;
|
|
default:
|
|
DtSearchAddMessage (PROGNAME "140 "
|
|
"Program Error: Invalid sort type.");
|
|
return DtSrERROR;
|
|
}
|
|
} /* DtSearchSortResults() */
|
|
|
|
/**************************** DTSRJOINT.C *************************/
|