You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1325 lines
43 KiB
1325 lines
43 KiB
// *********************************************************************************
|
|
//
|
|
// Copyright (c) Microsoft Corporation
|
|
//
|
|
// Module Name:
|
|
//
|
|
// ShowResults.c
|
|
//
|
|
// Abstract:
|
|
//
|
|
// This modules has functions which are to shoow formatted Results on screen.
|
|
//
|
|
// Author:
|
|
//
|
|
// Sunil G.V.N. Murali ([email protected]) 24-Sep-2000
|
|
//
|
|
// Revision History:
|
|
//
|
|
// Sunil G.V.N. Murali ([email protected]) 01-Sep-2000 : Created It.
|
|
//
|
|
// *********************************************************************************
|
|
|
|
#include "pch.h"
|
|
#include "cmdline.h"
|
|
#include "cmdlineres.h"
|
|
|
|
/******************************************/
|
|
/*** CONSTANTS / DEFINES / ENUMERATIONS ***/
|
|
/******************************************/
|
|
|
|
// VAL1 = Buffer length ; VAL2 = Number of characters to copy.
|
|
#define MIN_VALUE( VAL1, VAL2 ) ( ( VAL1 > VAL2 ) ? VAL2 : VAL1 )
|
|
|
|
// Indexes for buffers to store strings
|
|
#define INDEX_TEMP_FORMAT_STRING 0
|
|
#define INDEX_TEMP_DYNARRAY_STRING 1
|
|
#define INDEX_TEMP_BUFFER 2
|
|
#define INDEX_TEMP_BUFFER_LEN4096 3
|
|
|
|
|
|
/********************************************************/
|
|
/*** Private functions ... used only within this file ***/
|
|
/********************************************************/
|
|
|
|
__inline
|
|
LPWSTR
|
|
GetSRTempBuffer( IN DWORD dwIndexNumber,
|
|
IN LPCWSTR pwszText,
|
|
IN DWORD dwLength,
|
|
IN BOOL bNullify )
|
|
/*++
|
|
Routine Description:
|
|
|
|
Since every file will need the temporary buffers -- in order to see
|
|
that their buffers wont be override with other functions, seperate
|
|
buffer space are created for each file.
|
|
This function will provide an access to internal buffers and also
|
|
safe guards the file buffer boundaries.
|
|
|
|
Arguments:
|
|
|
|
[ IN ] dwIndexNumber - File specific index number.
|
|
|
|
[ IN ] pwszText - Default text that needs to be copied into
|
|
temporary buffer.
|
|
|
|
[ IN ] dwLength - Length of the temporary buffer that is required.
|
|
Ignored when 'pwszText' is specified.
|
|
|
|
[ IN ] bNullify - Informs whether to clear the buffer or not
|
|
before giving the temporary buffer.
|
|
Set to 'TRUE' to clear buffer else FALSE.
|
|
|
|
Return Value:
|
|
|
|
NULL - When any failure occurs.
|
|
NOTE: do not rely on GetLastError to know the reason
|
|
for the failure.
|
|
|
|
success - Return memory address of the requested size.
|
|
|
|
NOTE:
|
|
----
|
|
If 'pwszText' and 'dwLength' both are NULL, then we treat that the caller
|
|
is asking for the reference of the buffer and we return the buffer address.
|
|
In this call, there wont be any memory allocations -- if the requested index
|
|
doesn't exist, failure is returned.
|
|
|
|
Also, the buffer returned by this function need not released by the caller.
|
|
While exiting from the tool, all the memory will be freed automatically by
|
|
the 'ReleaseGlobals' function.
|
|
|
|
Value contained by 'dwLength' parameter should be number of characters to
|
|
store. EX: Buffer requested is "abcd\0" then 'dwLength' should be 5 instead
|
|
of 10 ( 5 * sizeof( WCHAR ) ).
|
|
|
|
To get the size of a buffer, get a buffer pointer and pass it as argument to
|
|
'GetBufferSize' function.
|
|
--*/
|
|
{
|
|
if( TEMP_SHOWRESULTS_C_COUNT <= dwIndexNumber )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// Check if caller is requesting existing buffer contents
|
|
if( ( NULL == pwszText ) &&
|
|
( 0 == dwLength ) &&
|
|
( FALSE == bNullify ) )
|
|
{
|
|
// yes -- we need to pass the existing buffer contents
|
|
return GetInternalTemporaryBufferRef(
|
|
dwIndexNumber + INDEX_TEMP_SHOWRESULTS_C );
|
|
}
|
|
|
|
// ...
|
|
return GetInternalTemporaryBuffer(
|
|
dwIndexNumber + INDEX_TEMP_SHOWRESULTS_C, pwszText, dwLength, bNullify );
|
|
}
|
|
|
|
|
|
VOID
|
|
PrepareString(
|
|
TARRAY arrValues,
|
|
DWORD dwLength,
|
|
LPCWSTR szFormat,
|
|
LPCWSTR szSeperator
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Prepares the pszBuffer string by taking values from arrValues and
|
|
formate these values as per szFormat string.
|
|
|
|
Arguments:
|
|
[ in ] arrValues : values to be formated.
|
|
[ out ] pszBuffer : output string
|
|
[ in ] dwLength : string length.
|
|
[ in ] szFormat : format
|
|
[ in ] szSeperator : Seperator string
|
|
|
|
Return Value:
|
|
NONE
|
|
--*/
|
|
{
|
|
// local variables
|
|
DWORD dw = 0;
|
|
DWORD dwCount = 0;
|
|
DWORD dwTemp = 0;
|
|
LPWSTR pszTemp = NULL;
|
|
LPWSTR pszValue = NULL;
|
|
LPWSTR pszBuffer = NULL;
|
|
|
|
// Get temporary memory.
|
|
pszBuffer = GetSRTempBuffer( INDEX_TEMP_BUFFER_LEN4096, NULL, 0 , FALSE );
|
|
//
|
|
// kick off
|
|
if( ( NULL == pszBuffer ) || ( NULL == szFormat ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// init
|
|
StringCopy( pszBuffer, NULL_STRING, ( GetBufferSize( pszBuffer )/ sizeof( WCHAR ) ) );
|
|
dwCount = DynArrayGetCount( arrValues );
|
|
|
|
// allocate memory for buffers
|
|
pszTemp = GetSRTempBuffer( INDEX_TEMP_FORMAT_STRING, NULL, ( dwLength + 5 ) , TRUE );
|
|
pszValue = GetSRTempBuffer( INDEX_TEMP_DYNARRAY_STRING, NULL, ( dwLength + 5 ), TRUE );
|
|
if ( NULL == pszTemp || NULL == pszValue )
|
|
{
|
|
// release memories
|
|
return;
|
|
}
|
|
|
|
dwTemp = ( DWORD ) StringLengthInBytes( szSeperator );
|
|
//
|
|
// traverse thru the list of the values and concatenate them
|
|
// to the destination buffer
|
|
for( dw = 0; dw < dwCount; dw++ )
|
|
{
|
|
// get the current value into the temporary string buffer
|
|
DynArrayItemAsStringEx( arrValues, dw, pszValue, dwLength );
|
|
|
|
// concatenate the temporary string to the original buffer
|
|
StringCchPrintfW( pszTemp, (GetBufferSize(pszTemp)/sizeof(WCHAR)) - 1 ,
|
|
szFormat, _X( pszValue ) );
|
|
StringConcat( pszBuffer, pszTemp, ( GetBufferSize( pszBuffer )/ sizeof( WCHAR ) ) );
|
|
|
|
// check whether this is the last value or not
|
|
if ( dw + 1 < dwCount )
|
|
{
|
|
// there are some more values
|
|
// check whether is space for adding the seperator or not
|
|
if ( dwLength < dwTemp )
|
|
{
|
|
// no more space available ... break
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// add the seperator and update the length accordingly
|
|
StringConcat( pszBuffer, szSeperator, ( GetBufferSize( pszBuffer )/ sizeof( WCHAR ) ) );
|
|
dwLength -= dwTemp;
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
GetValue(
|
|
PTCOLUMNS pColumn,
|
|
DWORD dwColumn,
|
|
TARRAY arrRecord,
|
|
LPCWSTR szArraySeperator
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Gets the value from arrRecord and copies it to pszValue using
|
|
proper format.
|
|
|
|
Arguments:
|
|
[ in ] pColumn : format info.
|
|
[ in ] dwColumn : no of columns
|
|
[ in ] arrRecord : value to be formatted
|
|
[ out ] pszValue : output string
|
|
[ in ] szArraySeperator : seperator used.
|
|
|
|
Return Value:
|
|
NONE
|
|
--*/
|
|
{
|
|
// local variables
|
|
LPVOID pData = NULL; // data to be passed to formatter function
|
|
TARRAY arrTemp = NULL;
|
|
LPCWSTR pszTemp = NULL;
|
|
const TCHAR cszString[] = _T( "%s" );
|
|
const TCHAR cszDecimal[] = _T( "%d" );
|
|
const TCHAR cszFloat[] = _T( "%f" );
|
|
LPCWSTR pszFormat = NULL; // format
|
|
LPWSTR pszValue = NULL;
|
|
|
|
// variables used in formatting time
|
|
DWORD dwReturn = 0;
|
|
SYSTEMTIME systime;
|
|
|
|
pszValue = GetSRTempBuffer( INDEX_TEMP_BUFFER_LEN4096, NULL, 0 , FALSE );
|
|
|
|
if( ( NULL == pColumn ) ||
|
|
( NULL == pszValue ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ZeroMemory( &systime, sizeof( SYSTEMTIME ) );
|
|
// init first
|
|
StringCopy( pszValue, NULL_STRING, ( GetBufferSize( pszValue )/ sizeof( WCHAR ) ) );
|
|
|
|
// get the column value and do formatting appropriately
|
|
switch( pColumn->dwFlags & SR_TYPE_MASK )
|
|
{
|
|
case SR_TYPE_STRING:
|
|
{
|
|
// identify the format to be used
|
|
if ( pColumn->dwFlags & SR_VALUEFORMAT )
|
|
{
|
|
pszFormat = pColumn->szFormat;
|
|
}
|
|
else
|
|
{
|
|
pszFormat = cszString; // default format
|
|
}
|
|
|
|
// copy the value to the temporary buffer based on the flags specified
|
|
if ( pColumn->dwFlags & SR_ARRAY )
|
|
{
|
|
// get the value into buffer first - AVOIDING PREFIX BUGS
|
|
arrTemp = DynArrayItem( arrRecord, dwColumn );
|
|
if ( NULL == arrTemp )
|
|
{
|
|
return;
|
|
}
|
|
// form the array of values into one single string with ',' seperated
|
|
PrepareString( arrTemp, pColumn->dwWidth,
|
|
pszFormat, szArraySeperator );
|
|
}
|
|
else
|
|
{
|
|
// get the value into buffer first - AVOIDING PREFIX BUGS
|
|
pszTemp = DynArrayItemAsString( arrRecord, dwColumn );
|
|
if ( NULL == pszTemp )
|
|
{
|
|
return;
|
|
}
|
|
// now copy the value into buffer
|
|
StringCchPrintfW( pszValue, ( GetBufferSize( pszValue )/ sizeof( WCHAR ) ) - 1, pszFormat, pszTemp );
|
|
}
|
|
|
|
// switch case completed
|
|
break;
|
|
}
|
|
|
|
case SR_TYPE_NUMERIC:
|
|
{
|
|
// identify the format to be used
|
|
if ( pColumn->dwFlags & SR_VALUEFORMAT )
|
|
{
|
|
pszFormat = pColumn->szFormat;
|
|
}
|
|
else
|
|
{
|
|
pszFormat = cszDecimal; // default format
|
|
}
|
|
|
|
// copy the value to the temporary buffer based on the flags specified
|
|
if ( pColumn->dwFlags & SR_ARRAY )
|
|
{
|
|
// get the value into buffer first - AVOIDING PREFIX BUGS
|
|
arrTemp = DynArrayItem( arrRecord, dwColumn );
|
|
if ( NULL == arrTemp )
|
|
{
|
|
return;
|
|
}
|
|
// form the array of values into one single string with ',' seperated
|
|
PrepareString( arrTemp, pColumn->dwWidth,
|
|
pszFormat, szArraySeperator );
|
|
}
|
|
else
|
|
{
|
|
// get the value using format specified
|
|
StringCchPrintfW( pszValue, ( GetBufferSize( pszValue )/ sizeof( WCHAR ) ) - 1, pszFormat,
|
|
DynArrayItemAsDWORD( arrRecord, dwColumn ) );
|
|
}
|
|
|
|
// switch case completed
|
|
break;
|
|
}
|
|
|
|
case SR_TYPE_FLOAT:
|
|
{
|
|
// identify the format to be used
|
|
// NOTE: for this type, format needs to be specified
|
|
// if not, value displayed is unpredictable
|
|
if ( pColumn->dwFlags & SR_VALUEFORMAT )
|
|
{
|
|
pszFormat = pColumn->szFormat;
|
|
}
|
|
else
|
|
{
|
|
pszFormat = cszFloat; // default format
|
|
}
|
|
|
|
// copy the value to the temporary buffer based on the flags specified
|
|
if ( pColumn->dwFlags & SR_ARRAY )
|
|
{
|
|
// get the value into buffer first - AVOIDING PREFIX BUGS
|
|
arrTemp = DynArrayItem( arrRecord, dwColumn );
|
|
if ( NULL == arrTemp )
|
|
{
|
|
return;
|
|
}
|
|
// form the array of values into one single string with ',' seperated
|
|
PrepareString( arrTemp,
|
|
pColumn->dwWidth, pszFormat, szArraySeperator );
|
|
}
|
|
else
|
|
{
|
|
// get the value using format specified
|
|
StringCchPrintfW( pszValue, ( GetBufferSize( pszValue )/ sizeof( WCHAR ) ) - 1, pszFormat,
|
|
DynArrayItemAsFloat( arrRecord, dwColumn ) );
|
|
}
|
|
|
|
// switch case completed
|
|
break;
|
|
}
|
|
|
|
case SR_TYPE_DOUBLE:
|
|
{
|
|
// identify the format to be used
|
|
// NOTE: for this type, format needs to be specified
|
|
// if not, value displayed is unpredictable
|
|
if ( pColumn->dwFlags & SR_VALUEFORMAT )
|
|
{
|
|
pszFormat = pColumn->szFormat;
|
|
}
|
|
else
|
|
{
|
|
pszFormat = cszFloat; // default format
|
|
}
|
|
|
|
// copy the value to the temporary buffer based on the flags specified
|
|
if ( pColumn->dwFlags & SR_ARRAY )
|
|
{
|
|
// get the value into buffer first - AVOIDING PREFIX BUGS
|
|
arrTemp = DynArrayItem( arrRecord, dwColumn );
|
|
if ( NULL == arrTemp )
|
|
{
|
|
return;
|
|
}
|
|
// form the array of values into one single string with ',' seperated
|
|
PrepareString( arrTemp, pColumn->dwWidth,
|
|
pszFormat, szArraySeperator );
|
|
}
|
|
else
|
|
{
|
|
// get the value using format specified
|
|
StringCchPrintfW( pszValue, ( GetBufferSize( pszValue )/ sizeof( WCHAR ) ) - 1, pszFormat,
|
|
DynArrayItemAsDouble( arrRecord, dwColumn ) );
|
|
}
|
|
|
|
// switch case completed
|
|
break;
|
|
}
|
|
|
|
case SR_TYPE_TIME:
|
|
{
|
|
// get the time in the required format
|
|
systime = DynArrayItemAsSystemTime( arrRecord, dwColumn );
|
|
|
|
// get the time in current locale format
|
|
dwReturn = GetTimeFormat( LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE,
|
|
&systime, NULL, pszValue, MAX_STRING_LENGTH );
|
|
|
|
// check the result
|
|
if ( 0 == dwReturn )
|
|
{
|
|
// save the error info that has occurred
|
|
SaveLastError();
|
|
StringCopy( pszValue, GetReason(), ( GetBufferSize( pszValue )/ sizeof( WCHAR ) ) );
|
|
}
|
|
|
|
// switch case completed
|
|
break;
|
|
}
|
|
|
|
case SR_TYPE_CUSTOM:
|
|
{
|
|
// check whether function pointer is specified or not
|
|
// if not specified, error
|
|
if ( NULL == pColumn->pFunction )
|
|
{
|
|
return; // function ptr not specified ... error
|
|
}
|
|
// determine the data to be passed to the formatter function
|
|
pData = pColumn->pFunctionData;
|
|
if ( NULL == pData ) // function data is not defined
|
|
{
|
|
pData = pColumn; // the current column info itself as data
|
|
}
|
|
// call the custom function
|
|
( *pColumn->pFunction)( dwColumn, arrRecord, pData, pszValue );
|
|
|
|
// switch case completed
|
|
break;
|
|
}
|
|
|
|
case SR_TYPE_DATE:
|
|
case SR_TYPE_DATETIME:
|
|
default:
|
|
{
|
|
// this should not occur ... still
|
|
StringCopy( pszValue, NULL_STRING, ( GetBufferSize( pszValue )/ sizeof( WCHAR ) ) );
|
|
|
|
// switch case completed
|
|
break;
|
|
}
|
|
}
|
|
|
|
// user wants to display "N/A", when the value is empty, copy that
|
|
if ( 0 == lstrlen( pszValue ) && pColumn->dwFlags & SR_SHOW_NA_WHEN_BLANK )
|
|
{
|
|
// copy N/A
|
|
StringCopy( pszValue, V_NOT_AVAILABLE, ( GetBufferSize( pszValue )/ sizeof( WCHAR ) ) );
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
__DisplayTextWrapped(
|
|
FILE* fp,
|
|
LPWSTR pszValue,
|
|
LPCWSTR pszSeperator,
|
|
DWORD dwWidth
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Data is written to file depending upon the number of
|
|
bytes ( width ) to be displayed.
|
|
|
|
If number of bytes to be displayed is greater than
|
|
max bytes ( width ) then text is wrapped to max bytes
|
|
length.
|
|
|
|
Arguments:
|
|
[ in ] fp - File such as stdout, stderr
|
|
etc. on to which data needs
|
|
to be written.
|
|
[ in ] pszValue - Contains data to be displayed.
|
|
[ in ] pszSeperator - Contains data seperator.
|
|
[ in ] dwWidth - Max bytes that can be displyed.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
|
|
{
|
|
// local variables
|
|
LPWSTR pszBuffer = NULL;
|
|
LPCWSTR pszRestValue = NULL;
|
|
DWORD dwTemp = 0;
|
|
DWORD dwLength = 0;
|
|
DWORD dwSepLength = 0;
|
|
DWORD dwLenMemAlloc = 0;
|
|
|
|
// check the input
|
|
if ( NULL == pszValue || 0 == dwWidth || NULL == fp )
|
|
{
|
|
return;
|
|
}
|
|
// allocate buffer
|
|
dwLenMemAlloc = StringLengthInBytes( pszValue );
|
|
if ( dwLenMemAlloc < dwWidth )
|
|
{
|
|
dwLenMemAlloc = dwWidth;
|
|
}
|
|
// ...
|
|
pszBuffer = GetSRTempBuffer( INDEX_TEMP_BUFFER, NULL, ( dwLenMemAlloc + 5 ), TRUE );
|
|
if ( NULL == pszBuffer )
|
|
{
|
|
OUT_OF_MEMORY();
|
|
SaveLastError();
|
|
// null-ify the remaining text
|
|
StringCopy( pszValue, NULL_STRING, ( GetBufferSize( pszValue )/ sizeof( WCHAR ) ) );
|
|
return;
|
|
}
|
|
|
|
// determine the length of the seperator
|
|
dwSepLength = 0;
|
|
if ( NULL != pszSeperator )
|
|
{
|
|
dwSepLength = StringLengthInBytes( pszSeperator );
|
|
}
|
|
// determine the length of the data that can be displayed in this row
|
|
dwTemp = 0;
|
|
dwLength = 0;
|
|
for( ;; )
|
|
{
|
|
pszRestValue = NULL;
|
|
if ( NULL != pszSeperator )
|
|
{
|
|
pszRestValue = FindString( pszValue, pszSeperator, dwLength );
|
|
}
|
|
// check whether seperator is found or not
|
|
if ( NULL != pszRestValue )
|
|
{
|
|
// determine the position
|
|
dwTemp = StringLengthInBytes( pszValue ) -
|
|
StringLengthInBytes( pszRestValue ) + dwSepLength;
|
|
|
|
// check the length
|
|
if ( dwTemp >= dwWidth )
|
|
{
|
|
// string position exceed the max. width
|
|
if ( 0 == dwLength || dwTemp == dwWidth )
|
|
{
|
|
dwLength = dwWidth;
|
|
}
|
|
// break from the loop
|
|
break;
|
|
}
|
|
|
|
// store the current position
|
|
dwLength = dwTemp;
|
|
}
|
|
else
|
|
{
|
|
// check if length is determined or not .. if not required width itself is length
|
|
if ( 0 == dwLength || ((StringLengthInBytes( pszValue ) - dwLength) > dwWidth) )
|
|
{
|
|
dwLength = dwWidth;
|
|
}
|
|
else
|
|
{
|
|
if ( StringLengthInBytes( pszValue ) <= (LONG) dwWidth )
|
|
{
|
|
dwLength = StringLengthInBytes( pszValue );
|
|
}
|
|
}
|
|
|
|
// break the loop
|
|
break;
|
|
}
|
|
}
|
|
|
|
// get the partial value that has to be displayed
|
|
StringCopy( pszBuffer, pszValue,
|
|
MIN_VALUE( dwLenMemAlloc, ( dwLength + 1 ) ) ); // +1 for NULL character
|
|
AdjustStringLength( pszBuffer, dwWidth, FALSE ); // adjust the string
|
|
ShowMessage( fp, _X( pszBuffer ) ); // display the value
|
|
|
|
// change the buffer contents so that it contains rest of the undisplayed text
|
|
StringCopy( pszBuffer, pszValue, ( GetBufferSize( pszBuffer )/ sizeof( WCHAR ) ) );
|
|
if ( StringLengthInBytes( pszValue ) > (LONG) dwLength )
|
|
{
|
|
StringCopy( pszValue, pszBuffer + dwLength, ( GetBufferSize( pszValue )/ sizeof( WCHAR ) ) );
|
|
}
|
|
else
|
|
{
|
|
StringCopy( pszValue, _T( "" ), ( GetBufferSize( pszValue )/ sizeof( WCHAR ) ) );
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
__ShowAsTable(
|
|
FILE* fp,
|
|
DWORD dwColumns,
|
|
PTCOLUMNS pColumns,
|
|
DWORD dwFlags,
|
|
TARRAY arrData
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Displays the arrData in Tabular form.
|
|
|
|
Arguments:
|
|
[ in ] fp : Output Device
|
|
[ in ] dwColumns : no. of columns
|
|
[ in ] pColumns : Header strings
|
|
[ in ] dwFlags : flags
|
|
[ in ] arrData : data to be shown
|
|
|
|
Return Value:
|
|
NONE
|
|
--*/
|
|
{
|
|
// local variables
|
|
DWORD dwCount = 0; // holds the count of the records
|
|
DWORD i = 0; // looping variables
|
|
DWORD j = 0; // looping variables
|
|
DWORD k = 0; // looping variables
|
|
DWORD dwColumn = 0;
|
|
LONG lLastColumn = 0;
|
|
DWORD dwMultiLineColumns = 0;
|
|
BOOL bNeedSpace = FALSE;
|
|
BOOL bPadLeft = FALSE;
|
|
TARRAY arrRecord = NULL;
|
|
TARRAY arrMultiLine = NULL;
|
|
LPCWSTR pszData = NULL;
|
|
LPCWSTR pszSeperator = NULL;
|
|
LPWSTR szValue = NULL_STRING; // custom value formatter
|
|
|
|
// constants
|
|
const DWORD cdwColumn = 0;
|
|
const DWORD cdwSeperator = 1;
|
|
const DWORD cdwData = 2;
|
|
|
|
if( ( NULL == fp ) || ( NULL == pColumns ) )
|
|
{
|
|
INVALID_PARAMETER();
|
|
SaveLastError();
|
|
return ;
|
|
}
|
|
|
|
// Allocate temporary memory.
|
|
szValue = GetSRTempBuffer( INDEX_TEMP_BUFFER_LEN4096, NULL, 4096 , TRUE );
|
|
// create an multi-line data display helper array
|
|
arrMultiLine = CreateDynamicArray();
|
|
if ( ( NULL == arrMultiLine ) || ( NULL == szValue ) )
|
|
{
|
|
OUT_OF_MEMORY();
|
|
SaveLastError();
|
|
return;
|
|
}
|
|
|
|
// check whether header has to be displayed or not
|
|
if ( ! ( dwFlags & SR_NOHEADER ) )
|
|
{
|
|
//
|
|
// header needs to be displayed
|
|
|
|
// traverse thru the column headers and display
|
|
bNeedSpace = FALSE;
|
|
for ( i = 0; i < dwColumns; i++ )
|
|
{
|
|
// check whether user wants to display this column or not
|
|
if ( pColumns[ i ].dwFlags & SR_HIDECOLUMN )
|
|
{
|
|
continue; // user doesn't want this column to be displayed .. skip
|
|
}
|
|
// determine the padding direction
|
|
bPadLeft = FALSE;
|
|
if ( pColumns[ i ].dwFlags & SR_ALIGN_LEFT )
|
|
{
|
|
bPadLeft = TRUE;
|
|
}
|
|
else
|
|
{
|
|
switch( pColumns[ i ].dwFlags & SR_TYPE_MASK )
|
|
{
|
|
case SR_TYPE_NUMERIC:
|
|
case SR_TYPE_FLOAT:
|
|
case SR_TYPE_DOUBLE:
|
|
bPadLeft = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// check if column header seperator is needed or not and based on that show
|
|
if ( TRUE == bNeedSpace )
|
|
{
|
|
// show space as column header seperator
|
|
ShowMessage( fp, _T( " " ) );
|
|
}
|
|
|
|
// print the column heading
|
|
// NOTE: column will be displayed either by expanding or shrinking
|
|
// based on the length of the column heading as well as width of the column
|
|
StringCopy( szValue, pColumns[ i ].szColumn, ( GetBufferSize( szValue )/ sizeof( WCHAR ) ) );
|
|
AdjustStringLength( szValue, pColumns[ i ].dwWidth, bPadLeft );
|
|
ShowMessage( fp, szValue ); // column heading
|
|
|
|
// inform that from next time onward display column header separator
|
|
bNeedSpace = TRUE;
|
|
}
|
|
|
|
// display the new line character ... seperation b/w headings and separator line
|
|
ShowMessage( fp, _T( "\n" ) );
|
|
|
|
// display the seperator chars under each column header
|
|
bNeedSpace = FALSE;
|
|
for ( i = 0; i < dwColumns; i++ )
|
|
{
|
|
// check whether user wants to display this column or not
|
|
if ( pColumns[ i ].dwFlags & SR_HIDECOLUMN )
|
|
{
|
|
continue; // user doesn't want this column to be displayed .. skip
|
|
}
|
|
// check if column header seperator is needed or not and based on that show
|
|
if ( TRUE == bNeedSpace )
|
|
{
|
|
// show space as column header seperator
|
|
ShowMessage( fp, _T( " " ) );
|
|
}
|
|
|
|
// display seperator based on the required column width
|
|
Replicate( szValue, _T( "=" ), pColumns[ i ].dwWidth, pColumns[ i ].dwWidth + 1 );
|
|
ShowMessage( fp, szValue );
|
|
|
|
// inform that from next time onward display column header separator
|
|
bNeedSpace = TRUE;
|
|
}
|
|
|
|
// display the new line character ... seperation b/w headings and actual data
|
|
ShowMessage( fp, _T( "\n" ) );
|
|
}
|
|
|
|
//
|
|
// start displaying
|
|
|
|
// get the total no. of records available
|
|
dwCount = DynArrayGetCount( arrData );
|
|
|
|
// traverse thru the records one-by-one
|
|
for( i = 0; i < dwCount; i++ )
|
|
{
|
|
// clear the existing value
|
|
StringCopy( szValue, NULL_STRING, ( GetBufferSize( szValue )/ sizeof( WCHAR ) ) );
|
|
|
|
// get the pointer to the current record
|
|
arrRecord = DynArrayItem( arrData, i );
|
|
if ( NULL == arrRecord )
|
|
{
|
|
continue;
|
|
}
|
|
// traverse thru the columns and display the values
|
|
bNeedSpace = FALSE;
|
|
for ( j = 0; j < dwColumns; j++ )
|
|
{
|
|
// sub-local variables used in this loop
|
|
DWORD dwTempWidth = 0;
|
|
BOOL bTruncation = FALSE;
|
|
|
|
// check whether user wants to display this column or not
|
|
if ( pColumns[ j ].dwFlags & SR_HIDECOLUMN )
|
|
{
|
|
continue; // user doesn't want this column to be displayed .. skip
|
|
}
|
|
// get the value of the column
|
|
// NOTE: CHECK IF USER ASKED NOT TO TRUNCATE THE DATA OR NOT
|
|
if ( pColumns[ j ].dwFlags & SR_NO_TRUNCATION )
|
|
{
|
|
bTruncation = TRUE;
|
|
dwTempWidth = pColumns[ j ].dwWidth;
|
|
pColumns[ j ].dwWidth = ( GetBufferSize( szValue )/ sizeof( WCHAR ) );
|
|
}
|
|
|
|
// prepare the value
|
|
GetValue( &pColumns[ j ], j, arrRecord, _T( ", " ) );
|
|
|
|
// determine the padding direction
|
|
bPadLeft = FALSE;
|
|
if ( FALSE == bTruncation )
|
|
{
|
|
if ( pColumns[ j ].dwFlags & SR_ALIGN_LEFT )
|
|
{
|
|
bPadLeft = TRUE;
|
|
}
|
|
else
|
|
{
|
|
switch( pColumns[ j ].dwFlags & SR_TYPE_MASK )
|
|
{
|
|
case SR_TYPE_NUMERIC:
|
|
case SR_TYPE_FLOAT:
|
|
case SR_TYPE_DOUBLE:
|
|
bPadLeft = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// adjust ...
|
|
AdjustStringLength( szValue, pColumns[ j ].dwWidth, bPadLeft );
|
|
}
|
|
|
|
// reset the width of the current column if it is modified
|
|
if ( TRUE == bTruncation )
|
|
{
|
|
pColumns[ j ].dwWidth = dwTempWidth;
|
|
}
|
|
// check if column header seperator is needed or not and based on that show
|
|
if ( TRUE == bNeedSpace )
|
|
{
|
|
// show space as column header seperator
|
|
ShowMessage( fp, _T( " " ) );
|
|
}
|
|
|
|
// now display the value
|
|
if ( pColumns[ j ].dwFlags & SR_WORDWRAP )
|
|
{
|
|
// display the text ( might be partial )
|
|
__DisplayTextWrapped( fp, szValue, _T( ", " ), pColumns[ j ].dwWidth );
|
|
|
|
// check if any info is left to be displayed
|
|
if ( 0 != StringLengthInBytes( szValue ) )
|
|
{
|
|
LONG lIndex = 0;
|
|
lIndex = DynArrayAppendRow( arrMultiLine, 3 );
|
|
if ( -1 != lIndex )
|
|
{
|
|
DynArraySetDWORD2( arrMultiLine, lIndex, cdwColumn, j );
|
|
DynArraySetString2( arrMultiLine, lIndex, cdwData, szValue, 0 );
|
|
DynArraySetString2( arrMultiLine, lIndex,
|
|
cdwSeperator, _T( ", " ), 0 );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ShowMessage( fp, _X( szValue ) );
|
|
}
|
|
|
|
// inform that from next time onward display column header separator
|
|
bNeedSpace = TRUE;
|
|
}
|
|
|
|
// display the new line character ... seperation b/w two record
|
|
ShowMessage( fp, _T( "\n" ) );
|
|
|
|
// now display the multi-line column values
|
|
dwMultiLineColumns = DynArrayGetCount( arrMultiLine );
|
|
while( 0 != dwMultiLineColumns )
|
|
{
|
|
// reset
|
|
dwColumn = 0;
|
|
lLastColumn = -1;
|
|
bNeedSpace = FALSE;
|
|
|
|
// ...
|
|
for( j = 0; j < dwMultiLineColumns; j++ )
|
|
{
|
|
// ge the column number
|
|
dwColumn = DynArrayItemAsDWORD2( arrMultiLine, j, cdwColumn );
|
|
|
|
// show spaces till the current column from the last column
|
|
for( k = lLastColumn + 1; k < dwColumn; k++ )
|
|
{
|
|
// check whether user wants to display this column or not
|
|
if ( pColumns[ k ].dwFlags & SR_HIDECOLUMN )
|
|
{
|
|
continue; // user doesn't want this column to be displayed .. skip
|
|
}
|
|
// check if column header seperator is needed or not and based on that show
|
|
if ( TRUE == bNeedSpace )
|
|
{
|
|
// show space as column header seperator
|
|
ShowMessage( fp, _T( " " ) );
|
|
}
|
|
|
|
// display seperator based on the required column width
|
|
Replicate( szValue, _T( " " ), pColumns[ k ].dwWidth,
|
|
pColumns[ k ].dwWidth + 1 );
|
|
ShowMessage( fp, szValue );
|
|
|
|
// inform that from next time onward display column header separator
|
|
bNeedSpace = TRUE;
|
|
}
|
|
|
|
// update the last column
|
|
lLastColumn = dwColumn;
|
|
|
|
// check if column header seperator is needed or not and based on that show
|
|
if ( TRUE == bNeedSpace )
|
|
{
|
|
// show space as column header seperator
|
|
ShowMessage( fp, _T( " " ) );
|
|
}
|
|
|
|
// get the seperator and data
|
|
pszData = DynArrayItemAsString2( arrMultiLine, j, cdwData );
|
|
pszSeperator = DynArrayItemAsString2( arrMultiLine, j, cdwSeperator );
|
|
if ( NULL == pszData || NULL == pszSeperator )
|
|
{
|
|
continue;
|
|
}
|
|
// display the information
|
|
StringCopy( szValue, pszData, ( GetBufferSize( szValue )/ sizeof( WCHAR ) ) );
|
|
__DisplayTextWrapped( fp, szValue, pszSeperator,
|
|
pColumns[ dwColumn ].dwWidth );
|
|
|
|
// update the multi-line array with rest of the line
|
|
if ( 0 == StringLengthInBytes( szValue ) )
|
|
{
|
|
// data in this column is completely displayed ... remove it
|
|
DynArrayRemove( arrMultiLine, j );
|
|
|
|
// update the indexes
|
|
j--;
|
|
dwMultiLineColumns--;
|
|
}
|
|
else
|
|
{
|
|
// update the multi-line array with the remaining value
|
|
DynArraySetString2( arrMultiLine, j, cdwData, szValue, 0 );
|
|
}
|
|
}
|
|
|
|
// display the new line character ... seperation b/w two lines
|
|
ShowMessage( fp, _T( "\n" ) );
|
|
}
|
|
}
|
|
|
|
// destroy the array
|
|
DestroyDynamicArray( &arrMultiLine );
|
|
}
|
|
|
|
|
|
VOID
|
|
__ShowAsList(
|
|
FILE* fp,
|
|
DWORD dwColumns,
|
|
PTCOLUMNS pColumns,
|
|
DWORD dwFlags,
|
|
TARRAY arrData
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Displays the in List format
|
|
|
|
Arguments:
|
|
[ in ] fp : Output Device
|
|
[ in ] dwColumns : no. of columns
|
|
[ in ] pColumns : Header strings
|
|
[ in ] dwFlags : flags
|
|
[ in ] arrData : data to be shown
|
|
|
|
Return Value:
|
|
NONE
|
|
--*/
|
|
{
|
|
// local variables
|
|
DWORD dwCount = 0; // holds the count of all records
|
|
DWORD i = 0, j = 0; // looping variables
|
|
DWORD dwTempWidth = 0;
|
|
DWORD dwMaxColumnLen = 0; // holds the length of the which max. among all the columns
|
|
LPWSTR pszTemp = NULL;
|
|
TARRAY arrRecord = NULL;
|
|
__STRING_64 szBuffer = NULL_STRING;
|
|
LPWSTR szValue = NULL_STRING; // custom value formatter
|
|
|
|
UNREFERENCED_PARAMETER( dwFlags );
|
|
|
|
if( ( NULL == fp ) || ( NULL == pColumns ) )
|
|
{
|
|
INVALID_PARAMETER();
|
|
SaveLastError();
|
|
return ;
|
|
}
|
|
|
|
// Allocate temporary memory.
|
|
szValue = GetSRTempBuffer( INDEX_TEMP_BUFFER_LEN4096, NULL, 4096 , TRUE );
|
|
if( NULL == szValue )
|
|
{
|
|
OUT_OF_MEMORY();
|
|
SaveLastError();
|
|
return;
|
|
}
|
|
|
|
// find out the max. length among all the column headers
|
|
dwMaxColumnLen = 0;
|
|
for ( i = 0; i < dwColumns; i++ )
|
|
{
|
|
dwTempWidth = ( DWORD ) StringLengthInBytes( pColumns[ i ].szColumn );
|
|
if ( dwMaxColumnLen < dwTempWidth )
|
|
{
|
|
dwMaxColumnLen = dwTempWidth;
|
|
}
|
|
}
|
|
|
|
// start displaying the data
|
|
|
|
// get the total no. of records available
|
|
dwCount = DynArrayGetCount( arrData );
|
|
|
|
// get the total no. of records available
|
|
for( i = 0; i < dwCount; i++ )
|
|
{
|
|
// get the pointer to the current record
|
|
arrRecord = DynArrayItem( arrData, i );
|
|
if ( NULL == arrRecord )
|
|
{
|
|
continue;
|
|
}
|
|
// traverse thru the columns and display the values
|
|
for ( j = 0; j < dwColumns; j++)
|
|
{
|
|
// clear the existing value
|
|
StringCopy( szValue, NULL_STRING, ( GetBufferSize( szValue )/ sizeof( WCHAR ) ) );
|
|
|
|
// check whether user wants to display this column or not
|
|
if ( pColumns[ j ].dwFlags & SR_HIDECOLUMN )
|
|
{
|
|
continue; // user doesn't want this column to be displayed .. skip
|
|
}
|
|
// display the column heading and its value
|
|
// ( heading will be displayed based on the max. column length )
|
|
StringCchPrintfW( szValue, ( GetBufferSize( szValue )/ sizeof( WCHAR ) ) - 1,
|
|
_T( "%s:" ), pColumns[ j ].szColumn);
|
|
AdjustStringLength( szValue, dwMaxColumnLen + 2, FALSE );
|
|
|
|
ShowMessage( fp, szValue );
|
|
|
|
// get the value of the column
|
|
dwTempWidth = pColumns[ j ].dwWidth; // save the current width
|
|
pColumns[ j ].dwWidth = ( GetBufferSize( szValue )/ sizeof( WCHAR ) ); // change the width
|
|
GetValue( &pColumns[ j ], j, arrRecord, _T( "\n" ) );
|
|
pColumns[ j ].dwWidth = dwTempWidth; // restore the original width
|
|
|
|
// display the [ list of ] values
|
|
pszTemp = _tcstok( szValue, _T( "\n" ) );
|
|
while ( NULL != pszTemp )
|
|
{
|
|
// display the value
|
|
ShowMessage( fp, _X( pszTemp ) );
|
|
pszTemp = _tcstok( NULL, _T( "\n" ) );
|
|
if ( NULL != pszTemp )
|
|
{
|
|
// prepare the next line
|
|
StringCopy( szBuffer, _T( " " ), ( GetBufferSize( szValue )/ sizeof( WCHAR ) ) );
|
|
AdjustStringLength( szBuffer, dwMaxColumnLen + 2, FALSE );
|
|
ShowMessage( fp, _T( "\n" ) );
|
|
ShowMessage( fp, _X( szBuffer ) );
|
|
}
|
|
}
|
|
|
|
// display the next line character seperation b/w two fields
|
|
ShowMessage( fp, _T( "\n" ) );
|
|
}
|
|
|
|
// display the new line character ... seperation b/w two records
|
|
// NOTE: do this only if there are some more records
|
|
if ( i + 1 < dwCount )
|
|
{
|
|
ShowMessage( fp, _T( "\n" ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
__ShowAsCSV(
|
|
FILE* fp,
|
|
DWORD dwColumns,
|
|
PTCOLUMNS pColumns,
|
|
DWORD dwFlags,
|
|
TARRAY arrData
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Displays the arrData in CSV form.
|
|
|
|
Arguments:
|
|
[ in ] fp : Output Device
|
|
[ in ] dwColumns : no. of columns
|
|
[ in ] pColumns : Header strings
|
|
[ in ] dwFlags : flags
|
|
[ in ] arrData : data to be shown
|
|
|
|
Return Value:
|
|
NONE
|
|
--*/
|
|
{
|
|
// local variables
|
|
DWORD dwCount = 0; // holds the count of all records
|
|
DWORD i = 0; // looping variables
|
|
DWORD j = 0; // looping variables
|
|
DWORD dwTempWidth = 0;
|
|
BOOL bNeedComma = FALSE;
|
|
TARRAY arrRecord = NULL;
|
|
LPWSTR szValue = NULL_STRING;
|
|
|
|
if( ( NULL == fp ) || ( NULL == pColumns ) )
|
|
{
|
|
INVALID_PARAMETER();
|
|
SaveLastError();
|
|
return ;
|
|
}
|
|
|
|
// Allocate temporary memory.
|
|
szValue = GetSRTempBuffer( INDEX_TEMP_BUFFER_LEN4096, NULL, 4096 , TRUE );
|
|
if( NULL == szValue )
|
|
{
|
|
OUT_OF_MEMORY();
|
|
SaveLastError();
|
|
return;
|
|
}
|
|
|
|
// check whether header has to be displayed or not
|
|
if ( ! ( dwFlags & SR_NOHEADER ) )
|
|
{
|
|
//
|
|
// header needs to be displayed
|
|
|
|
// first display the columns ... with comma seperated
|
|
bNeedComma = FALSE;
|
|
for ( i = 0; i < dwColumns; i++ )
|
|
{
|
|
// check whether user wants to display this column or not
|
|
if ( pColumns[ i ].dwFlags & SR_HIDECOLUMN )
|
|
continue; // user doesn't want this column to be displayed .. skip
|
|
|
|
// check whether we need to display ',' or not and then display
|
|
if ( TRUE == bNeedComma )
|
|
{
|
|
// ',' has to be displayed
|
|
ShowMessage( fp, _T( "," ) );
|
|
}
|
|
|
|
// display the column heading
|
|
StringCchPrintfW( szValue, ( GetBufferSize( szValue )/ sizeof( WCHAR ) ) - 1,
|
|
_T( "\"%s\"" ), pColumns[ i ].szColumn );
|
|
DISPLAY_MESSAGE ( fp, szValue );
|
|
|
|
// inform that from next time onwards we need to display comma before data
|
|
bNeedComma = TRUE;
|
|
}
|
|
|
|
// new line character
|
|
ShowMessage( fp, _T( "\n" ) );
|
|
}
|
|
|
|
//
|
|
// start displaying the data
|
|
|
|
// get the total no. of records available
|
|
dwCount = DynArrayGetCount( arrData );
|
|
|
|
// get the total no. of records available
|
|
for( i = 0; i < dwCount; i++ )
|
|
{
|
|
// get the pointer to the current record
|
|
arrRecord = DynArrayItem( arrData, i );
|
|
if ( NULL == arrRecord )
|
|
continue;
|
|
|
|
// traverse thru the columns and display the values
|
|
bNeedComma = FALSE;
|
|
for ( j = 0; j < dwColumns; j++ )
|
|
{
|
|
// clear the existing value
|
|
StringCopy( szValue, NULL_STRING, ( GetBufferSize( szValue )/ sizeof( WCHAR ) ) );
|
|
|
|
// check whether user wants to display this column or not
|
|
if ( pColumns[ j ].dwFlags & SR_HIDECOLUMN )
|
|
continue; // user doesn't want this column to be displayed .. skip
|
|
|
|
// get the value of the column
|
|
dwTempWidth = pColumns[ j ].dwWidth; // save the current width
|
|
pColumns[ j ].dwWidth = ( GetBufferSize( szValue )/ sizeof( WCHAR ) ); // change the width
|
|
GetValue( &pColumns[ j ], j, arrRecord, _T( "," ) );
|
|
pColumns[ j ].dwWidth = dwTempWidth; // restore the original width
|
|
|
|
// check whether we need to display ',' or not and then display
|
|
if ( TRUE == bNeedComma )
|
|
{
|
|
// ',' has to be displayed
|
|
ShowMessage( fp, _T( "," ) );
|
|
}
|
|
|
|
// print the value
|
|
ShowMessage( fp, _T( "\"" ) );
|
|
ShowMessage( fp, _X( szValue ) );
|
|
ShowMessage( fp, _T( "\"" ) );
|
|
|
|
// inform that from next time onwards we need to display comma before data
|
|
bNeedComma = TRUE;
|
|
}
|
|
|
|
// new line character
|
|
ShowMessage( fp, _T( "\n" ) );
|
|
}
|
|
}
|
|
|
|
//
|
|
// public functions ... exposed to external world
|
|
//
|
|
|
|
VOID
|
|
ShowResults(
|
|
DWORD dwColumns,
|
|
PTCOLUMNS pColumns,
|
|
DWORD dwFlags,
|
|
TARRAY arrData
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Wrapper function to ShowResults2.
|
|
|
|
Arguments:
|
|
[ in ] dwColumns : No. of Columns to be shown
|
|
[ in ] pColumns : Columns header
|
|
[ in ] dwFlags : Required format
|
|
[ in ] arrData : Data to be displayed.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
// just call the main function ... with stdout
|
|
ShowResults2( stdout, dwColumns, pColumns, dwFlags, arrData );
|
|
}
|
|
|
|
|
|
VOID
|
|
ShowResults2(
|
|
FILE* fp,
|
|
DWORD dwColumns,
|
|
PTCOLUMNS pColumns,
|
|
DWORD dwFlags,
|
|
TARRAY arrData
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Show the resuls (arrData) on the screen.
|
|
|
|
Arguments:
|
|
[ in ] fp : File on to which data is to be displayed.
|
|
[ in ] dwColumns : No. of Columns to be shown
|
|
[ in ] pColumns : Columns header
|
|
[ in ] dwFlags : Required format
|
|
[ in ] arrData : Data to be displayed.
|
|
|
|
Return Value:
|
|
NONE
|
|
--*/
|
|
{
|
|
// local variables
|
|
|
|
if( ( NULL == fp ) || ( NULL == pColumns ) )
|
|
{
|
|
return ;
|
|
}
|
|
|
|
//
|
|
// Display the information in the format specified
|
|
//
|
|
switch( dwFlags & SR_FORMAT_MASK )
|
|
{
|
|
case SR_FORMAT_TABLE:
|
|
{
|
|
// show the data in table format
|
|
__ShowAsTable( fp, dwColumns, pColumns, dwFlags, arrData );
|
|
|
|
// switch case completed
|
|
break;
|
|
}
|
|
|
|
case SR_FORMAT_LIST:
|
|
{
|
|
// show the data in table format
|
|
__ShowAsList( fp, dwColumns, pColumns, dwFlags, arrData );
|
|
|
|
// switch case completed
|
|
break;
|
|
}
|
|
|
|
case SR_FORMAT_CSV:
|
|
{
|
|
// show the data in table format
|
|
__ShowAsCSV( fp, dwColumns, pColumns, dwFlags, arrData );
|
|
|
|
// switch case completed
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
// invalid format requested by the user
|
|
break;
|
|
}
|
|
}
|
|
|
|
// flush the memory onto the file buffer
|
|
fflush( fp );
|
|
}
|