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.
681 lines
18 KiB
681 lines
18 KiB
/*
|
|
** A program to convert the XML rendered by KWord into LATEX.
|
|
**
|
|
** Copyright (C) 2000, 2001, 2002 Robert JACOLIN
|
|
**
|
|
** This library is free software; you can redistribute it and/or
|
|
** modify it under the terms of the GNU Library General Public
|
|
** License as published by the Free Software Foundation; either
|
|
** version 2 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
|
|
** Library General Public License for more details.
|
|
**
|
|
** To receive a copy of the GNU Library General Public License, write to the
|
|
** Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
**
|
|
*/
|
|
|
|
#include <stdlib.h> /* for atoi function */
|
|
#include <kdebug.h> /* for kdDebug() stream */
|
|
#include "para.h"
|
|
#include "textFrame.h" /* father class. */
|
|
#include "format.h" /* children classes. */
|
|
//#include "picturezone.h"
|
|
#include "fileheader.h"
|
|
#include "textzone.h"
|
|
#include "variablezone.h"
|
|
#include "footnote.h"
|
|
#include "anchor.h"
|
|
|
|
/* static data */
|
|
TQPtrStack<EType> Para::_historicList;
|
|
int Para::_tabulation = 0;
|
|
|
|
/*******************************************/
|
|
/* Constructor */
|
|
/*******************************************/
|
|
Para::Para(TextFrame* textFrame)
|
|
{
|
|
_element = textFrame;
|
|
_lines = 0;
|
|
_name = 0;
|
|
_info = EP_NONE; /* the parag is not a footnote */
|
|
//_hardbrk = EP_FLOW; /* and it's not a new page */
|
|
_currentPos = 0; /* At the beginning of the paragraph */
|
|
_tabulation = 0;
|
|
_text = "";
|
|
}
|
|
|
|
/*******************************************/
|
|
/* Destructor */
|
|
/*******************************************/
|
|
Para::~Para()
|
|
{
|
|
kdDebug(30522) << "Destruction of a parag." << endl;
|
|
if(_lines != 0)
|
|
delete _lines;
|
|
}
|
|
|
|
/*******************************************/
|
|
/* GetFrameType */
|
|
/*******************************************/
|
|
/* To know if it's the text or it's a */
|
|
/* header or a footer. */
|
|
/*******************************************/
|
|
SSect Para::getFrameType() const
|
|
{
|
|
return _element->getSection();
|
|
}
|
|
|
|
/*******************************************/
|
|
/* getTypeFormat */
|
|
/*******************************************/
|
|
/* To know if the zone is a textzone, a */
|
|
/* footnote, a picture, a variable. */
|
|
/*******************************************/
|
|
EFormat Para::getTypeFormat(const TQDomNode balise) const
|
|
{
|
|
//<FORMAT id="1" ...>
|
|
|
|
return (EFormat) getAttr(balise, "id").toInt();
|
|
}
|
|
|
|
/*******************************************/
|
|
/* getNbCharPara */
|
|
/*******************************************/
|
|
/* To know the size of a paragraph. */
|
|
/*******************************************/
|
|
int Para::getNbCharPara() const
|
|
{
|
|
int nb = 0;
|
|
Format* zone = 0;
|
|
|
|
if(_lines != 0)
|
|
{
|
|
kdDebug(30522) << " NB ZONE : " << _lines->count() << endl;
|
|
|
|
for(zone = _lines->first(); zone != 0; zone = _lines->next())
|
|
{
|
|
switch(zone->getId())
|
|
{
|
|
case EF_TEXTZONE:
|
|
nb = nb + ((TextZone*) zone)->getSize();
|
|
break;
|
|
case EF_PICTURE:
|
|
break;
|
|
case EF_TABULATOR:
|
|
break;
|
|
case EF_VARIABLE:
|
|
break;
|
|
case EF_FOOTNOTE:
|
|
break;
|
|
case EF_ANCHOR:
|
|
break;
|
|
case EF_ERROR:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return nb;
|
|
}
|
|
|
|
/*******************************************/
|
|
/* Analyse */
|
|
/*******************************************/
|
|
void Para::analyse(const TQDomNode balise)
|
|
{
|
|
/* MARKUP TYPE : PARAGRAPH */
|
|
|
|
kdDebug(30522) << "**** PARAGRAPH ****" << endl;
|
|
|
|
/* Analyse of the children markups */
|
|
for(int index = 0; index < getNbChild(balise); index++)
|
|
{
|
|
if(getChildName(balise, index).compare("TEXT")== 0)
|
|
{
|
|
_text = getData(balise, index);
|
|
kdDebug(30522) << "TEXT : " << _text << endl;
|
|
}
|
|
else if(getChildName(balise, index).compare("NAME")== 0)
|
|
{
|
|
analyseName(getChild(balise, index));
|
|
}
|
|
else if(getChildName(balise, index).compare("INFO")== 0)
|
|
{
|
|
analyseInfo(getChild(balise, index));
|
|
}
|
|
/*else if(getChildName(balise, index).compare("HARDBRK")== 0)
|
|
{
|
|
analyseBrk(getChild(balise, index));
|
|
}*/
|
|
else if(getChildName(balise, index).compare("FORMATS")== 0)
|
|
{
|
|
// IMPORTANT ==> police + style
|
|
kdDebug(30522) << "FORMATS" << endl;
|
|
analyseFormats(getChild(balise, index));
|
|
|
|
}
|
|
else if(getChildName(balise, index).compare("LAYOUT")== 0)
|
|
{
|
|
kdDebug(30522) << "LAYOUT" << endl;
|
|
analyseLayoutPara(getChild(balise, index));
|
|
}
|
|
}
|
|
kdDebug(30522) << " **** END PARAGRAPH ****" << endl;
|
|
}
|
|
|
|
/*******************************************/
|
|
/* AnalyseName */
|
|
/*******************************************/
|
|
/* If a footnote have a name : it's a */
|
|
/* footnote/endnote. */
|
|
/*******************************************/
|
|
void Para::analyseName(const TQDomNode balise)
|
|
{
|
|
/* <NAME name="Footnote/Endnote_1"> */
|
|
|
|
_name = new TQString(getAttr(balise, "NAME"));
|
|
}
|
|
|
|
/*******************************************/
|
|
/* AnalyseInfo */
|
|
/*******************************************/
|
|
/* Type of the parag. : if info is 1, it's */
|
|
/* a footnote/endnote (so it have a name). */
|
|
/*******************************************/
|
|
void Para::analyseInfo(const TQDomNode balise)
|
|
{
|
|
/* <INFO info="1"> */
|
|
|
|
_info = (EP_INFO) getAttr(balise, "INFO").toInt();
|
|
}
|
|
|
|
/*******************************************/
|
|
/* AnalyseBrk */
|
|
/*******************************************/
|
|
/* There is a new page before this */
|
|
/* paragraph. */
|
|
/*******************************************/
|
|
/*void Para::analyseBrk(const TQDomNode balise)
|
|
{
|
|
//<NAME name="Footnote/Endnote_1">
|
|
|
|
_hardbrk = (EP_HARDBRK) getAttr(balise, "FRAME").toInt();
|
|
}*/
|
|
|
|
/*******************************************/
|
|
/* AnalyseLayoutPara */
|
|
/*******************************************/
|
|
/* Analyse the layout of a para. */
|
|
/* For each format, keep the type (picture,*/
|
|
/* text, variable, footnote) and put the */
|
|
/* zone in a list. */
|
|
/*******************************************/
|
|
void Para::analyseLayoutPara(const TQDomNode balise)
|
|
{
|
|
Format* zone = 0;
|
|
|
|
analyseLayout(balise);
|
|
for(int index= 0; index < getNbChild(balise); index++)
|
|
{
|
|
if(getChildName(balise, index).compare("FORMAT")== 0)
|
|
{
|
|
//analyseFormat(balise);
|
|
/* No more format : verify if all the text zone has been formated */
|
|
if(_currentPos != _text.length())
|
|
{
|
|
zone = new TextZone(_text, this);
|
|
((TextZone*) zone)->setPos(_currentPos);
|
|
((TextZone*) zone)->setLength(_currentPos - _text.length());
|
|
((TextZone*) zone)->analyse();
|
|
if(_lines == 0)
|
|
_lines = new TQPtrList<Format>;
|
|
/* add the text */
|
|
_lines->append(zone);
|
|
_currentPos = _currentPos + ((TextZone*) zone)->getLength();
|
|
|
|
}
|
|
}
|
|
/*else
|
|
kdDebug(30522) << " FORMAT FIELD UNKNOWN" << endl;*/
|
|
}
|
|
}
|
|
|
|
/*******************************************/
|
|
/* AnalyseFormats */
|
|
/*******************************************/
|
|
/* Analyse several formats. */
|
|
/* keep the type (picture, text, variable,*/
|
|
/* footnote) and put the zone in a list. */
|
|
/*******************************************/
|
|
void Para::analyseFormats(const TQDomNode balise)
|
|
{
|
|
for(int index= 0; index < getNbChild(balise, "FORMAT"); index++)
|
|
{
|
|
if(getChildName(balise, index).compare("FORMAT")== 0)
|
|
{
|
|
kdDebug(30522) << "A FORMAT !!!" << endl;
|
|
analyseFormat(getChild(balise, index));
|
|
}
|
|
else
|
|
kdDebug(30522) << " FORMAT UNUSEFULL HERE" << endl;
|
|
}
|
|
}
|
|
|
|
/*******************************************/
|
|
/* AnalyseFormat */
|
|
/*******************************************/
|
|
/* Analyse one format. */
|
|
/* keep the type (picture, text, variable,*/
|
|
/* footnote) and put the zone in a list. */
|
|
/*******************************************/
|
|
void Para::analyseFormat(const TQDomNode balise)
|
|
{
|
|
Format *zone = 0;
|
|
Format *zoneFirst = 0;
|
|
|
|
kdDebug(30522) << "ANALYSE FORMAT BODY" << endl;
|
|
switch(getTypeFormat(balise))
|
|
{
|
|
case EF_ERROR: kdDebug(30522) << "Id format error" << endl;
|
|
break;
|
|
case EF_TEXTZONE: /* It's a text line (1) */
|
|
zone = new TextZone(_text, this);
|
|
if(_currentPos != _text.length())
|
|
{
|
|
zone->analyse(balise);
|
|
if(zone->getPos() != _currentPos)
|
|
{
|
|
if(_lines == 0)
|
|
_lines = new TQPtrList<Format>;
|
|
/* Create first a default format */
|
|
zoneFirst = new TextZone(_text, this);
|
|
zoneFirst->setPos(_currentPos);
|
|
zoneFirst->setLength(zone->getPos() - _currentPos);
|
|
((TextZone*) zoneFirst)->analyse();
|
|
|
|
/* Add the text without format */
|
|
_lines->append(zoneFirst);
|
|
_currentPos = _currentPos + ((TextZone*) zoneFirst)->getLength();
|
|
}
|
|
}
|
|
break;
|
|
case EF_PICTURE: /* It's a picture (2) */
|
|
/*zone = new PictureZone(this);
|
|
zone->analyse(balise);*/
|
|
break;
|
|
case EF_VARIABLE: /* It's a variable (4) */
|
|
zone = new VariableZone(this);
|
|
zone->analyse(balise);
|
|
break;
|
|
case EF_FOOTNOTE: /* It's a footnote (5) */
|
|
zone = new Footnote(this);
|
|
zone->analyse(balise);
|
|
break;
|
|
case EF_ANCHOR: /* It's an anchor (6) */
|
|
zone = new Anchor(this);
|
|
zone->analyse(balise);
|
|
break;
|
|
default: /* Unknown */
|
|
kdDebug(30522) << "Format not yet supported" << endl;
|
|
}
|
|
|
|
if(zone->getPos() != _currentPos)
|
|
{
|
|
if(_lines == 0)
|
|
_lines = new TQPtrList<Format>;
|
|
/* Create first a default format */
|
|
zoneFirst = new TextZone(_text, this);
|
|
zoneFirst->setPos(_currentPos);
|
|
zoneFirst->setLength(zone->getPos() - _currentPos);
|
|
((TextZone*) zoneFirst)->analyse();
|
|
kdDebug(30522) << "pos courante : " << _currentPos << endl;
|
|
/* Add the text without format */
|
|
_lines->append(zoneFirst);
|
|
_currentPos = _currentPos + zoneFirst->getLength();
|
|
}
|
|
|
|
if(zone != 0)
|
|
{
|
|
if(_lines == 0)
|
|
_lines = new TQPtrList<Format>;
|
|
|
|
/* add the text */
|
|
_lines->append(zone);
|
|
_currentPos = _currentPos + zone->getLength();
|
|
}
|
|
}
|
|
|
|
/*******************************************/
|
|
/* Generate */
|
|
/*******************************************/
|
|
/* Generate each text zone with the parag. */
|
|
/* markup. */
|
|
/*******************************************/
|
|
void Para::generate(TQTextStream &out)
|
|
{
|
|
|
|
kdDebug(30522) << " GENERATION PARA" << endl;
|
|
|
|
if(getInfo() != EP_FOOTNOTE && getFrameType() != SS_HEADERS &&
|
|
getFrameType() != SS_FOOTERS)
|
|
{
|
|
/* We generate center, itemize tag and new page only for
|
|
* parag not for footnote
|
|
* If a parag. have a special format (begining)
|
|
*/
|
|
if(isHardBreak())
|
|
out << "\\newpage" << endl;
|
|
generateDebut(out);
|
|
}
|
|
|
|
/* If text is a \n, then it's a break line. */
|
|
if(_text == "\n")
|
|
out << "\\\\" << endl;
|
|
else if(_lines != 0)
|
|
{
|
|
Format* zone = 0;
|
|
kdDebug(30522) << " NB ZONE : " << _lines->count() << endl;
|
|
|
|
for(zone = _lines->first(); zone != 0; zone = _lines->next())
|
|
{
|
|
zone->generate(out);
|
|
}
|
|
/* To separate the text zones. */
|
|
}
|
|
|
|
if(getInfo() != EP_FOOTNOTE && getFrameType() != SS_HEADERS &&
|
|
getFrameType() != SS_FOOTERS)
|
|
{
|
|
/* id than above : a parag. have a special format. (end)
|
|
* only it's not a header, nor a footer nor a footnote/endnote
|
|
*/
|
|
generateFin(out);
|
|
if(isHardBreakAfter())
|
|
out << "\\newpage" << endl;
|
|
}
|
|
kdDebug(30522) << "PARA GENERATED" << endl;
|
|
}
|
|
|
|
/*******************************************/
|
|
/* GenerateDebut */
|
|
/*******************************************/
|
|
/* Generate the begining paragraph markup. */
|
|
/*******************************************/
|
|
void Para::generateDebut(TQTextStream &out)
|
|
{
|
|
/* Be careful we are in a table !
|
|
* You can't use directly environment, ...
|
|
*/
|
|
if(getFrameType() == SS_TABLE)
|
|
{
|
|
//int sizeCell = 5;
|
|
/* first number depends with the cell size (next number}
|
|
* and with the number of characters in the para.
|
|
* It can be 20 char. / 5 cm = 4 char / cm so */
|
|
/* nbLines = nb_char_para / (4 * cell size) + 1 */
|
|
//sizeCell = (_element->getRight() - _element->getLeft()) / 27;
|
|
//kdDebug(30522) << "SIZE OF CELL : " << sizeCell << endl;
|
|
// TODO : arrondir au superieur avec tgmath.h ??
|
|
//_nbLines = ((_element->getBottom() - _element->getTop()) / 27) + 1;
|
|
//kdDebug(30522) << "NB OF LINES : " << _nbLines << endl;
|
|
/* 2 at least, 1 for the line, 1 for the space line */
|
|
/*if(_nbLines < 2)
|
|
_nbLines = 2;
|
|
out << "\\multirow{" << _nbLines << "}{"<< sizeCell << "cm}{" << endl;*/
|
|
}
|
|
/* if it's a chapter */
|
|
if(isChapter())
|
|
{
|
|
/* switch the type, the depth do */
|
|
generateTitle(out);
|
|
Config::instance()->indent();
|
|
}
|
|
else if(isEnum())
|
|
{
|
|
Config::instance()->writeIndent(out);
|
|
out << "\\item ";
|
|
}
|
|
else
|
|
Config::instance()->writeIndent(out);
|
|
}
|
|
|
|
void Para::generateBeginEnv(TQTextStream &out)
|
|
{
|
|
kdDebug(30522) << "Begin new Env : " << getEnv() << endl;
|
|
|
|
Config::instance()->writeIndent(out);
|
|
|
|
switch(getEnv())
|
|
{
|
|
case ENV_LEFT: out << "\\begin{flushleft}" << endl;
|
|
break;
|
|
case ENV_RIGHT: out << "\\begin{flushright}" << endl;
|
|
break;
|
|
case ENV_CENTER: out << "\\begin{center}" << endl;
|
|
break;
|
|
case ENV_JUSTIFY: out << endl;
|
|
break;
|
|
case ENV_NONE:
|
|
break;
|
|
}
|
|
|
|
Config::instance()->indent();
|
|
}
|
|
|
|
/*******************************************/
|
|
/* openList */
|
|
/*******************************************/
|
|
/* Generate the markup to begin a list and */
|
|
/* push the type in the historic stack. */
|
|
/*******************************************/
|
|
void Para::openList(TQTextStream &out)
|
|
{
|
|
EType *type_temp = 0;
|
|
|
|
Config::instance()->writeIndent(out);
|
|
|
|
switch(getCounterType())
|
|
{
|
|
case TL_NONE:
|
|
break;
|
|
case TL_ARABIC:
|
|
out << "\\begin{enumerate}" << endl;
|
|
break;
|
|
case TL_LLETTER: /* a, b, ... */
|
|
out << "\\begin{enumerate}[a]" << endl;
|
|
break;
|
|
case TL_CLETTER: /* A, B, ... */
|
|
out << "\\begin{enumerate}[A]" << endl;
|
|
break;
|
|
case TL_LLNUMBER: /* i, ii, ... */
|
|
out << "\\begin{enumerate}[i]" << endl;
|
|
break;
|
|
case TL_CLNUMBER: /* I, II, ... */
|
|
out << "\\begin{enumerate}[I]" << endl;
|
|
break;
|
|
case TL_CUSTOM_SIMPLE: /* - */
|
|
out << "\\begin{enumerate}[" << convertSpecialChar(getCounterBullet()) << "]" << endl;
|
|
break;
|
|
case TL_CUSTOM_COMPLEX: /* - */
|
|
out << "\\begin{enumerate}[" << convertSpecialChar(getCounterBullet()) << "]" << endl;
|
|
break;
|
|
case TL_CIRCLE_BULLET:
|
|
out << "\\begin{itemize}" << endl;
|
|
break;
|
|
case TL_SQUARE_BULLET:
|
|
out << "\\begin{itemize}" << endl;
|
|
break;
|
|
case TL_DISC_BULLET:
|
|
out << "\\begin{itemize}" << endl;
|
|
break;
|
|
default:
|
|
out << "\\begin{itemize}[SPECIAL]" << endl;
|
|
}
|
|
|
|
Config::instance()->indent();
|
|
|
|
/* Keep the list type */
|
|
type_temp = new EType(getCounterType());
|
|
kdDebug(30522) << " type list to open : " << *type_temp << endl;
|
|
_historicList.push(type_temp);
|
|
}
|
|
|
|
/*******************************************/
|
|
/* GenerateFin */
|
|
/*******************************************/
|
|
/* Generate the closing paragraph markup. */
|
|
/*******************************************/
|
|
void Para::generateFin(TQTextStream &out)
|
|
{
|
|
/* Close a title of chapter */
|
|
if(isChapter())
|
|
out << "}";
|
|
}
|
|
|
|
/*******************************************/
|
|
/* GenerateEndEnv */
|
|
/*******************************************/
|
|
/* Generate the closing environment markup.*/
|
|
/*******************************************/
|
|
void Para::generateEndEnv(TQTextStream &out)
|
|
{
|
|
kdDebug(30522) << "end of an environment : " << getEnv() << endl;
|
|
|
|
Config::instance()->desindent();
|
|
|
|
switch(getEnv())
|
|
{
|
|
case ENV_LEFT:
|
|
out << endl;
|
|
Config::instance()->writeIndent(out);
|
|
out << "\\end{flushleft}";
|
|
break;
|
|
case ENV_RIGHT:
|
|
out << endl;
|
|
Config::instance()->writeIndent(out);
|
|
out << "\\end{flushright}";
|
|
break;
|
|
case ENV_CENTER:
|
|
out << endl;
|
|
Config::instance()->writeIndent(out);
|
|
out << "\\end{center}";
|
|
break;
|
|
case ENV_JUSTIFY:
|
|
break;
|
|
case ENV_NONE:
|
|
break;
|
|
}
|
|
|
|
Config::instance()->desindent();
|
|
}
|
|
|
|
/*******************************************/
|
|
/* closeList */
|
|
/*******************************************/
|
|
/* Generate the closing list markup for a */
|
|
/* list type (letter, custom, ...) and */
|
|
/* remove the last list saved. */
|
|
/*******************************************/
|
|
void Para::closeList(TQTextStream &out, Para* next)
|
|
{
|
|
closeList(getCounterType(), out);
|
|
|
|
if(((getCounterDepth() - 1) >= 0) && ((next!= 0 && !next->isEnum()) || next == 0))
|
|
{
|
|
/* We must close all the lists since
|
|
* after this paragraph it's a normal paragraph.
|
|
*/
|
|
kdDebug(30522) << "lists to close" << endl;
|
|
while(!_historicList.isEmpty())
|
|
{
|
|
EType *type_temp = 0;
|
|
type_temp = _historicList.pop();
|
|
if(type_temp != 0)
|
|
closeList(*type_temp, out);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*******************************************/
|
|
/* closeList */
|
|
/*******************************************/
|
|
/* Generate the closing list markup for a */
|
|
/* list type (letter, custom, ...) and */
|
|
/* remove the last list saved. */
|
|
/*******************************************/
|
|
void Para::closeList(EType type, TQTextStream &out)
|
|
{
|
|
//out << endl;
|
|
kdDebug(30522) << " type list to close : " << type << endl;
|
|
|
|
/* Because of a new markup, we need a new line. */
|
|
out << endl;
|
|
|
|
Config::instance()->desindent();
|
|
Config::instance()->writeIndent(out);
|
|
|
|
/* but the next parag is not a same list */
|
|
switch(type)
|
|
{
|
|
case TL_NONE: //out << endl;
|
|
break;
|
|
case TL_ARABIC:
|
|
case TL_LLETTER: /* a, b, ... */
|
|
case TL_CLETTER: /* A, B, ... P. 250*/
|
|
case TL_LLNUMBER: /* i, ii, ... */
|
|
case TL_CLNUMBER: /* I, II, ... */
|
|
case TL_CUSTOM_SIMPLE: /* - */
|
|
case TL_CUSTOM_COMPLEX: /* - */
|
|
out << "\\end{enumerate}" << endl;
|
|
break;
|
|
case TL_CIRCLE_BULLET:
|
|
out << "\\end{itemize}" << endl;
|
|
break;
|
|
case TL_SQUARE_BULLET:
|
|
case TL_DISC_BULLET:
|
|
out << "\\end{itemize}" << endl;
|
|
break;
|
|
default:
|
|
out << "no suported" << endl;
|
|
}
|
|
|
|
Config::instance()->writeIndent(out);
|
|
|
|
/* Pop the list which has been closed */
|
|
_historicList.remove();
|
|
kdDebug(30522) << "removed" << endl;
|
|
}
|
|
|
|
/*******************************************/
|
|
/* GenerateTitle */
|
|
/*******************************************/
|
|
void Para::generateTitle(TQTextStream &out)
|
|
{
|
|
switch(getCounterDepth())
|
|
{
|
|
case 0:
|
|
out << "\\section{";
|
|
break;
|
|
case 1:
|
|
out << "\\subsection{";
|
|
break;
|
|
case 2:
|
|
out << "\\subsubsection{";
|
|
break;
|
|
case 3:
|
|
out << "\\paragraph{";
|
|
break;
|
|
case 4:
|
|
out << "\\subparagraph{";
|
|
break;
|
|
case 5:
|
|
out << "% section too deep" << endl;
|
|
out << "\\textbf{";
|
|
}
|
|
}
|