6ba868cb2f
X-SVN-Rev: 10008
604 lines
17 KiB
C
604 lines
17 KiB
C
/*
|
|
*******************************************************************************
|
|
*
|
|
* Copyright (C) 2002, International Business Machines
|
|
* Corporation and others. All Rights Reserved.
|
|
*
|
|
*******************************************************************************
|
|
*
|
|
* File wrtxml.c
|
|
*
|
|
* Modification History:
|
|
*
|
|
* Date Name Description
|
|
* 10/01/02 Ram Creation.
|
|
*******************************************************************************
|
|
*/
|
|
|
|
#include "reslist.h"
|
|
#include "unewdata.h"
|
|
#include "unicode/ures.h"
|
|
#include "errmsg.h"
|
|
#include "filestrm.h"
|
|
#include "cstring.h"
|
|
#include "unicode/ucnv.h"
|
|
#include "genrb.h"
|
|
#include "rle.h"
|
|
#include "ucol_tok.h"
|
|
#include "uhash.h"
|
|
#include "uresimp.h"
|
|
#include "unicode/ustring.h"
|
|
#include "unicode/uchar.h"
|
|
/*
|
|
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<!DOCTYPE resourceBundle
|
|
SYSTEM "http://oss.software.ibm.com/icu/dtd/resourceBundle.dtd">
|
|
<resourceBundle name="eo">
|
|
<table>
|
|
<int key="a" val="2"/>
|
|
<str key="s" val="Vladimir"/>
|
|
<str key="s2" val="Markus"/>
|
|
<int key="i" val="0x22"/>
|
|
<str key="emptyString" val=""/>
|
|
<str key="anotherEmptyString"/>
|
|
<intVector key="iv" val="20 21 -1 0x7f"/>
|
|
<intVector key="emptyIntegerVector"/>
|
|
<array key="array">
|
|
<int val="20"/>
|
|
<str val="Andy"/>
|
|
<str val="Andy2"/>
|
|
<bin val="fe ff 0a b5"/>
|
|
<intVector val="20 21 -1 0x7f"/>
|
|
<importBin filename="/other.jpeg"/>
|
|
<str/><str val=""/><!-- two empty strings -->
|
|
<bin/><!-- empty binary -->
|
|
<intVector/><!-- empty integer vector -->
|
|
<array/><!-- empty array -->
|
|
<table/><!-- empty table -->
|
|
</array>
|
|
<array key="emptyArray"/>
|
|
<bin key="b" val="fe ff 0a b5"/>
|
|
<bin key="emptyBinary"/>
|
|
<importBin key="bb" filename="/something.jpeg"/>
|
|
<table key="t">
|
|
<int key="t0" val="-21"/>
|
|
</table>
|
|
<table key="emptyTable"/>
|
|
</table>
|
|
</resourceBundle>
|
|
|
|
*/
|
|
|
|
|
|
static int tabCount = 0;
|
|
|
|
static FileStream* out=NULL;
|
|
static struct SRBRoot* srBundle ;
|
|
static const char* outDir = NULL;
|
|
static const char* enc ="";
|
|
static UConverter* conv = NULL;
|
|
|
|
static void write_tabs(FileStream* os){
|
|
int i=0;
|
|
for(;i<=tabCount;i++){
|
|
T_FileStream_write(os," ",4);
|
|
}
|
|
}
|
|
static const char* xmlHeader = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
|
"<!DOCTYPE resourceBundle "
|
|
"SYSTEM \"http://oss.software.ibm.com/cvs/icu/~checkout~/icuhtml/design/resourceBundle.dtd\">\n";
|
|
static const char* bundleStart = "<resourceBundle name=\"";
|
|
static const char* bundleEnd = "</resourceBundle>\n";
|
|
|
|
|
|
void res_write_xml(struct SResource *res,UErrorCode *status);
|
|
|
|
|
|
static char* convertAndEscape(char** pDest, int32_t destCap, int32_t* destLength,
|
|
const UChar* src, int32_t srcLen, UBool isRule,
|
|
UErrorCode* status){
|
|
int32_t i=0;
|
|
char* dest=NULL;
|
|
char* temp=NULL;
|
|
int32_t len=0;
|
|
int32_t destLen=0;
|
|
|
|
if(status==NULL || U_FAILURE(*status) || pDest==NULL || srcLen==0 || src == NULL){
|
|
return NULL;
|
|
}
|
|
dest =*pDest;
|
|
if(dest==NULL || destCap <=0){
|
|
destCap = srcLen * 8;
|
|
dest = (char*) uprv_malloc(sizeof(char) * destCap);
|
|
if(dest==NULL){
|
|
*status=U_MEMORY_ALLOCATION_ERROR;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
dest[0]=0;
|
|
|
|
while(i<srcLen){
|
|
if((destLen+UTF8_CHAR_LENGTH(src[i])) < destCap){
|
|
/* ASCII Range */
|
|
if((uint16_t)src[i] <=0x007F){
|
|
|
|
switch(src[i]){
|
|
case '&':
|
|
uprv_strcpy(dest+( destLen),"&");
|
|
destLen+=uprv_strlen("&");
|
|
break;
|
|
case '<':
|
|
uprv_strcpy(dest+(destLen),"<");
|
|
destLen+=uprv_strlen("<");
|
|
break;
|
|
case '>':
|
|
uprv_strcpy(dest+(destLen),">");
|
|
destLen+=uprv_strlen(">");
|
|
break;
|
|
case '"':
|
|
uprv_strcpy(dest+(destLen),""");
|
|
destLen+=uprv_strlen(""");
|
|
break;
|
|
case '\'':
|
|
uprv_strcpy(dest+(destLen),"'");
|
|
destLen+=uprv_strlen("'");
|
|
break;
|
|
default:
|
|
dest[destLen++]=(char)src[i];
|
|
|
|
}
|
|
|
|
}else{
|
|
if(isRule){
|
|
if(destLen+6 > destCap){
|
|
goto REALLOC;
|
|
}
|
|
uprv_strcpy(dest+destLen,"\\u");
|
|
destLen+=2;
|
|
itostr(dest+destLen,src[i],16,4);
|
|
destLen+=4;
|
|
|
|
}else{
|
|
int32_t len=0;
|
|
if(UTF_IS_SURROGATE(src[i])){
|
|
u_strToUTF8(dest+destLen,destCap-destLen,&len,src+i,2,status);
|
|
i++;
|
|
}else{
|
|
u_strToUTF8(dest+destLen,destCap-destLen,&len,src+i,1,status);
|
|
}
|
|
destLen+=len;
|
|
if(U_FAILURE(*status)){
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
i++;
|
|
}else{
|
|
REALLOC:
|
|
|
|
destCap += destLen;
|
|
|
|
temp = (char*) uprv_malloc(sizeof(char)*destCap);
|
|
if(temp==NULL){
|
|
*status=U_MEMORY_ALLOCATION_ERROR;
|
|
return NULL;
|
|
}
|
|
uprv_memmove(temp,dest,destLen);
|
|
destLen=0;
|
|
uprv_free(dest);
|
|
dest=temp;
|
|
temp=NULL;
|
|
}
|
|
|
|
}
|
|
*destLength = destLen;
|
|
return dest;
|
|
}
|
|
|
|
|
|
/* Writing Functions */
|
|
static void
|
|
string_write_xml(struct SResource *res,UErrorCode *status) {
|
|
|
|
char* buf = NULL;
|
|
int32_t bufLen = 0;
|
|
UBool isRule=FALSE;
|
|
if(status==NULL || U_FAILURE(*status)){
|
|
return;
|
|
}
|
|
|
|
if(res->fKey==0xFFFF || uprv_strcmp(srBundle->fKeys+res->fKey ,"")==0){
|
|
const char* valStrStart ="<string val=\"";
|
|
const char* valStrEnd ="\"/>\n";
|
|
write_tabs(out);
|
|
T_FileStream_write(out,valStrStart, uprv_strlen(valStrStart));
|
|
|
|
buf = convertAndEscape(&buf,0,&bufLen,res->u.fString.fChars,res->u.fString.fLength,isRule,status);
|
|
|
|
if(U_FAILURE(*status)){
|
|
return;
|
|
}
|
|
T_FileStream_write(out,buf,bufLen);
|
|
T_FileStream_write(out,valStrEnd,uprv_strlen(valStrEnd));
|
|
}else{
|
|
|
|
const char* keyStrStart ="<string key=\"";
|
|
const char* val ="\" val=\"";
|
|
const char* keyStrEnd ="\"/>\n";
|
|
write_tabs(out);
|
|
if(uprv_strstr(srBundle->fKeys+res->fKey,"Rule") || uprv_strstr(srBundle->fKeys+res->fKey,"RULES")){
|
|
isRule=TRUE;
|
|
}
|
|
T_FileStream_write(out,keyStrStart, uprv_strlen(keyStrStart));
|
|
T_FileStream_write(out,srBundle->fKeys+res->fKey, uprv_strlen(srBundle->fKeys+res->fKey));
|
|
T_FileStream_write(out,val,uprv_strlen(val));
|
|
|
|
buf = convertAndEscape(&buf,0,&bufLen,res->u.fString.fChars,res->u.fString.fLength,isRule,status);
|
|
if(U_FAILURE(*status)){
|
|
return;
|
|
}
|
|
T_FileStream_write(out,buf,bufLen);
|
|
T_FileStream_write(out,keyStrEnd,uprv_strlen(keyStrEnd));
|
|
|
|
}
|
|
uprv_free(buf);
|
|
|
|
}
|
|
|
|
static void
|
|
alias_write_xml(struct SResource *res,UErrorCode *status) {
|
|
static const char* startKey = "<alias key=\"";
|
|
static const char* val = " val=\"";
|
|
static const char* endKey = "\">\n";
|
|
static const char* start = "<alias";
|
|
static const char* end = "</alias>\n";
|
|
char* buf = NULL;
|
|
int32_t bufLen=0;
|
|
write_tabs(out);
|
|
if(res->fKey==0xFFFF || uprv_strcmp(srBundle->fKeys+res->fKey ,"")==0){
|
|
T_FileStream_write(out, start, (int32_t)uprv_strlen(start));
|
|
T_FileStream_write(out, val, (int32_t)uprv_strlen(val));
|
|
}else{
|
|
T_FileStream_write(out, startKey, (int32_t)uprv_strlen(startKey));
|
|
T_FileStream_write(out, srBundle->fKeys+res->fKey, (int32_t) uprv_strlen(srBundle->fKeys+res->fKey));
|
|
T_FileStream_write(out, "\"", 1);
|
|
T_FileStream_write(out, val, (int32_t)uprv_strlen(val));
|
|
}
|
|
|
|
buf = convertAndEscape(&buf,0,&bufLen,res->u.fString.fChars,res->u.fString.fLength,FALSE,status);
|
|
if(U_FAILURE(*status)){
|
|
return;
|
|
}
|
|
T_FileStream_write(out,buf,bufLen);
|
|
T_FileStream_write(out, endKey, uprv_strlen(endKey));
|
|
write_tabs(out);
|
|
T_FileStream_write(out, end, uprv_strlen(end));
|
|
uprv_free(buf);
|
|
|
|
}
|
|
|
|
static void
|
|
array_write_xml( struct SResource *res, UErrorCode *status) {
|
|
static const char* startKey = "<array key=\"";
|
|
static const char* endKey = "\">\n";
|
|
static const char* start = "<array>\n";
|
|
static const char* end = "</array>\n";
|
|
struct SResource *current = NULL;
|
|
struct SResource *first =NULL;
|
|
|
|
write_tabs(out);
|
|
if(res->fKey==0xFFFF ||uprv_strcmp(srBundle->fKeys+res->fKey ,"")==0){
|
|
T_FileStream_write(out, start, (int32_t)uprv_strlen(start));
|
|
}else{
|
|
T_FileStream_write(out, startKey, (int32_t)uprv_strlen(startKey));
|
|
T_FileStream_write(out, srBundle->fKeys+res->fKey, (int32_t) uprv_strlen(srBundle->fKeys+res->fKey));
|
|
T_FileStream_write(out, endKey, uprv_strlen(endKey));
|
|
}
|
|
current = res->u.fArray.fFirst;
|
|
first=current;
|
|
tabCount++;
|
|
while (current != NULL) {
|
|
res_write_xml(current, status);
|
|
if(U_FAILURE(*status)){
|
|
return;
|
|
}
|
|
current = current->fNext;
|
|
}
|
|
tabCount--;
|
|
write_tabs(out);
|
|
T_FileStream_write(out,end,uprv_strlen(end));
|
|
|
|
}
|
|
|
|
static void
|
|
intvector_write_xml( struct SResource *res, UErrorCode *status) {
|
|
static const char* startKey = "<intVector key=\"";
|
|
static const char* val = " val=\"";
|
|
static const char* endKey = "\">\n";
|
|
static const char* start = "<intVector";
|
|
static const char* end = "</intVector>\n";
|
|
uint32_t i=0;
|
|
uint32_t len=0;
|
|
char buf[100]={0};
|
|
write_tabs(out);
|
|
if(res->fKey==0xFFFF || uprv_strcmp(srBundle->fKeys+res->fKey ,"")==0){
|
|
T_FileStream_write(out, start, (int32_t)uprv_strlen(start));
|
|
T_FileStream_write(out, val, (int32_t)uprv_strlen(val));
|
|
}else{
|
|
T_FileStream_write(out, startKey, (int32_t)uprv_strlen(startKey));
|
|
T_FileStream_write(out, srBundle->fKeys+res->fKey, (int32_t) uprv_strlen(srBundle->fKeys+res->fKey));
|
|
T_FileStream_write(out, "\"", 1);
|
|
T_FileStream_write(out, val, (int32_t)uprv_strlen(val));
|
|
}
|
|
/* write the value out */
|
|
for(i = 0; i<res->u.fIntVector.fCount; i++) {
|
|
len=itostr(buf,res->u.fIntVector.fArray[i],10,0);
|
|
T_FileStream_write(out,buf,len);
|
|
T_FileStream_write(out," ",1);
|
|
}
|
|
T_FileStream_write(out, endKey, uprv_strlen(endKey));
|
|
write_tabs(out);
|
|
T_FileStream_write(out, end, uprv_strlen(end));
|
|
|
|
}
|
|
|
|
static void
|
|
int_write_xml(struct SResource *res,UErrorCode *status) {
|
|
static const char* startKey = "<int key=\"";
|
|
static const char* val = " val=\"";
|
|
static const char* endKey = "\">\n";
|
|
static const char* start = "<int";
|
|
static const char* end = "</int>\n";
|
|
uint32_t len=0;
|
|
char buf[100]={0};
|
|
write_tabs(out);
|
|
if(res->fKey==0xFFFF || uprv_strcmp(srBundle->fKeys+res->fKey ,"")==0){
|
|
T_FileStream_write(out, start, (int32_t)uprv_strlen(start));
|
|
T_FileStream_write(out, val, (int32_t)uprv_strlen(val));
|
|
}else{
|
|
T_FileStream_write(out, startKey, (int32_t)uprv_strlen(startKey));
|
|
T_FileStream_write(out, srBundle->fKeys+res->fKey, (int32_t) uprv_strlen(srBundle->fKeys+res->fKey));
|
|
T_FileStream_write(out, "\"", 1);
|
|
T_FileStream_write(out, val, (int32_t)uprv_strlen(val));
|
|
}
|
|
len=itostr(buf,res->u.fIntValue.fValue,10,0);
|
|
T_FileStream_write(out,buf,len);
|
|
T_FileStream_write(out," ",1);
|
|
T_FileStream_write(out, endKey, uprv_strlen(endKey));
|
|
write_tabs(out);
|
|
T_FileStream_write(out, end, uprv_strlen(end));
|
|
|
|
}
|
|
|
|
static void
|
|
bin_write_xml( struct SResource *res, UErrorCode *status) {
|
|
|
|
const char* start= "<bin val=\"";
|
|
const char* end = "\"/>\n";
|
|
const char* importStart ="<importBin key=\"%s\" filename=\"%s\"/>\n";
|
|
char fileName[1024] ={0};
|
|
char* fn = (char*) uprv_malloc(sizeof(char) * (uprv_strlen(outDir)+1024 +
|
|
(res->u.fBinaryValue.fFileName !=NULL ?
|
|
uprv_strlen(res->u.fBinaryValue.fFileName) :0)));
|
|
char* buffer = NULL;
|
|
char* ext = NULL;
|
|
fn[0]=0;
|
|
|
|
if(res->u.fBinaryValue.fLength>100){
|
|
|
|
write_tabs(out);
|
|
if(res->u.fBinaryValue.fFileName!=NULL){
|
|
|
|
buffer = (char*) uprv_malloc(sizeof(char) * ( uprv_strlen(importStart) +
|
|
uprv_strlen(srBundle->fKeys+res->fKey) +
|
|
uprv_strlen(res->u.fBinaryValue.fFileName)
|
|
));
|
|
sprintf(buffer,importStart,srBundle->fKeys+res->fKey,fileName);
|
|
write_tabs(out);
|
|
T_FileStream_write(out, buffer, (int32_t)uprv_strlen(buffer));
|
|
|
|
}else{
|
|
|
|
FileStream* datFile = NULL;
|
|
if(uprv_strcmp(srBundle->fKeys+res->fKey,"BreakDictionaryData")==0){
|
|
uprv_strcat(fileName,"BreakDictionaryData_");
|
|
ext = ".brk";
|
|
}else if(uprv_strcmp(srBundle->fKeys+res->fKey,"%%CollationBin")==0) {
|
|
uprv_strcat(fileName,"CollationElements_");
|
|
ext=".col";
|
|
}else{
|
|
ext =".bin";
|
|
}
|
|
|
|
uprv_strcat(fileName,srBundle->fLocale);
|
|
|
|
uprv_strcat(fileName,ext);
|
|
|
|
uprv_strcat(fn,outDir);
|
|
if(outDir[uprv_strlen(outDir)-1]!=U_FILE_SEP_CHAR){
|
|
uprv_strcat(fn,U_FILE_SEP_STRING);
|
|
}
|
|
uprv_strcat(fn,fileName);
|
|
buffer = (char*) uprv_malloc(sizeof(char) * ( uprv_strlen(importStart) +
|
|
uprv_strlen(srBundle->fKeys+res->fKey) +
|
|
uprv_strlen(fileName)
|
|
));
|
|
buffer[0]=0;
|
|
|
|
sprintf(buffer, importStart,srBundle->fKeys+res->fKey,fileName);
|
|
|
|
write_tabs(out);
|
|
T_FileStream_write(out, buffer, (int32_t)uprv_strlen(buffer));
|
|
|
|
datFile=T_FileStream_open(fn,"w");
|
|
T_FileStream_write(datFile, res->u.fBinaryValue.fData, res->u.fBinaryValue.fLength);
|
|
T_FileStream_close(datFile);
|
|
}
|
|
}else{
|
|
char temp[4] ={0};
|
|
uint32_t i = 0;
|
|
int32_t len=0;
|
|
write_tabs(out);
|
|
T_FileStream_write(out,start,uprv_strlen(start));
|
|
while(i <res->u.fBinaryValue.fLength){
|
|
len = itostr(temp,res->u.fBinaryValue.fData[i],16,2);
|
|
T_FileStream_write(out,temp,len);
|
|
i++;
|
|
}
|
|
|
|
T_FileStream_write(out,end,uprv_strlen(end));
|
|
}
|
|
uprv_free(fn);
|
|
}
|
|
|
|
|
|
static UBool start = TRUE;
|
|
|
|
static void
|
|
table_write_xml(struct SResource *res, UErrorCode *status) {
|
|
|
|
uint32_t i = 0;
|
|
|
|
struct SResource *current = NULL;
|
|
struct SResource *save = NULL;
|
|
const char* start = "<table>\n";
|
|
const char* end = "</table>\n";
|
|
const char* startKey= "<table key=\"";
|
|
const char* endKey = "\">\n";
|
|
|
|
if (U_FAILURE(*status)) {
|
|
return ;
|
|
}
|
|
|
|
if (res->u.fTable.fCount > 0) {
|
|
write_tabs(out);
|
|
if(res->fKey==0xFFFF || uprv_strcmp(srBundle->fKeys+res->fKey ,"")==0){
|
|
T_FileStream_write(out, start, (int32_t)uprv_strlen(start));
|
|
}else{
|
|
T_FileStream_write(out, startKey, (int32_t)uprv_strlen(startKey));
|
|
T_FileStream_write(out, srBundle->fKeys+res->fKey, (int32_t) uprv_strlen(srBundle->fKeys+res->fKey));
|
|
T_FileStream_write(out, endKey, uprv_strlen(endKey));
|
|
}
|
|
tabCount++;
|
|
|
|
save = current = res->u.fTable.fFirst;
|
|
i = 0;
|
|
while (current != NULL) {
|
|
res_write_xml(current, status);
|
|
if(U_FAILURE(*status)){
|
|
return;
|
|
}
|
|
i++;
|
|
current = current->fNext;
|
|
}
|
|
tabCount--;
|
|
write_tabs(out);
|
|
T_FileStream_write(out,end,uprv_strlen(end));
|
|
} else {
|
|
write_tabs(out);
|
|
T_FileStream_write(out,start,uprv_strlen(start));
|
|
write_tabs(out);
|
|
T_FileStream_write(out,end,uprv_strlen(end));
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
res_write_xml(struct SResource *res,UErrorCode *status) {
|
|
|
|
if (U_FAILURE(*status)) {
|
|
return ;
|
|
}
|
|
|
|
if (res != NULL) {
|
|
switch (res->fType) {
|
|
case RES_STRING:
|
|
string_write_xml (res, status);
|
|
return;
|
|
case RES_ALIAS:
|
|
alias_write_xml (res, status);
|
|
return;
|
|
case RES_INT_VECTOR:
|
|
intvector_write_xml (res, status);
|
|
return;
|
|
case RES_BINARY:
|
|
bin_write_xml (res, status);
|
|
return;
|
|
case RES_INT:
|
|
int_write_xml (res, status);
|
|
return;
|
|
case RES_ARRAY:
|
|
array_write_xml (res, status);
|
|
return;
|
|
case RES_TABLE:
|
|
table_write_xml (res, status);
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
*status = U_INTERNAL_PROGRAM_ERROR;
|
|
}
|
|
|
|
void
|
|
bundle_write_xml(struct SRBRoot *bundle, const char *outputDir,const char* outputEnc,
|
|
char *writtenFilename, int writtenFilenameLen,
|
|
UErrorCode *status) {
|
|
|
|
char fileName[256] = {'\0'};
|
|
outDir = outputDir;
|
|
|
|
srBundle = bundle;
|
|
|
|
if(outputDir){
|
|
uprv_strcpy(fileName, outputDir);
|
|
if(outputDir[uprv_strlen(outputDir)-1] !=U_FILE_SEP_CHAR){
|
|
uprv_strcat(fileName,U_FILE_SEP_STRING);
|
|
}
|
|
uprv_strcat(fileName,srBundle->fLocale);
|
|
uprv_strcat(fileName,".xml");
|
|
}else{
|
|
uprv_strcat(fileName,srBundle->fLocale);
|
|
uprv_strcat(fileName,".xml");
|
|
}
|
|
|
|
if (writtenFilename) {
|
|
uprv_strncpy(writtenFilename, fileName, writtenFilenameLen);
|
|
}
|
|
|
|
if (U_FAILURE(*status)) {
|
|
return;
|
|
}
|
|
|
|
out= T_FileStream_open(fileName,"w");
|
|
|
|
if(out==NULL){
|
|
*status = U_FILE_ACCESS_ERROR;
|
|
return;
|
|
}
|
|
T_FileStream_write(out,xmlHeader, uprv_strlen(xmlHeader));
|
|
|
|
if(outputEnc && *outputEnc!='\0'){
|
|
/* store the output encoding */
|
|
enc = outputEnc;
|
|
conv=ucnv_open(enc,status);
|
|
if(U_FAILURE(*status)){
|
|
return;
|
|
}
|
|
}
|
|
T_FileStream_write(out,bundleStart,uprv_strlen(bundleStart));
|
|
T_FileStream_write(out,srBundle->fLocale,uprv_strlen(srBundle->fLocale));
|
|
T_FileStream_write(out,"\">\n",3);
|
|
res_write_xml(bundle->fRoot, status);
|
|
T_FileStream_write(out,bundleEnd,uprv_strlen(bundleEnd));
|
|
T_FileStream_close(out);
|
|
|
|
ucnv_close(conv);
|
|
|
|
}
|
|
|