|
/***************************************************************************
|
|
$RCSfile$
|
|
-------------------
|
|
cvs : $Id: xmldb.c 1020 2006-03-26 17:16:05Z aquamaniac $
|
|
begin : Thu Oct 30 2003
|
|
copyright : (C) 2003 by Martin Preuss
|
|
email : martin@libchipcard.de
|
|
|
|
***************************************************************************
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Lesser General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2.1 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library is distributed in the hope that it 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 this library; if not, write to the Free Software *
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
|
|
* MA 02111-1307 USA *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include "xmldb_p.h"
|
|
#include <gwenhywfar/text.h>
|
|
#include <gwenhywfar/debug.h>
|
|
#include <gwenhywfar/stringlist.h>
|
|
#include <gwenhywfar/xml.h>
|
|
#include <gwenhywfar/dbio_be.h>
|
|
#include <gwenhywfar/syncio_file.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int GWEN_DBIO__XmlDb_ImportGroup(GWEN_DBIO *dbio,
|
|
uint32_t flags,
|
|
GWEN_DB_NODE *data,
|
|
GWEN_DB_NODE *cfg,
|
|
GWEN_XMLNODE *node) {
|
|
GWEN_XMLNODE *n;
|
|
|
|
assert(node);
|
|
n=GWEN_XMLNode_GetFirstTag(node);
|
|
while(n) {
|
|
const char *tname;
|
|
const char *s;
|
|
|
|
tname=GWEN_XMLNode_GetData(n);
|
|
assert(tname && *tname);
|
|
s=GWEN_XMLNode_GetProperty(n, "type", 0);
|
|
if (s) {
|
|
if (strcasecmp(s, "group")==0) {
|
|
GWEN_DB_NODE *db;
|
|
int rv;
|
|
|
|
db=GWEN_DB_GetGroup(data, flags, tname);
|
|
if (db==0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here");
|
|
return GWEN_ERROR_INVALID;
|
|
}
|
|
rv=GWEN_DBIO__XmlDb_ImportGroup(dbio, flags, db, cfg, n);
|
|
if (rv) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
return rv;
|
|
}
|
|
}
|
|
else if (strcasecmp(s, "var")==0) {
|
|
int rv;
|
|
|
|
rv=GWEN_DBIO__XmlDb_ImportVar(dbio, flags, data, cfg, n);
|
|
if (rv) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
return rv;
|
|
}
|
|
}
|
|
else {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Unexpected type \"%s\"", s);
|
|
return GWEN_ERROR_INVALID;
|
|
}
|
|
}
|
|
|
|
n=GWEN_XMLNode_GetNextTag(n);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
void GWEN_DBIO__XmlDb_ReadDataTags(GWEN_XMLNODE *node, GWEN_BUFFER *buf) {
|
|
GWEN_XMLNODE *ndata;
|
|
GWEN_BUFFER *tbuf;
|
|
int rv;
|
|
|
|
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
|
|
ndata=GWEN_XMLNode_GetFirstData(node);
|
|
while(ndata) {
|
|
const char *s;
|
|
|
|
s=GWEN_XMLNode_GetData(ndata);
|
|
if (s) {
|
|
if (GWEN_Buffer_GetUsedBytes(tbuf))
|
|
GWEN_Buffer_AppendByte(tbuf, ' ');
|
|
GWEN_Buffer_AppendString(tbuf, s);
|
|
}
|
|
ndata=GWEN_XMLNode_GetNextData(node);
|
|
}
|
|
rv=GWEN_Text_UnescapeXmlToBuffer(GWEN_Buffer_GetStart(tbuf), buf);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
}
|
|
GWEN_Buffer_free(tbuf);
|
|
}
|
|
|
|
|
|
|
|
int GWEN_DBIO__XmlDb_ImportVar(GWEN_DBIO *dbio,
|
|
uint32_t flags,
|
|
GWEN_DB_NODE *data,
|
|
GWEN_DB_NODE *cfg,
|
|
GWEN_XMLNODE *node) {
|
|
GWEN_XMLNODE *n;
|
|
const char *vname;
|
|
GWEN_BUFFER *tbuf;
|
|
|
|
assert(node);
|
|
vname=GWEN_XMLNode_GetData(node);
|
|
assert(vname && *vname);
|
|
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
|
|
n=GWEN_XMLNode_FindFirstTag(node, "value", 0, 0);
|
|
while(n) {
|
|
const char *s;
|
|
const char *d;
|
|
int rv;
|
|
|
|
GWEN_DBIO__XmlDb_ReadDataTags(n, tbuf);
|
|
d=GWEN_Buffer_GetStart(tbuf);
|
|
if (*d) {
|
|
s=GWEN_XMLNode_GetData(n);
|
|
assert(s && *s);
|
|
s=GWEN_XMLNode_GetProperty(n, "type", 0);
|
|
if (s) {
|
|
if (strcasecmp(s, "char")==0) {
|
|
rv=GWEN_DB_SetCharValue(data, flags, vname, d);
|
|
if (rv) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
}
|
|
else if (strcasecmp(s, "int")==0) {
|
|
int val;
|
|
|
|
if (1!=sscanf(d, "%i", &val)) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Non-integer value [%s]", d);
|
|
GWEN_Buffer_free(tbuf);
|
|
return GWEN_ERROR_INVALID;
|
|
}
|
|
rv=GWEN_DB_SetIntValue(data, flags, vname, val);
|
|
if (rv) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
}
|
|
else if (strcasecmp(s, "bin")==0) {
|
|
GWEN_BUFFER *xbuf;
|
|
|
|
xbuf=GWEN_Buffer_new(0, 256, 0, 1);
|
|
rv=GWEN_Text_FromHexBuffer(d, xbuf);
|
|
if (rv) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(xbuf);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
|
|
rv=GWEN_DB_SetBinValue(data, flags, vname,
|
|
GWEN_Buffer_GetStart(xbuf),
|
|
GWEN_Buffer_GetUsedBytes(xbuf));
|
|
GWEN_Buffer_free(xbuf);
|
|
if (rv) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
}
|
|
else {
|
|
DBG_ERROR(GWEN_LOGDOMAIN,
|
|
"Unhandled value type \"%s\"", s);
|
|
GWEN_Buffer_free(tbuf);
|
|
return GWEN_ERROR_INVALID;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Empty value");
|
|
}
|
|
GWEN_Buffer_Reset(tbuf);
|
|
|
|
n=GWEN_XMLNode_FindNextTag(n, "value", 0, 0);
|
|
}
|
|
GWEN_Buffer_free(tbuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
int GWEN_DBIO_XmlDb_Import(GWEN_DBIO *dbio,
|
|
GWEN_SYNCIO *sio,
|
|
GWEN_DB_NODE *data,
|
|
GWEN_DB_NODE *cfg,
|
|
uint32_t flags) {
|
|
int rv;
|
|
GWEN_XMLNODE *root;
|
|
GWEN_XMLNODE *n;
|
|
const char *rootName=0;
|
|
GWEN_XML_CONTEXT *ctx;
|
|
|
|
assert(data);
|
|
|
|
if (cfg)
|
|
rootName=GWEN_DB_GetCharValue(cfg, "rootElement", 0, 0);
|
|
|
|
root=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, "root");
|
|
ctx=GWEN_XmlCtxStore_new(root,
|
|
GWEN_XML_FLAGS_DEFAULT |
|
|
GWEN_XML_FLAGS_HANDLE_HEADERS);
|
|
rv=GWEN_XML_ReadFromIo(ctx, sio);
|
|
if (rv) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_XmlCtx_free(ctx);
|
|
GWEN_XMLNode_free(root);
|
|
return rv;
|
|
}
|
|
GWEN_XmlCtx_free(ctx);
|
|
|
|
if (rootName) {
|
|
n=GWEN_XMLNode_FindFirstTag(root, rootName, 0, 0);
|
|
if (!n) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN,
|
|
"Root node in XML file is not \"%s\"", rootName);
|
|
GWEN_XMLNode_free(root);
|
|
return GWEN_ERROR_BAD_DATA;
|
|
}
|
|
}
|
|
else {
|
|
n=GWEN_XMLNode_GetFirstTag(root);
|
|
if (!n) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN,
|
|
"No root node in XML file");
|
|
GWEN_XMLNode_free(root);
|
|
return GWEN_ERROR_BAD_DATA;
|
|
}
|
|
}
|
|
|
|
rv=GWEN_DBIO__XmlDb_ImportGroup(dbio, flags, data, cfg, n);
|
|
if (rv) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_XMLNode_free(root);
|
|
return rv;
|
|
}
|
|
|
|
GWEN_XMLNode_free(root);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_DBIO_XmlDb__ExportGroup(GWEN_DBIO *dbio,
|
|
GWEN_DB_NODE *data,
|
|
GWEN_XMLNODE *node,
|
|
const char *newName) {
|
|
const char *s;
|
|
GWEN_XMLNODE *n;
|
|
GWEN_DB_NODE *dbT;
|
|
int rv;
|
|
|
|
if (newName)
|
|
s=newName;
|
|
else
|
|
s=GWEN_DB_GroupName(data);
|
|
assert(s && *s);
|
|
|
|
n=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, s);
|
|
GWEN_XMLNode_SetProperty(n, "type", "group");
|
|
GWEN_XMLNode_AddChild(node, n);
|
|
|
|
/* store variables */
|
|
dbT=GWEN_DB_GetFirstVar(data);
|
|
while(dbT) {
|
|
if (!(GWEN_DB_GetNodeFlags(dbT) & GWEN_DB_NODE_FLAGS_VOLATILE)) {
|
|
rv=GWEN_DBIO_XmlDb__ExportVar(dbio, dbT, n);
|
|
if (rv) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
return rv;
|
|
}
|
|
}
|
|
dbT=GWEN_DB_GetNextVar(dbT);
|
|
}
|
|
|
|
/* store groups */
|
|
dbT=GWEN_DB_GetFirstGroup(data);
|
|
while(dbT) {
|
|
if (!(GWEN_DB_GetNodeFlags(dbT) & GWEN_DB_NODE_FLAGS_VOLATILE)) {
|
|
rv=GWEN_DBIO_XmlDb__ExportGroup(dbio, dbT, n, 0);
|
|
if (rv) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
return rv;
|
|
}
|
|
}
|
|
dbT=GWEN_DB_GetNextGroup(dbT);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_DBIO_XmlDb__ExportVar(GWEN_DBIO *dbio,
|
|
GWEN_DB_NODE *data,
|
|
GWEN_XMLNODE *node) {
|
|
const char *s;
|
|
GWEN_XMLNODE *n;
|
|
GWEN_DB_NODE *dbT;
|
|
int rv;
|
|
|
|
s=GWEN_DB_VariableName(data);
|
|
assert(s && *s);
|
|
|
|
n=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, s);
|
|
GWEN_XMLNode_SetProperty(n, "type", "var");
|
|
GWEN_XMLNode_AddChild(node, n);
|
|
|
|
/* store variables */
|
|
dbT=GWEN_DB_GetFirstValue(data);
|
|
while(dbT) {
|
|
if (!(GWEN_DB_GetNodeFlags(dbT) & GWEN_DB_NODE_FLAGS_VOLATILE)) {
|
|
GWEN_DB_NODE_TYPE vt;
|
|
GWEN_XMLNODE *vn;
|
|
|
|
vt=GWEN_DB_GetValueType(dbT);
|
|
switch(vt) {
|
|
|
|
case GWEN_DB_NodeType_ValueChar:
|
|
s=GWEN_DB_GetCharValueFromNode(dbT);
|
|
if (s && *s) {
|
|
GWEN_XMLNODE *dn;
|
|
GWEN_BUFFER *tbuf;
|
|
|
|
vn=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, "value");
|
|
GWEN_XMLNode_SetProperty(vn, "type", "char");
|
|
GWEN_XMLNode_AddChild(n, vn);
|
|
|
|
tbuf=GWEN_Buffer_new(0, 64, 0, 1);
|
|
rv=GWEN_Text_EscapeXmlToBuffer(s, tbuf);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
dn=GWEN_XMLNode_new(GWEN_XMLNodeTypeData, GWEN_Buffer_GetStart(tbuf));
|
|
GWEN_Buffer_free(tbuf);
|
|
GWEN_XMLNode_AddChild(vn, dn);
|
|
}
|
|
break;
|
|
|
|
case GWEN_DB_NodeType_ValueInt: {
|
|
char nbuf[32];
|
|
GWEN_XMLNODE *dn;
|
|
|
|
snprintf(nbuf, sizeof(nbuf), "%i", GWEN_DB_GetIntValueFromNode(dbT));
|
|
|
|
vn=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, "value");
|
|
GWEN_XMLNode_SetProperty(vn, "type", "int");
|
|
GWEN_XMLNode_AddChild(n, vn);
|
|
|
|
dn=GWEN_XMLNode_new(GWEN_XMLNodeTypeData, nbuf);
|
|
GWEN_XMLNode_AddChild(vn, dn);
|
|
|
|
break;
|
|
}
|
|
|
|
case GWEN_DB_NodeType_ValueBin: {
|
|
const void *vp;
|
|
unsigned int vsize;
|
|
|
|
vp=GWEN_DB_GetBinValueFromNode(dbT, &vsize);
|
|
if (vp && vsize) {
|
|
GWEN_BUFFER *bbuf;
|
|
GWEN_XMLNODE *dn;
|
|
|
|
bbuf=GWEN_Buffer_new(0, vsize*2, 0, 1);
|
|
rv=GWEN_Text_ToHexBuffer((const char*)vp,
|
|
vsize,
|
|
bbuf,
|
|
0, 0, 0);
|
|
if (rv) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(bbuf);
|
|
return rv;
|
|
}
|
|
|
|
vn=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, "value");
|
|
GWEN_XMLNode_SetProperty(vn, "type", "bin");
|
|
GWEN_XMLNode_AddChild(n, vn);
|
|
|
|
dn=GWEN_XMLNode_new(GWEN_XMLNodeTypeData,
|
|
GWEN_Buffer_GetStart(bbuf));
|
|
GWEN_Buffer_free(bbuf);
|
|
GWEN_XMLNode_AddChild(vn, dn);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case GWEN_DB_NodeType_ValuePtr:
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Not storing pointer value");
|
|
break;
|
|
|
|
default:
|
|
DBG_ERROR(GWEN_LOGDOMAIN,
|
|
"Unsupported variable type %d",
|
|
vt);
|
|
return GWEN_ERROR_INVALID;
|
|
}
|
|
} /* if non-volatile */
|
|
|
|
dbT=GWEN_DB_GetNextValue(dbT);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_DBIO_XmlDb_Export(GWEN_DBIO *dbio,
|
|
GWEN_SYNCIO *sio,
|
|
GWEN_DB_NODE *data,
|
|
GWEN_DB_NODE *cfg,
|
|
uint32_t flags) {
|
|
GWEN_XMLNODE *root;
|
|
GWEN_XMLNODE *nh;
|
|
int rv;
|
|
const char *rootName=0;
|
|
GWEN_XML_CONTEXT *ctx;
|
|
|
|
if (cfg)
|
|
rootName=GWEN_DB_GetCharValue(cfg, "rootElement", 0, 0);
|
|
|
|
root=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, "root");
|
|
nh=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, "?xml");
|
|
GWEN_XMLNode_SetProperty(nh, "version", "1.0");
|
|
GWEN_XMLNode_SetProperty(nh, "encoding", "utf8");
|
|
GWEN_XMLNode_AddHeader(root, nh);
|
|
|
|
rv=GWEN_DBIO_XmlDb__ExportGroup(dbio, data, root, rootName);
|
|
if (rv) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_XMLNode_free(root);
|
|
return rv;
|
|
}
|
|
|
|
|
|
ctx=GWEN_XmlCtxStore_new(root,
|
|
GWEN_XML_FLAGS_DEFAULT |
|
|
GWEN_XML_FLAGS_SIMPLE |
|
|
GWEN_XML_FLAGS_HANDLE_HEADERS);
|
|
|
|
rv=GWEN_XMLNode_WriteToStream(root, ctx, sio);
|
|
if (rv) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_XmlCtx_free(ctx);
|
|
GWEN_XMLNode_free(root);
|
|
return rv;
|
|
}
|
|
GWEN_XmlCtx_free(ctx);
|
|
GWEN_XMLNode_free(root);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
GWEN_DBIO_CHECKFILE_RESULT GWEN_DBIO_XmlDb_CheckFile(GWEN_DBIO *dbio,
|
|
const char *fname){
|
|
GWEN_SYNCIO *sio;
|
|
int rv;
|
|
uint8_t tbuf[256];
|
|
|
|
assert(dbio);
|
|
assert(fname);
|
|
|
|
sio=GWEN_SyncIo_File_new(fname, GWEN_SyncIo_File_CreationMode_OpenExisting);
|
|
GWEN_SyncIo_AddFlags(sio, GWEN_SYNCIO_FILE_FLAGS_READ);
|
|
rv=GWEN_SyncIo_Connect(sio);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_SyncIo_free(sio);
|
|
return rv;
|
|
}
|
|
|
|
|
|
rv=GWEN_SyncIo_Read(sio, tbuf, sizeof(tbuf)-1);
|
|
if (rv<1) {
|
|
DBG_INFO(GWEN_LOGDOMAIN,
|
|
"File \"%s\" is not supported by this plugin",
|
|
fname);
|
|
GWEN_SyncIo_Disconnect(sio);
|
|
GWEN_SyncIo_free(sio);
|
|
return GWEN_DBIO_CheckFileResultNotOk;
|
|
}
|
|
tbuf[rv-1]=0;
|
|
if (-1!=GWEN_Text_ComparePattern((const char*) tbuf, "*<?xml*", 0)) {
|
|
/* match */
|
|
DBG_INFO(GWEN_LOGDOMAIN,
|
|
"File \"%s\" is supported by this plugin",
|
|
fname);
|
|
GWEN_SyncIo_Disconnect(sio);
|
|
GWEN_SyncIo_free(sio);
|
|
/* don't be too sure about this, we *may* support the file,
|
|
* so we dont say we don't support this file */
|
|
return GWEN_DBIO_CheckFileResultUnknown;
|
|
}
|
|
GWEN_SyncIo_Disconnect(sio);
|
|
GWEN_SyncIo_free(sio);
|
|
return GWEN_DBIO_CheckFileResultNotOk;
|
|
}
|
|
|
|
|
|
|
|
GWEN_DBIO *GWEN_DBIO_XmlDb_Factory(GWEN_PLUGIN *pl) {
|
|
GWEN_DBIO *dbio;
|
|
|
|
dbio=GWEN_DBIO_new("XmlDb", "Imports and exports XML data");
|
|
GWEN_DBIO_SetImportFn(dbio, GWEN_DBIO_XmlDb_Import);
|
|
GWEN_DBIO_SetExportFn(dbio, GWEN_DBIO_XmlDb_Export);
|
|
GWEN_DBIO_SetCheckFileFn(dbio, GWEN_DBIO_XmlDb_CheckFile);
|
|
return dbio;
|
|
}
|
|
|
|
|
|
|
|
GWEN_PLUGIN *dbio_xmldb_factory(GWEN_PLUGIN_MANAGER *pm,
|
|
const char *modName,
|
|
const char *fileName) {
|
|
GWEN_PLUGIN *pl;
|
|
|
|
pl=GWEN_DBIO_Plugin_new(pm, modName, fileName);
|
|
assert(pl);
|
|
|
|
GWEN_DBIO_Plugin_SetFactoryFn(pl, GWEN_DBIO_XmlDb_Factory);
|
|
|
|
return pl;
|
|
|
|
}
|
|
|
|
|
|
|