1 : /**********************************************************************
2 : * $Id: gmlfeatureclass.cpp 22954 2011-08-19 21:47:19Z rouault $
3 : *
4 : * Project: GML Reader
5 : * Purpose: Implementation of GMLFeatureClass.
6 : * Author: Frank Warmerdam, warmerdam@pobox.com
7 : *
8 : **********************************************************************
9 : * Copyright (c) 2002, Frank Warmerdam
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : #include "gmlreader.h"
31 : #include "cpl_conv.h"
32 : #include "ogr_core.h"
33 :
34 : /************************************************************************/
35 : /* GMLFeatureClass() */
36 : /************************************************************************/
37 :
38 626 : GMLFeatureClass::GMLFeatureClass( const char *pszName )
39 :
40 : {
41 626 : m_pszName = CPLStrdup( pszName );
42 626 : n_nNameLen = strlen( m_pszName );
43 626 : m_pszElementName = NULL;
44 626 : n_nElementNameLen = 0;
45 626 : m_pszGeometryElement = NULL;
46 626 : m_nPropertyCount = 0;
47 626 : m_papoProperty = NULL;
48 626 : m_bSchemaLocked = FALSE;
49 :
50 626 : m_pszExtraInfo = NULL;
51 626 : m_bHaveExtents = FALSE;
52 626 : m_nFeatureCount = -1; // unknown
53 :
54 626 : m_nGeometryType = 0; // wkbUnknown
55 626 : m_nGeometryIndex = -1;
56 :
57 626 : m_pszSRSName = NULL;
58 626 : m_bSRSNameConsistant = TRUE;
59 626 : }
60 :
61 : /************************************************************************/
62 : /* ~GMLFeatureClass() */
63 : /************************************************************************/
64 :
65 626 : GMLFeatureClass::~GMLFeatureClass()
66 :
67 : {
68 626 : CPLFree( m_pszName );
69 626 : CPLFree( m_pszElementName );
70 626 : CPLFree( m_pszGeometryElement );
71 :
72 4022 : for( int i = 0; i < m_nPropertyCount; i++ )
73 3396 : delete m_papoProperty[i];
74 626 : CPLFree( m_papoProperty );
75 626 : CPLFree( m_pszSRSName );
76 626 : }
77 :
78 : /************************************************************************/
79 : /* GetProperty(int) */
80 : /************************************************************************/
81 :
82 1659738 : GMLPropertyDefn *GMLFeatureClass::GetProperty( int iIndex ) const
83 :
84 : {
85 1659738 : if( iIndex < 0 || iIndex >= m_nPropertyCount )
86 3686 : return NULL;
87 : else
88 1656052 : return m_papoProperty[iIndex];
89 : }
90 :
91 : /************************************************************************/
92 : /* GetPropertyIndex() */
93 : /************************************************************************/
94 :
95 7398 : int GMLFeatureClass::GetPropertyIndex( const char *pszName ) const
96 :
97 : {
98 22418 : for( int i = 0; i < m_nPropertyCount; i++ )
99 18698 : if( EQUAL(pszName,m_papoProperty[i]->GetName()) )
100 3678 : return i;
101 :
102 3720 : return -1;
103 : }
104 :
105 : /************************************************************************/
106 : /* GetPropertyIndexBySrcElement() */
107 : /************************************************************************/
108 :
109 3702 : int GMLFeatureClass::GetPropertyIndexBySrcElement( const char *pszElement, int nLen ) const
110 :
111 : {
112 9892 : for( int i = 0; i < m_nPropertyCount; i++ )
113 12420 : if( nLen == (int)m_papoProperty[i]->GetSrcElementLen() &&
114 3608 : memcmp(pszElement,m_papoProperty[i]->GetSrcElement(), nLen) == 0)
115 2622 : return i;
116 :
117 1080 : return -1;
118 : }
119 :
120 :
121 :
122 : /************************************************************************/
123 : /* AddProperty() */
124 : /************************************************************************/
125 :
126 3396 : int GMLFeatureClass::AddProperty( GMLPropertyDefn *poDefn )
127 :
128 : {
129 3396 : if( GetProperty(poDefn->GetName()) != NULL )
130 : {
131 : CPLError(CE_Warning, CPLE_AppDefined,
132 : "Field with same name (%s) already exists. Skipping newer ones",
133 0 : poDefn->GetName());
134 0 : return -1;
135 : }
136 :
137 3396 : m_nPropertyCount++;
138 : m_papoProperty = (GMLPropertyDefn **)
139 3396 : CPLRealloc( m_papoProperty, sizeof(void*) * m_nPropertyCount );
140 :
141 3396 : m_papoProperty[m_nPropertyCount-1] = poDefn;
142 :
143 3396 : return m_nPropertyCount-1;
144 : }
145 :
146 : /************************************************************************/
147 : /* SetElementName() */
148 : /************************************************************************/
149 :
150 300 : void GMLFeatureClass::SetElementName( const char *pszElementName )
151 :
152 : {
153 300 : CPLFree( m_pszElementName );
154 300 : m_pszElementName = CPLStrdup( pszElementName );
155 300 : n_nElementNameLen = strlen(pszElementName);
156 300 : }
157 :
158 : /************************************************************************/
159 : /* GetElementName() */
160 : /************************************************************************/
161 :
162 2503088 : const char *GMLFeatureClass::GetElementName() const
163 :
164 : {
165 2503088 : if( m_pszElementName == NULL )
166 3674 : return m_pszName;
167 : else
168 2499414 : return m_pszElementName;
169 : }
170 :
171 : /************************************************************************/
172 : /* GetElementName() */
173 : /************************************************************************/
174 :
175 1320 : size_t GMLFeatureClass::GetElementNameLen() const
176 :
177 : {
178 1320 : if( m_pszElementName == NULL )
179 900 : return n_nNameLen;
180 : else
181 420 : return n_nElementNameLen;
182 : }
183 :
184 : /************************************************************************/
185 : /* SetGeometryElement() */
186 : /************************************************************************/
187 :
188 242 : void GMLFeatureClass::SetGeometryElement( const char *pszElement )
189 :
190 : {
191 242 : CPLFree( m_pszGeometryElement );
192 242 : if (pszElement)
193 224 : m_pszGeometryElement = CPLStrdup( pszElement );
194 : else
195 18 : m_pszGeometryElement = NULL;
196 242 : }
197 :
198 : /************************************************************************/
199 : /* GetFeatureCount() */
200 : /************************************************************************/
201 :
202 2694 : int GMLFeatureClass::GetFeatureCount()
203 :
204 : {
205 2694 : return m_nFeatureCount;
206 : }
207 :
208 : /************************************************************************/
209 : /* SetFeatureCount() */
210 : /************************************************************************/
211 :
212 1084 : void GMLFeatureClass::SetFeatureCount( int nNewCount )
213 :
214 : {
215 1084 : m_nFeatureCount = nNewCount;
216 1084 : }
217 :
218 : /************************************************************************/
219 : /* GetExtraInfo() */
220 : /************************************************************************/
221 :
222 0 : const char *GMLFeatureClass::GetExtraInfo()
223 :
224 : {
225 0 : return m_pszExtraInfo;
226 : }
227 :
228 : /************************************************************************/
229 : /* SetExtraInfo() */
230 : /************************************************************************/
231 :
232 0 : void GMLFeatureClass::SetExtraInfo( const char *pszExtraInfo )
233 :
234 : {
235 0 : CPLFree( m_pszExtraInfo );
236 0 : m_pszExtraInfo = NULL;
237 :
238 0 : if( pszExtraInfo != NULL )
239 0 : m_pszExtraInfo = CPLStrdup( pszExtraInfo );
240 0 : }
241 :
242 : /************************************************************************/
243 : /* SetExtents() */
244 : /************************************************************************/
245 :
246 964 : void GMLFeatureClass::SetExtents( double dfXMin, double dfXMax,
247 : double dfYMin, double dfYMax )
248 :
249 : {
250 964 : m_dfXMin = dfXMin;
251 964 : m_dfXMax = dfXMax;
252 964 : m_dfYMin = dfYMin;
253 964 : m_dfYMax = dfYMax;
254 :
255 964 : m_bHaveExtents = TRUE;
256 964 : }
257 :
258 : /************************************************************************/
259 : /* GetExtents() */
260 : /************************************************************************/
261 :
262 736 : int GMLFeatureClass::GetExtents( double *pdfXMin, double *pdfXMax,
263 : double *pdfYMin, double *pdfYMax )
264 :
265 : {
266 736 : if( m_bHaveExtents )
267 : {
268 660 : *pdfXMin = m_dfXMin;
269 660 : *pdfXMax = m_dfXMax;
270 660 : *pdfYMin = m_dfYMin;
271 660 : *pdfYMax = m_dfYMax;
272 : }
273 :
274 736 : return m_bHaveExtents;
275 : }
276 :
277 : /************************************************************************/
278 : /* SetSRSName() */
279 : /************************************************************************/
280 :
281 312 : void GMLFeatureClass::SetSRSName( const char* pszSRSName )
282 :
283 : {
284 312 : m_bSRSNameConsistant = TRUE;
285 312 : CPLFree(m_pszSRSName);
286 312 : m_pszSRSName = (pszSRSName) ? CPLStrdup(pszSRSName) : NULL;
287 312 : }
288 :
289 : /************************************************************************/
290 : /* MergeSRSName() */
291 : /************************************************************************/
292 :
293 726 : void GMLFeatureClass::MergeSRSName( const char* pszSRSName )
294 :
295 : {
296 726 : if(!m_bSRSNameConsistant)
297 0 : return;
298 :
299 726 : if( m_pszSRSName == NULL )
300 : {
301 568 : if (pszSRSName)
302 24 : m_pszSRSName = CPLStrdup(pszSRSName);
303 : }
304 : else
305 : {
306 : m_bSRSNameConsistant = pszSRSName != NULL &&
307 158 : strcmp(m_pszSRSName, pszSRSName) == 0;
308 158 : if (!m_bSRSNameConsistant)
309 : {
310 0 : CPLFree(m_pszSRSName);
311 0 : m_pszSRSName = NULL;
312 : }
313 : }
314 : }
315 :
316 : /************************************************************************/
317 : /* InitializeFromXML() */
318 : /************************************************************************/
319 :
320 300 : int GMLFeatureClass::InitializeFromXML( CPLXMLNode *psRoot )
321 :
322 : {
323 : /* -------------------------------------------------------------------- */
324 : /* Do some rudimentary checking that this is a well formed */
325 : /* node. */
326 : /* -------------------------------------------------------------------- */
327 300 : if( psRoot == NULL
328 : || psRoot->eType != CXT_Element
329 : || !EQUAL(psRoot->pszValue,"GMLFeatureClass") )
330 : {
331 : CPLError( CE_Failure, CPLE_AppDefined,
332 : "GMLFeatureClass::InitializeFromXML() called on %s node!",
333 0 : psRoot->pszValue );
334 0 : return FALSE;
335 : }
336 :
337 300 : if( CPLGetXMLValue( psRoot, "Name", NULL ) == NULL )
338 : {
339 : CPLError( CE_Failure, CPLE_AppDefined,
340 0 : "GMLFeatureClass has no <Name> element." );
341 0 : return FALSE;
342 : }
343 :
344 : /* -------------------------------------------------------------------- */
345 : /* Collect base info. */
346 : /* -------------------------------------------------------------------- */
347 300 : CPLFree( m_pszName );
348 300 : m_pszName = CPLStrdup( CPLGetXMLValue( psRoot, "Name", NULL ) );
349 300 : n_nNameLen = strlen(m_pszName);
350 :
351 300 : SetElementName( CPLGetXMLValue( psRoot, "ElementPath", m_pszName ) );
352 :
353 300 : const char *pszGPath = CPLGetXMLValue( psRoot, "GeometryElementPath", "" );
354 :
355 300 : if( strlen( pszGPath ) > 0 )
356 4 : SetGeometryElement( pszGPath );
357 :
358 300 : const char* pszGeometryType = CPLGetXMLValue( psRoot, "GeometryType", NULL );
359 300 : if( pszGeometryType != NULL )
360 : {
361 256 : int nGeomType = atoi(pszGeometryType) & (~wkb25DBit);
362 512 : if ((nGeomType >= 0 && nGeomType <= 7) || nGeomType == 100)
363 256 : SetGeometryType( atoi(pszGeometryType) );
364 : else
365 : {
366 : CPLError(CE_Warning, CPLE_AppDefined, "Unrecognised geometry type : %s",
367 0 : pszGeometryType);
368 : }
369 : }
370 :
371 300 : SetSRSName( CPLGetXMLValue( psRoot, "SRSName", NULL ) );
372 :
373 : /* -------------------------------------------------------------------- */
374 : /* Collect dataset specific info. */
375 : /* -------------------------------------------------------------------- */
376 300 : CPLXMLNode *psDSI = CPLGetXMLNode( psRoot, "DatasetSpecificInfo" );
377 300 : if( psDSI != NULL )
378 : {
379 : const char *pszValue;
380 :
381 296 : pszValue = CPLGetXMLValue( psDSI, "FeatureCount", NULL );
382 296 : if( pszValue != NULL )
383 296 : SetFeatureCount( atoi(pszValue) );
384 :
385 : // Eventually we should support XML subtrees.
386 296 : pszValue = CPLGetXMLValue( psDSI, "ExtraInfo", NULL );
387 296 : if( pszValue != NULL )
388 0 : SetExtraInfo( pszValue );
389 :
390 296 : if( CPLGetXMLValue( psDSI, "ExtentXMin", NULL ) != NULL
391 : && CPLGetXMLValue( psDSI, "ExtentXMax", NULL ) != NULL
392 : && CPLGetXMLValue( psDSI, "ExtentYMin", NULL ) != NULL
393 : && CPLGetXMLValue( psDSI, "ExtentYMax", NULL ) != NULL )
394 : {
395 : SetExtents( CPLAtof(CPLGetXMLValue( psDSI, "ExtentXMin", "0.0" )),
396 : CPLAtof(CPLGetXMLValue( psDSI, "ExtentXMax", "0.0" )),
397 : CPLAtof(CPLGetXMLValue( psDSI, "ExtentYMin", "0.0" )),
398 236 : CPLAtof(CPLGetXMLValue( psDSI, "ExtentYMax", "0.0" )) );
399 : }
400 : }
401 :
402 : /* -------------------------------------------------------------------- */
403 : /* Collect property definitions. */
404 : /* -------------------------------------------------------------------- */
405 3504 : for( CPLXMLNode *psThis = psRoot->psChild;
406 : psThis != NULL; psThis = psThis->psNext )
407 : {
408 3204 : if( EQUAL(psThis->pszValue, "PropertyDefn") )
409 : {
410 2034 : const char *pszName = CPLGetXMLValue( psThis, "Name", NULL );
411 2034 : const char *pszType = CPLGetXMLValue( psThis, "Type", "Untyped" );
412 : GMLPropertyDefn *poPDefn;
413 :
414 2034 : if( pszName == NULL )
415 : {
416 : CPLError( CE_Failure, CPLE_AppDefined,
417 : "GMLFeatureClass %s has a PropertyDefn without a <Name>..",
418 0 : m_pszName );
419 0 : return FALSE;
420 : }
421 :
422 : poPDefn = new GMLPropertyDefn(
423 2034 : pszName, CPLGetXMLValue( psThis, "ElementPath", NULL ) );
424 :
425 2034 : if( EQUAL(pszType,"Untyped") )
426 64 : poPDefn->SetType( GMLPT_Untyped );
427 1970 : else if( EQUAL(pszType,"String") )
428 : {
429 1182 : poPDefn->SetType( GMLPT_String );
430 1182 : poPDefn->SetWidth( atoi( CPLGetXMLValue( psThis, "Width", "0" ) ) );
431 : }
432 788 : else if( EQUAL(pszType,"Integer") )
433 : {
434 718 : poPDefn->SetType( GMLPT_Integer );
435 718 : poPDefn->SetWidth( atoi( CPLGetXMLValue( psThis, "Width", "0" ) ) );
436 : }
437 70 : else if( EQUAL(pszType,"Real") )
438 : {
439 22 : poPDefn->SetType( GMLPT_Real );
440 22 : poPDefn->SetWidth( atoi( CPLGetXMLValue( psThis, "Width", "0" ) ) );
441 22 : poPDefn->SetPrecision( atoi( CPLGetXMLValue( psThis, "Precision", "0" ) ) );
442 : }
443 48 : else if( EQUAL(pszType,"StringList") )
444 28 : poPDefn->SetType( GMLPT_StringList );
445 20 : else if( EQUAL(pszType,"IntegerList") )
446 14 : poPDefn->SetType( GMLPT_IntegerList );
447 6 : else if( EQUAL(pszType,"RealList") )
448 6 : poPDefn->SetType( GMLPT_RealList );
449 0 : else if( EQUAL(pszType,"Complex") )
450 0 : poPDefn->SetType( GMLPT_Complex );
451 : else
452 : {
453 : CPLError( CE_Failure, CPLE_AppDefined,
454 : "Unrecognised property type %s.",
455 0 : pszType );
456 0 : delete poPDefn;
457 0 : return FALSE;
458 : }
459 :
460 2034 : AddProperty( poPDefn );
461 : }
462 : }
463 :
464 300 : return TRUE;
465 : }
466 :
467 : /************************************************************************/
468 : /* SerializeToXML() */
469 : /************************************************************************/
470 :
471 78 : CPLXMLNode *GMLFeatureClass::SerializeToXML()
472 :
473 : {
474 : CPLXMLNode *psRoot;
475 : int iProperty;
476 :
477 : /* -------------------------------------------------------------------- */
478 : /* Set feature class and core information. */
479 : /* -------------------------------------------------------------------- */
480 78 : psRoot = CPLCreateXMLNode( NULL, CXT_Element, "GMLFeatureClass" );
481 :
482 78 : CPLCreateXMLElementAndValue( psRoot, "Name", GetName() );
483 78 : CPLCreateXMLElementAndValue( psRoot, "ElementPath", GetElementName() );
484 78 : if( GetGeometryElement() != NULL && strlen(GetGeometryElement()) > 0 )
485 : CPLCreateXMLElementAndValue( psRoot, "GeometryElementPath",
486 0 : GetGeometryElement() );
487 :
488 78 : if( GetGeometryType() != 0 /* wkbUnknown */ )
489 : {
490 : char szValue[128];
491 :
492 74 : sprintf( szValue, "%d", GetGeometryType() );
493 74 : CPLCreateXMLElementAndValue( psRoot, "GeometryType", szValue );
494 : }
495 :
496 78 : const char* pszSRSName = GetSRSName();
497 78 : if( pszSRSName )
498 : {
499 26 : CPLCreateXMLElementAndValue( psRoot, "SRSName", pszSRSName );
500 : }
501 :
502 : /* -------------------------------------------------------------------- */
503 : /* Write out dataset specific information. */
504 : /* -------------------------------------------------------------------- */
505 : CPLXMLNode *psDSI;
506 :
507 78 : if( m_bHaveExtents || m_nFeatureCount != -1 || m_pszExtraInfo != NULL )
508 : {
509 78 : psDSI = CPLCreateXMLNode( psRoot, CXT_Element, "DatasetSpecificInfo" );
510 :
511 78 : if( m_nFeatureCount != -1 )
512 : {
513 : char szValue[128];
514 :
515 78 : sprintf( szValue, "%d", m_nFeatureCount );
516 78 : CPLCreateXMLElementAndValue( psDSI, "FeatureCount", szValue );
517 : }
518 :
519 78 : if( m_bHaveExtents )
520 : {
521 : char szValue[128];
522 :
523 70 : sprintf( szValue, "%.5f", m_dfXMin );
524 70 : CPLCreateXMLElementAndValue( psDSI, "ExtentXMin", szValue );
525 :
526 70 : sprintf( szValue, "%.5f", m_dfXMax );
527 70 : CPLCreateXMLElementAndValue( psDSI, "ExtentXMax", szValue );
528 :
529 70 : sprintf( szValue, "%.5f", m_dfYMin );
530 70 : CPLCreateXMLElementAndValue( psDSI, "ExtentYMin", szValue );
531 :
532 70 : sprintf( szValue, "%.5f", m_dfYMax );
533 70 : CPLCreateXMLElementAndValue( psDSI, "ExtentYMax", szValue );
534 : }
535 :
536 78 : if( m_pszExtraInfo )
537 0 : CPLCreateXMLElementAndValue( psDSI, "ExtraInfo", m_pszExtraInfo );
538 : }
539 :
540 : /* -------------------------------------------------------------------- */
541 : /* emit property information. */
542 : /* -------------------------------------------------------------------- */
543 348 : for( iProperty = 0; iProperty < GetPropertyCount(); iProperty++ )
544 : {
545 270 : GMLPropertyDefn *poPDefn = GetProperty( iProperty );
546 : CPLXMLNode *psPDefnNode;
547 270 : const char *pszTypeName = "Unknown";
548 :
549 270 : psPDefnNode = CPLCreateXMLNode( psRoot, CXT_Element, "PropertyDefn" );
550 : CPLCreateXMLElementAndValue( psPDefnNode, "Name",
551 270 : poPDefn->GetName() );
552 : CPLCreateXMLElementAndValue( psPDefnNode, "ElementPath",
553 270 : poPDefn->GetSrcElement() );
554 270 : switch( poPDefn->GetType() )
555 : {
556 : case GMLPT_Untyped:
557 4 : pszTypeName = "Untyped";
558 4 : break;
559 :
560 : case GMLPT_String:
561 118 : pszTypeName = "String";
562 118 : break;
563 :
564 : case GMLPT_Integer:
565 110 : pszTypeName = "Integer";
566 110 : break;
567 :
568 : case GMLPT_Real:
569 28 : pszTypeName = "Real";
570 28 : break;
571 :
572 : case GMLPT_Complex:
573 0 : pszTypeName = "Complex";
574 0 : break;
575 :
576 : case GMLPT_IntegerList:
577 0 : pszTypeName = "IntegerList";
578 0 : break;
579 :
580 : case GMLPT_RealList:
581 0 : pszTypeName = "RealList";
582 0 : break;
583 :
584 : case GMLPT_StringList:
585 10 : pszTypeName = "StringList";
586 : break;
587 : }
588 270 : CPLCreateXMLElementAndValue( psPDefnNode, "Type", pszTypeName );
589 :
590 270 : if( EQUAL(pszTypeName,"String") )
591 : {
592 : char szMaxLength[48];
593 118 : sprintf(szMaxLength, "%d", poPDefn->GetWidth());
594 118 : CPLCreateXMLElementAndValue ( psPDefnNode, "Width", szMaxLength );
595 : }
596 270 : if( poPDefn->GetWidth() > 0 && EQUAL(pszTypeName,"Integer") )
597 : {
598 : char szLength[48];
599 0 : sprintf(szLength, "%d", poPDefn->GetWidth());
600 0 : CPLCreateXMLElementAndValue ( psPDefnNode, "Width", szLength );
601 : }
602 270 : if( poPDefn->GetWidth() > 0 && EQUAL(pszTypeName,"Real") )
603 : {
604 : char szLength[48];
605 0 : sprintf(szLength, "%d", poPDefn->GetWidth());
606 0 : CPLCreateXMLElementAndValue ( psPDefnNode, "Width", szLength );
607 : char szPrecision[48];
608 0 : sprintf(szPrecision, "%d", poPDefn->GetPrecision());
609 0 : CPLCreateXMLElementAndValue ( psPDefnNode, "Precision", szPrecision );
610 : }
611 : }
612 :
613 78 : return psRoot;
614 : }
615 :
|