557 lines
15 KiB
C
557 lines
15 KiB
C
|
/****************************** Module Header ******************************\
|
||
|
* Module Name: sofile.c
|
||
|
*
|
||
|
* Copyright (c) 1985-96, Microsoft Corporation
|
||
|
*
|
||
|
* 04/09/96 GerardoB Created
|
||
|
\***************************************************************************/
|
||
|
#include "structo.h"
|
||
|
|
||
|
/*********************************************************************
|
||
|
* soWriteOutputFileHeader
|
||
|
*
|
||
|
\***************************************************************************/
|
||
|
BOOL soWriteOutputFileHeader (PWORKINGFILES pwf)
|
||
|
{
|
||
|
char ** ppszHeader;
|
||
|
|
||
|
/*
|
||
|
* If building list only, done
|
||
|
*/
|
||
|
if (pwf->dwOptions & SOWF_LISTONLY) {
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
if ( !soWriteFile(pwf->hfileOutput, "/*********************************************************************\\\r\n")
|
||
|
|| !soWriteFile(pwf->hfileOutput, "* File: %s\r\n", pwf->pszOutputFile)
|
||
|
|| !soWriteFile(pwf->hfileOutput, "* Generated by StructO on %s at %s\r\n", __DATE__, __TIME__)
|
||
|
|| !soWriteFile(pwf->hfileOutput, "\\*********************************************************************/\r\n\r\n")) {
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (pwf->dwOptions & SOWF_INLCLUDEPRECOMPH) {
|
||
|
if (!soWriteFile(pwf->hfileOutput, gszPrecomph)) {
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* structure definitions for generated tables
|
||
|
*/
|
||
|
ppszHeader = gpszHeader;
|
||
|
while (*ppszHeader != NULL) {
|
||
|
if (!soWriteFile(pwf->hfileOutput, *ppszHeader)
|
||
|
|| !soWriteFile(pwf->hfileOutput, "\r\n")) {
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
ppszHeader++;
|
||
|
}
|
||
|
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
/*********************************************************************
|
||
|
* soUnmapFile
|
||
|
*
|
||
|
\***************************************************************************/
|
||
|
void soUnmapFile (PFILEMAP pfm)
|
||
|
{
|
||
|
if (pfm->pmapStart != NULL) {
|
||
|
UnmapViewOfFile(pfm->pmap);
|
||
|
pfm->pmapStart = NULL;
|
||
|
pfm->pmap = NULL;
|
||
|
pfm->pmapEnd = NULL;
|
||
|
}
|
||
|
|
||
|
if (pfm->hmap != NULL) {
|
||
|
CloseHandle(pfm->hmap);
|
||
|
pfm->hmap = NULL;
|
||
|
}
|
||
|
|
||
|
if (pfm->hfile != INVALID_HANDLE_VALUE) {
|
||
|
CloseHandle(pfm->hfile);
|
||
|
pfm->hfile = INVALID_HANDLE_VALUE;
|
||
|
}
|
||
|
}
|
||
|
/*********************************************************************
|
||
|
* soMapFile
|
||
|
*
|
||
|
\***************************************************************************/
|
||
|
BOOL soMapFile (char * pszFile, PFILEMAP pfm)
|
||
|
{
|
||
|
DWORD dwFileSize;
|
||
|
|
||
|
pfm->hfile = CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ, NULL,
|
||
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, NULL);
|
||
|
if (pfm->hfile == INVALID_HANDLE_VALUE) {
|
||
|
soLogMsg(SOLM_APIERROR, "CreateFile");
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
|
||
|
dwFileSize = GetFileSize(pfm->hfile, NULL);
|
||
|
if (dwFileSize == 0xFFFFFFFF) {
|
||
|
soLogMsg(SOLM_APIERROR, "GetFileSize");
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
|
||
|
pfm->hmap = CreateFileMapping(pfm->hfile, NULL, PAGE_READONLY, 0, 0, NULL);
|
||
|
if (pfm->hmap == NULL) {
|
||
|
soLogMsg(SOLM_APIERROR, "CreateFileMapping");
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
|
||
|
pfm->pmapStart = MapViewOfFile(pfm->hmap, FILE_MAP_READ, 0, 0, 0);
|
||
|
if (pfm->pmapStart == NULL) {
|
||
|
soLogMsg(SOLM_APIERROR, "MapViewOfFile");
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
pfm->pmap = pfm->pmapStart;
|
||
|
pfm->pmapEnd = pfm->pmapStart + dwFileSize;
|
||
|
|
||
|
return TRUE;
|
||
|
|
||
|
CleanupAndFail:
|
||
|
soLogMsg(SOLM_ERROR, "soMapFile failed. File: '%s'", pszFile);
|
||
|
soUnmapFile (pfm);
|
||
|
return FALSE;
|
||
|
}
|
||
|
/*********************************************************************
|
||
|
* soBuildStructsList
|
||
|
*
|
||
|
\***************************************************************************/
|
||
|
BOOL soBuildStructsList (PWORKINGFILES pwf)
|
||
|
{
|
||
|
static char gszEOL [] = "\r\n";
|
||
|
|
||
|
char * pmap, * pStruct;
|
||
|
FILEMAP fm;
|
||
|
PSTRUCTLIST psl;
|
||
|
PVOID pTemp;
|
||
|
UINT uAlloc, uCount, uSize;
|
||
|
|
||
|
|
||
|
soLogMsg (SOLM_NOEOL, "Building structs list from %s ...", pwf->pszStructsFile);
|
||
|
|
||
|
if (!soMapFile (pwf->pszStructsFile, &fm)) {
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Let's guess a number of structures
|
||
|
*/
|
||
|
#define SO_LISTSIZE 20
|
||
|
uAlloc = SO_LISTSIZE;
|
||
|
|
||
|
/*
|
||
|
* Allocate list
|
||
|
*/
|
||
|
pwf->psl = (PSTRUCTLIST) LocalAlloc(LPTR, sizeof(STRUCTLIST) * (uAlloc + 1));
|
||
|
if (pwf->psl == NULL) {
|
||
|
soLogMsg(SOLM_APIERROR, "LocalAlloc");
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Load structure names
|
||
|
*/
|
||
|
pmap = fm.pmapStart;
|
||
|
psl = pwf->psl;
|
||
|
uCount = 0;
|
||
|
while (pmap < fm.pmapEnd) {
|
||
|
/*
|
||
|
* Name should start at first column
|
||
|
*/
|
||
|
if (!soIsIdentifierChar(*pmap)) {
|
||
|
/*
|
||
|
* Skip this line
|
||
|
*/
|
||
|
pmap = soFindTag(pmap, fm.pmapEnd, gszEOL);
|
||
|
if (pmap == NULL) {
|
||
|
break;
|
||
|
}
|
||
|
pmap += sizeof(gszEOL) - 1;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Find the name
|
||
|
*/
|
||
|
pStruct = soGetIdentifier (pmap, fm.pmapEnd, &uSize);
|
||
|
if (pStruct == NULL) {
|
||
|
soLogMsg(SOLM_ERROR, "soGetIdentifier failed.");
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Grow the list if needed
|
||
|
*/
|
||
|
if (uCount >= uAlloc) {
|
||
|
soLogMsg (SOLM_APPEND, ".");
|
||
|
uAlloc += SO_LISTSIZE;
|
||
|
pTemp = LocalReAlloc(pwf->psl, sizeof(STRUCTLIST) * (uAlloc + 1), LMEM_MOVEABLE | LMEM_ZEROINIT);
|
||
|
if (pTemp == NULL) {
|
||
|
soLogMsg(SOLM_APIERROR, "LocalReAlloc");
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
pwf->psl = (PSTRUCTLIST)pTemp;
|
||
|
psl = pwf->psl + uCount;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Copy it
|
||
|
*/
|
||
|
psl->uSize = uSize;
|
||
|
psl->pszName = soCopyTagName (pStruct, uSize);
|
||
|
if (psl->pszName == NULL) {
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
|
||
|
psl++;
|
||
|
uCount++;
|
||
|
pmap = pStruct + uSize;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Make sure it found at least one struct
|
||
|
*/
|
||
|
if (uCount == 0) {
|
||
|
soLogMsg(SOLM_ERROR, "Failed to get structure name");
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Let's save some memory
|
||
|
*/
|
||
|
if (uCount < uAlloc) {
|
||
|
pTemp = LocalReAlloc(pwf->psl, sizeof(STRUCTLIST) * (uCount + 1), LMEM_MOVEABLE | LMEM_ZEROINIT);
|
||
|
if (pTemp == NULL) {
|
||
|
soLogMsg(SOLM_APIERROR, "LocalReAlloc");
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
pwf->psl = (PSTRUCTLIST)pTemp;
|
||
|
}
|
||
|
|
||
|
soUnmapFile (&fm);
|
||
|
soLogMsg (SOLM_NOLABEL, ".");
|
||
|
return TRUE;
|
||
|
|
||
|
CleanupAndFail:
|
||
|
soLogMsg(SOLM_ERROR, "soBuildStructsList failed. File: '%s'", pwf->pszStructsFile);
|
||
|
soUnmapFile (&fm);
|
||
|
/*
|
||
|
* The process is going away so never mind the heap
|
||
|
*/
|
||
|
return FALSE;
|
||
|
}
|
||
|
/*********************************************************************
|
||
|
* soIncludeInputFile
|
||
|
*
|
||
|
* Add #include <pszInputFile Name>.<pszIncInputFileExt> to output file
|
||
|
\***************************************************************************/
|
||
|
BOOL soIncludeInputFile (PWORKINGFILES pwf)
|
||
|
{
|
||
|
BOOL fRet;
|
||
|
char * pszIncFile, * pDot;
|
||
|
UINT uInputFileNameSize;
|
||
|
|
||
|
/*
|
||
|
* If building list only, done
|
||
|
*/
|
||
|
if (pwf->dwOptions & SOWF_LISTONLY) {
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Allocate a buffer to build the name
|
||
|
*/
|
||
|
uInputFileNameSize = lstrlen(pwf->pszInputFile);
|
||
|
pszIncFile = (char *) LocalAlloc(LPTR,
|
||
|
uInputFileNameSize + lstrlen(pwf->pszIncInputFileExt) + 2);
|
||
|
if (pszIncFile == NULL) {
|
||
|
soLogMsg(SOLM_APIERROR, "LocalAlloc");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Copy file name
|
||
|
*/
|
||
|
pDot = soFindChar (pwf->pszInputFile, pwf->pszInputFile + uInputFileNameSize, '.');
|
||
|
if (pDot == NULL) {
|
||
|
strcpy(pszIncFile, pwf->pszInputFile);
|
||
|
strcat(pszIncFile, ".");
|
||
|
} else {
|
||
|
strncpy(pszIncFile, pwf->pszInputFile, (UINT)(pDot - pwf->pszInputFile + 1));
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Copy extension and write it to output file
|
||
|
*/
|
||
|
strcat(pszIncFile, pwf->pszIncInputFileExt);
|
||
|
fRet = soWriteFile(pwf->hfileOutput, gszIncInput, pszIncFile);
|
||
|
|
||
|
LocalFree(pszIncFile);
|
||
|
return fRet;
|
||
|
}
|
||
|
/*********************************************************************
|
||
|
* soOpenWorkingFiles
|
||
|
*
|
||
|
\***************************************************************************/
|
||
|
BOOL soOpenWorkingFiles (PWORKINGFILES pwf)
|
||
|
{
|
||
|
char szTempPath [MAX_PATH];
|
||
|
char szTempFile [MAX_PATH];
|
||
|
DWORD dwFileSize;
|
||
|
|
||
|
/*
|
||
|
* Load the structures list if provided and not built already
|
||
|
*/
|
||
|
if ((pwf->pszStructsFile != NULL) && (pwf->psl == NULL)) {
|
||
|
if (!soBuildStructsList(pwf)) {
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Map input file
|
||
|
*/
|
||
|
if (!soMapFile (pwf->pszInputFile, (PFILEMAP) &(pwf->hfileInput))) {
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Open output file if not open already
|
||
|
*/
|
||
|
if (pwf->hfileOutput == INVALID_HANDLE_VALUE) {
|
||
|
pwf->hfileOutput = CreateFile(pwf->pszOutputFile, GENERIC_WRITE, 0, NULL,
|
||
|
(pwf->dwOptions & SOWF_APPENDOUTPUT ? OPEN_EXISTING : CREATE_ALWAYS),
|
||
|
FILE_ATTRIBUTE_NORMAL, NULL);
|
||
|
if (pwf->hfileOutput == INVALID_HANDLE_VALUE) {
|
||
|
soLogMsg(SOLM_APIERROR, "CreateFile");
|
||
|
soLogMsg(SOLM_ERROR, "Failed to open output file: %s", pwf->pszOutputFile);
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
|
||
|
if (pwf->dwOptions & SOWF_APPENDOUTPUT) {
|
||
|
if (0xFFFFFFFF == SetFilePointer (pwf->hfileOutput, 0, 0, FILE_END)) {
|
||
|
soLogMsg(SOLM_APIERROR, "SetFilePointer");
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
} else {
|
||
|
if (!soWriteOutputFileHeader(pwf)) {
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* #include input file if requested
|
||
|
*/
|
||
|
if (pwf->dwOptions & SOWF_INCLUDEINPUTFILE) {
|
||
|
if (!soIncludeInputFile(pwf)) {
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Create temp file if not created already
|
||
|
*/
|
||
|
if (pwf->hfileTemp == INVALID_HANDLE_VALUE) {
|
||
|
if (!GetTempPath(sizeof(szTempPath) - 1, szTempPath)) {
|
||
|
soLogMsg(SOLM_APIERROR, "GetTempPath");
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
|
||
|
if (!GetTempFileName(szTempPath, "sot", 0, szTempFile)) {
|
||
|
soLogMsg(SOLM_APIERROR, "GetTempFileName");
|
||
|
soLogMsg(SOLM_ERROR, "Failed to get temp file name. szTempPath: %s.", szTempPath);
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
|
||
|
pwf->hfileTemp = CreateFile(szTempFile, GENERIC_WRITE | GENERIC_READ, 0, NULL,
|
||
|
CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, NULL);
|
||
|
if (pwf->hfileTemp == INVALID_HANDLE_VALUE) {
|
||
|
soLogMsg(SOLM_APIERROR, "CreateFile");
|
||
|
soLogMsg(SOLM_ERROR, "Failed to create temp file: '%s'", szTempFile);
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
|
||
|
if (!soWriteFile(pwf->hfileTemp, "%s", gszTableDef)) {
|
||
|
goto CleanupAndFail;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
return TRUE;
|
||
|
|
||
|
CleanupAndFail:
|
||
|
soLogMsg(SOLM_ERROR, "soOpenWorkingFiles Failed");
|
||
|
soCloseWorkingFiles (pwf, SOCWF_CLEANUP);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
/*********************************************************************
|
||
|
* soCloseWorkingFiles
|
||
|
*
|
||
|
\***************************************************************************/
|
||
|
BOOL soCloseWorkingFiles (PWORKINGFILES pwf, DWORD dwFlags)
|
||
|
{
|
||
|
|
||
|
if (dwFlags & SOCWF_CLEANUP) {
|
||
|
if (pwf->hfileTemp != INVALID_HANDLE_VALUE) {
|
||
|
CloseHandle(pwf->hfileTemp);
|
||
|
pwf->hfileTemp = INVALID_HANDLE_VALUE;
|
||
|
}
|
||
|
if (pwf->hfileOutput != INVALID_HANDLE_VALUE) {
|
||
|
CloseHandle(pwf->hfileOutput);
|
||
|
pwf->hfileOutput = INVALID_HANDLE_VALUE;
|
||
|
}
|
||
|
if (pwf->psl != NULL) {
|
||
|
// Never mind cleanning the heap. The process is going away.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
soUnmapFile((PFILEMAP) &(pwf->hfileInput));
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
/*********************************************************************
|
||
|
* soWriteFile
|
||
|
*
|
||
|
\***************************************************************************/
|
||
|
BOOL __cdecl soWriteFile(HANDLE hfile, char *pszfmt, ...)
|
||
|
{
|
||
|
static char gszbuff [1024+1];
|
||
|
|
||
|
BOOL fRet = TRUE;
|
||
|
va_list va;
|
||
|
DWORD dwWritten;
|
||
|
|
||
|
va_start(va, pszfmt);
|
||
|
vsprintf(gszbuff, pszfmt, va);
|
||
|
|
||
|
if (!WriteFile(hfile, gszbuff, strlen(gszbuff), &dwWritten, NULL)) {
|
||
|
soLogMsg(SOLM_APIERROR, "WriteFile");
|
||
|
soLogMsg(SOLM_ERROR, "buffer not written: %s.", gszbuff);
|
||
|
fRet = FALSE;
|
||
|
}
|
||
|
|
||
|
va_end(va);
|
||
|
return fRet;
|
||
|
}
|
||
|
/*********************************************************************
|
||
|
* soCopyStructuresTable
|
||
|
*
|
||
|
\***************************************************************************/
|
||
|
BOOL soCopyStructuresTable (PWORKINGFILES pwf)
|
||
|
{
|
||
|
static char szTemp[1024];
|
||
|
|
||
|
char ** ppszTail;
|
||
|
DWORD dwFileSize, dwRead, dwWritten;
|
||
|
PSTRUCTLIST psl;
|
||
|
UINT uLoops;
|
||
|
|
||
|
soLogMsg (SOLM_NOEOL, "Writting structs table ...");
|
||
|
|
||
|
/*
|
||
|
* If there are no structures, bail.
|
||
|
* If there was a struct list, fail
|
||
|
*/
|
||
|
if (pwf->uTablesCount == 0) {
|
||
|
if (pwf->psl != NULL) {
|
||
|
soLogMsg(SOLM_ERROR, "None of the structures in '%s' was found", pwf->pszStructsFile);
|
||
|
return FALSE;
|
||
|
} else {
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If building list only, done
|
||
|
*/
|
||
|
if (pwf->dwOptions & SOWF_LISTONLY) {
|
||
|
soLogMsg (SOLM_DEFAULT, "%d Structures found.", pwf->uTablesCount);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Let them know if we didn't find any of the structures in the list
|
||
|
*/
|
||
|
if (pwf->psl != NULL) {
|
||
|
psl = pwf->psl;
|
||
|
while (psl->uSize != 0) {
|
||
|
if (psl->uCount == 0) {
|
||
|
soLogMsg(SOLM_WARNING, "Structure not found: %s", psl->pszName);
|
||
|
}
|
||
|
psl++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!soWriteFile(pwf->hfileTemp, "%s", gszTableEnd)) {
|
||
|
goto MsgAndFail;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Move to beginning of temp file
|
||
|
*/
|
||
|
dwFileSize = GetFileSize(pwf->hfileTemp, NULL);
|
||
|
if (dwFileSize == 0xFFFFFFFF) {
|
||
|
soLogMsg(SOLM_APIERROR, "GetFileSize");
|
||
|
goto MsgAndFail;
|
||
|
}
|
||
|
|
||
|
if (0xFFFFFFFF == SetFilePointer (pwf->hfileTemp, 0, 0, FILE_BEGIN)) {
|
||
|
soLogMsg(SOLM_APIERROR, "SetFilePointer");
|
||
|
goto MsgAndFail;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Append temp file to output file
|
||
|
*/
|
||
|
uLoops = 0;
|
||
|
while (dwFileSize != 0) {
|
||
|
if (!ReadFile(pwf->hfileTemp, szTemp, sizeof(szTemp), &dwRead, NULL)) {
|
||
|
soLogMsg(SOLM_APIERROR, "ReadFile");
|
||
|
goto MsgAndFail;
|
||
|
}
|
||
|
|
||
|
if (!WriteFile(pwf->hfileOutput, szTemp, dwRead, &dwWritten, NULL)) {
|
||
|
soLogMsg(SOLM_APIERROR, "WriteFile");
|
||
|
goto MsgAndFail;
|
||
|
}
|
||
|
|
||
|
dwFileSize -= dwRead;
|
||
|
if (++uLoops == 50) {
|
||
|
uLoops = 0;
|
||
|
soLogMsg (SOLM_APPEND, ".");
|
||
|
}
|
||
|
}
|
||
|
soLogMsg (SOLM_NOLABEL, ".");
|
||
|
|
||
|
soLogMsg (SOLM_DEFAULT, "%d Tables generated.", pwf->uTablesCount);
|
||
|
|
||
|
/*
|
||
|
* Write file tail (code)
|
||
|
*/
|
||
|
ppszTail = gpszTail;
|
||
|
while (*ppszTail != NULL) {
|
||
|
if (!soWriteFile(pwf->hfileOutput, *ppszTail)
|
||
|
|| !soWriteFile(pwf->hfileOutput, "\r\n")) {
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
ppszTail++;
|
||
|
}
|
||
|
|
||
|
|
||
|
return TRUE;
|
||
|
|
||
|
MsgAndFail:
|
||
|
soLogMsg(SOLM_ERROR, "soCopyStructuresTable failed.");
|
||
|
return FALSE;
|
||
|
}
|