1 : /******************************************************************************
2 : * $Id: ogrgmldatasource.cpp 20033 2010-07-11 19:06:17Z rouault $
3 : *
4 : * Project: OGR
5 : * Purpose: Implements OGRGMLDataSource class.
6 : * Author: Frank Warmerdam, warmerdam@pobox.com
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2002, Frank Warmerdam <warmerdam@pobox.com>
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
22 : * OR 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 "ogr_gml.h"
31 : #include "cpl_conv.h"
32 : #include "cpl_string.h"
33 :
34 : CPL_CVSID("$Id: ogrgmldatasource.cpp 20033 2010-07-11 19:06:17Z rouault $");
35 :
36 : /************************************************************************/
37 : /* OGRGMLDataSource() */
38 : /************************************************************************/
39 :
40 119 : OGRGMLDataSource::OGRGMLDataSource()
41 :
42 : {
43 119 : pszName = NULL;
44 119 : papoLayers = NULL;
45 119 : nLayers = 0;
46 :
47 119 : poReader = NULL;
48 119 : fpOutput = NULL;
49 119 : bFpOutputIsStdout = FALSE;
50 :
51 119 : papszCreateOptions = NULL;
52 119 : bOutIsTempFile = FALSE;
53 119 : }
54 :
55 : /************************************************************************/
56 : /* ~OGRGMLDataSource() */
57 : /************************************************************************/
58 :
59 119 : OGRGMLDataSource::~OGRGMLDataSource()
60 :
61 : {
62 :
63 119 : if( fpOutput != NULL )
64 : {
65 : PrintLine( fpOutput, "%s",
66 2 : "</ogr:FeatureCollection>" );
67 :
68 2 : InsertHeader();
69 :
70 2 : if( nBoundedByLocation != -1
71 : && sBoundingRect.IsInit()
72 : && VSIFSeekL( fpOutput, nBoundedByLocation, SEEK_SET ) == 0 )
73 : {
74 0 : PrintLine( fpOutput, " <gml:boundedBy>" );
75 0 : PrintLine( fpOutput, " <gml:Box>" );
76 : PrintLine( fpOutput,
77 : " <gml:coord><gml:X>%.16g</gml:X>"
78 : "<gml:Y>%.16g</gml:Y></gml:coord>",
79 0 : sBoundingRect.MinX, sBoundingRect.MinY );
80 : PrintLine( fpOutput,
81 : " <gml:coord><gml:X>%.16g</gml:X>"
82 : "<gml:Y>%.16g</gml:Y></gml:coord>",
83 0 : sBoundingRect.MaxX, sBoundingRect.MaxY );
84 0 : PrintLine( fpOutput, " </gml:Box>" );
85 0 : PrintLine( fpOutput, " </gml:boundedBy>" );
86 : }
87 :
88 2 : VSIFCloseL( fpOutput );
89 : }
90 :
91 119 : CSLDestroy( papszCreateOptions );
92 119 : CPLFree( pszName );
93 :
94 138 : for( int i = 0; i < nLayers; i++ )
95 19 : delete papoLayers[i];
96 :
97 119 : CPLFree( papoLayers );
98 :
99 119 : if( poReader )
100 : {
101 20 : if (bOutIsTempFile)
102 1 : VSIUnlink(poReader->GetSourceFileName());
103 20 : delete poReader;
104 : }
105 119 : }
106 :
107 : /************************************************************************/
108 : /* Open() */
109 : /************************************************************************/
110 :
111 117 : int OGRGMLDataSource::Open( const char * pszNewName, int bTestOpen )
112 :
113 : {
114 : FILE *fp;
115 : char szHeader[1000];
116 :
117 : /* -------------------------------------------------------------------- */
118 : /* Open the source file. */
119 : /* -------------------------------------------------------------------- */
120 117 : fp = VSIFOpenL( pszNewName, "r" );
121 117 : if( fp == NULL )
122 : {
123 7 : if( !bTestOpen )
124 : CPLError( CE_Failure, CPLE_OpenFailed,
125 : "Failed to open GML file `%s'.",
126 0 : pszNewName );
127 :
128 7 : return FALSE;
129 : }
130 :
131 : /* -------------------------------------------------------------------- */
132 : /* If we aren't sure it is GML, load a header chunk and check */
133 : /* for signs it is GML */
134 : /* -------------------------------------------------------------------- */
135 110 : if( bTestOpen )
136 : {
137 110 : size_t nRead = VSIFReadL( szHeader, 1, sizeof(szHeader), fp );
138 110 : if (nRead <= 0)
139 : {
140 1 : VSIFCloseL( fp );
141 1 : return FALSE;
142 : }
143 109 : szHeader[MIN(nRead, sizeof(szHeader))-1] = '\0';
144 :
145 : /* -------------------------------------------------------------------- */
146 : /* Check for a UTF-8 BOM and skip if found */
147 : /* */
148 : /* TODO: BOM is variable-lenght parameter and depends on encoding. */
149 : /* Add BOM detection for other encodings. */
150 : /* -------------------------------------------------------------------- */
151 :
152 : // Used to skip to actual beginning of XML data
153 109 : char* szPtr = szHeader;
154 :
155 109 : if( ( (unsigned char)szHeader[0] == 0xEF )
156 : && ( (unsigned char)szHeader[1] == 0xBB )
157 : && ( (unsigned char)szHeader[2] == 0xBF) )
158 : {
159 1 : szPtr += 3;
160 : }
161 :
162 : /* -------------------------------------------------------------------- */
163 : /* Here, we expect the opening chevrons of GML tree root element */
164 : /* -------------------------------------------------------------------- */
165 109 : if( szPtr[0] != '<'
166 : || strstr(szPtr,"opengis.net/gml") == NULL )
167 : {
168 89 : VSIFCloseL( fp );
169 89 : return FALSE;
170 : }
171 : }
172 :
173 : /* -------------------------------------------------------------------- */
174 : /* We assume now that it is GML. Close and instantiate a */
175 : /* GMLReader on it. */
176 : /* -------------------------------------------------------------------- */
177 20 : VSIFCloseL( fp );
178 :
179 20 : poReader = CreateGMLReader();
180 20 : if( poReader == NULL )
181 : {
182 : CPLError( CE_Failure, CPLE_AppDefined,
183 : "File %s appears to be GML but the GML reader can't\n"
184 : "be instantiated, likely because Xerces or Expat support wasn't\n"
185 : "configured in.",
186 0 : pszNewName );
187 0 : return FALSE;
188 : }
189 :
190 20 : poReader->SetSourceFile( pszNewName );
191 :
192 : /* -------------------------------------------------------------------- */
193 : /* Resolve the xlinks in the source file and save it with the */
194 : /* extension ".resolved.gml". The source file will to set to that. */
195 : /* -------------------------------------------------------------------- */
196 :
197 20 : char *pszXlinkResolvedFilename = NULL;
198 20 : const char *pszOption = CPLGetConfigOption("GML_SAVE_RESOLVED_TO", NULL);
199 20 : int bResolve = TRUE;
200 20 : if( pszOption != NULL )
201 : {
202 0 : if( EQUALN( pszOption, "SAME", 4 ) )
203 : {
204 : // "SAME" will overwrite the existing gml file
205 0 : pszXlinkResolvedFilename = CPLStrdup( pszNewName );
206 : }
207 0 : else if( ( CPLStrnlen( pszOption, 5 ) >= 5 ) &&
208 : EQUALN( pszOption - 4 + strlen( pszOption ), ".gml", 4 ) )
209 : {
210 : // Any string ending with ".gml" will try and write to it
211 0 : pszXlinkResolvedFilename = CPLStrdup( pszNewName );
212 : }
213 : }
214 : else
215 : {
216 : // Default action would be to use a file with the extension
217 : // changed to resolved.gml
218 : pszXlinkResolvedFilename = CPLStrdup(
219 20 : CPLResetExtension( pszNewName, "resolved.gml" ) );
220 :
221 : // Check if the file already exists.
222 : VSIStatBufL sResStatBuf, sGMLStatBuf;
223 20 : if( VSIStatL( pszXlinkResolvedFilename, &sResStatBuf ) == 0 )
224 : {
225 0 : VSIStatL( pszNewName, &sGMLStatBuf );
226 0 : if( sGMLStatBuf.st_mtime > sResStatBuf.st_mtime )
227 : {
228 : CPLDebug( "GML",
229 : "Found %s but ignoring because it appears\n"
230 : "be older than the associated GML file.",
231 0 : pszXlinkResolvedFilename );
232 0 : bResolve = FALSE;
233 : }
234 : else
235 : {
236 0 : poReader->SetSourceFile( pszXlinkResolvedFilename );
237 0 : bResolve = FALSE;
238 : }
239 : }
240 : }
241 :
242 : const char *pszSkipOption = CPLGetConfigOption( "GML_SKIP_RESOLVE_ELEMS",
243 20 : "");
244 20 : char **papszSkip = NULL;
245 20 : if( EQUAL( pszSkipOption, "ALL" ) )
246 0 : bResolve = FALSE;
247 : else
248 : papszSkip = CSLTokenizeString2( pszSkipOption, ",",
249 : CSLT_STRIPLEADSPACES |
250 20 : CSLT_STRIPENDSPACES );
251 :
252 20 : if( bResolve )
253 20 : poReader->ResolveXlinks( pszXlinkResolvedFilename, &bOutIsTempFile, papszSkip );
254 :
255 20 : CPLFree( pszXlinkResolvedFilename );
256 20 : CSLDestroy( papszSkip );
257 :
258 20 : pszName = CPLStrdup( pszNewName );
259 :
260 : /* -------------------------------------------------------------------- */
261 : /* Can we find a GML Feature Schema (.gfs) for the input file? */
262 : /* -------------------------------------------------------------------- */
263 : const char *pszGFSFilename;
264 : VSIStatBufL sGFSStatBuf, sGMLStatBuf;
265 20 : int bHaveSchema = FALSE;
266 :
267 20 : pszGFSFilename = CPLResetExtension( pszNewName, "gfs" );
268 20 : if( VSIStatL( pszGFSFilename, &sGFSStatBuf ) == 0 )
269 : {
270 5 : VSIStatL( pszNewName, &sGMLStatBuf );
271 :
272 5 : if( sGMLStatBuf.st_mtime > sGFSStatBuf.st_mtime )
273 : {
274 : CPLDebug( "GML",
275 : "Found %s but ignoring because it appears\n"
276 : "be older than the associated GML file.",
277 0 : pszGFSFilename );
278 : }
279 : else
280 : {
281 5 : bHaveSchema = poReader->LoadClasses( pszGFSFilename );
282 : }
283 : }
284 :
285 : /* -------------------------------------------------------------------- */
286 : /* Can we find an xsd which might conform to tbe GML3 Level 0 */
287 : /* profile? We really ought to look for it based on the rules */
288 : /* schemaLocation in the GML feature collection but for now we */
289 : /* just hopes it is in the same director with the same name. */
290 : /* -------------------------------------------------------------------- */
291 : const char *pszXSDFilename;
292 :
293 20 : if( !bHaveSchema )
294 : {
295 15 : pszXSDFilename = CPLResetExtension( pszNewName, "xsd" );
296 15 : if( VSIStatL( pszXSDFilename, &sGMLStatBuf ) == 0 )
297 : {
298 3 : bHaveSchema = poReader->ParseXSD( pszXSDFilename );
299 : }
300 : }
301 :
302 : /* -------------------------------------------------------------------- */
303 : /* Force a first pass to establish the schema. Eventually we */
304 : /* will have mechanisms for remembering the schema and related */
305 : /* information. */
306 : /* -------------------------------------------------------------------- */
307 20 : if( !bHaveSchema && !poReader->PrescanForSchema( TRUE ) )
308 : {
309 : // we assume an errors have been reported.
310 4 : return FALSE;
311 : }
312 :
313 : /* -------------------------------------------------------------------- */
314 : /* Save the schema file if possible. Don't make a fuss if we */
315 : /* can't ... could be read-only directory or something. */
316 : /* -------------------------------------------------------------------- */
317 16 : if( !bHaveSchema && !poReader->HasStoppedParsing() &&
318 : !EQUALN(pszNewName, "/vsitar/", strlen("/vsitar/")) &&
319 : !EQUALN(pszNewName, "/vsigzip/", strlen("/vsigzip/")) &&
320 : !EQUALN(pszNewName, "/vsizip/", strlen("/vsizip/")))
321 : {
322 8 : FILE *fp = NULL;
323 :
324 8 : pszGFSFilename = CPLResetExtension( pszNewName, "gfs" );
325 8 : if( VSIStatL( pszGFSFilename, &sGFSStatBuf ) != 0
326 : && (fp = VSIFOpenL( pszGFSFilename, "wt" )) != NULL )
327 : {
328 8 : VSIFCloseL( fp );
329 8 : poReader->SaveClasses( pszGFSFilename );
330 : }
331 : else
332 : {
333 : CPLDebug("GML",
334 : "Not saving %s files already exists or can't be created.",
335 0 : pszGFSFilename );
336 : }
337 : }
338 :
339 : /* -------------------------------------------------------------------- */
340 : /* Translate the GMLFeatureClasses into layers. */
341 : /* -------------------------------------------------------------------- */
342 : papoLayers = (OGRGMLLayer **)
343 16 : CPLCalloc( sizeof(OGRGMLLayer *), poReader->GetClassCount());
344 16 : nLayers = 0;
345 :
346 49 : while( nLayers < poReader->GetClassCount() )
347 : {
348 17 : papoLayers[nLayers] = TranslateGMLSchema(poReader->GetClass(nLayers));
349 17 : nLayers++;
350 : }
351 :
352 :
353 :
354 16 : return TRUE;
355 : }
356 :
357 : /************************************************************************/
358 : /* TranslateGMLSchema() */
359 : /************************************************************************/
360 :
361 17 : OGRGMLLayer *OGRGMLDataSource::TranslateGMLSchema( GMLFeatureClass *poClass )
362 :
363 : {
364 : OGRGMLLayer *poLayer;
365 : OGRwkbGeometryType eGType
366 17 : = (OGRwkbGeometryType) poClass->GetGeometryType();
367 :
368 17 : if( poClass->GetFeatureCount() == 0 )
369 0 : eGType = wkbUnknown;
370 :
371 : /* -------------------------------------------------------------------- */
372 : /* Create an empty layer. */
373 : /* -------------------------------------------------------------------- */
374 : poLayer = new OGRGMLLayer( poClass->GetName(), NULL, FALSE,
375 17 : eGType, this );
376 :
377 : /* -------------------------------------------------------------------- */
378 : /* Added attributes (properties). */
379 : /* -------------------------------------------------------------------- */
380 74 : for( int iField = 0; iField < poClass->GetPropertyCount(); iField++ )
381 : {
382 57 : GMLPropertyDefn *poProperty = poClass->GetProperty( iField );
383 : OGRFieldType eFType;
384 :
385 57 : if( poProperty->GetType() == GMLPT_Untyped )
386 0 : eFType = OFTString;
387 57 : else if( poProperty->GetType() == GMLPT_String )
388 24 : eFType = OFTString;
389 33 : else if( poProperty->GetType() == GMLPT_Integer )
390 25 : eFType = OFTInteger;
391 8 : else if( poProperty->GetType() == GMLPT_Real )
392 8 : eFType = OFTReal;
393 0 : else if( poProperty->GetType() == GMLPT_StringList )
394 0 : eFType = OFTStringList;
395 0 : else if( poProperty->GetType() == GMLPT_IntegerList )
396 0 : eFType = OFTIntegerList;
397 0 : else if( poProperty->GetType() == GMLPT_RealList )
398 0 : eFType = OFTRealList;
399 : else
400 0 : eFType = OFTString;
401 :
402 57 : OGRFieldDefn oField( poProperty->GetName(), eFType );
403 57 : if ( EQUALN(oField.GetNameRef(), "ogr:", 4) )
404 0 : oField.SetName(poProperty->GetName()+4);
405 57 : if( poProperty->GetWidth() > 0 )
406 22 : oField.SetWidth( poProperty->GetWidth() );
407 57 : if( poProperty->GetPrecision() > 0 )
408 2 : oField.SetPrecision( poProperty->GetPrecision() );
409 :
410 57 : poLayer->GetLayerDefn()->AddFieldDefn( &oField );
411 : }
412 :
413 17 : return poLayer;
414 : }
415 :
416 : /************************************************************************/
417 : /* Create() */
418 : /************************************************************************/
419 :
420 : int OGRGMLDataSource::Create( const char *pszFilename,
421 2 : char **papszOptions )
422 :
423 : {
424 2 : if( fpOutput != NULL || poReader != NULL )
425 : {
426 0 : CPLAssert( FALSE );
427 0 : return FALSE;
428 : }
429 :
430 : /* -------------------------------------------------------------------- */
431 : /* Create the output file. */
432 : /* -------------------------------------------------------------------- */
433 2 : pszName = CPLStrdup( pszFilename );
434 :
435 2 : if( EQUAL(pszFilename,"stdout") || EQUAL(pszFilename,"/vsistdout/"))
436 : {
437 0 : fpOutput = VSIFOpenL("/vsistdout/", "wb");
438 0 : bFpOutputIsStdout = TRUE;
439 : }
440 : else
441 2 : fpOutput = VSIFOpenL( pszFilename, "wb+" );
442 2 : if( fpOutput == NULL )
443 : {
444 : CPLError( CE_Failure, CPLE_OpenFailed,
445 : "Failed to create GML file %s.",
446 0 : pszFilename );
447 0 : return FALSE;
448 : }
449 :
450 : /* -------------------------------------------------------------------- */
451 : /* Write out "standard" header. */
452 : /* -------------------------------------------------------------------- */
453 : PrintLine( fpOutput, "%s",
454 2 : "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" );
455 :
456 2 : nSchemaInsertLocation = VSIFTellL( fpOutput );
457 :
458 : PrintLine( fpOutput, "%s",
459 2 : "<ogr:FeatureCollection" );
460 :
461 : /* -------------------------------------------------------------------- */
462 : /* Write out schema info if provided in creation options. */
463 : /* -------------------------------------------------------------------- */
464 2 : const char *pszSchemaURI = CSLFetchNameValue(papszOptions,"XSISCHEMAURI");
465 2 : const char *pszSchemaOpt = CSLFetchNameValue( papszOptions, "XSISCHEMA" );
466 :
467 2 : if( pszSchemaURI != NULL )
468 : {
469 : PrintLine( fpOutput,
470 0 : " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
471 : PrintLine( fpOutput,
472 : " xsi:schemaLocation=\"%s\"",
473 0 : CSLFetchNameValue( papszOptions, "XSISCHEMAURI" ) );
474 : }
475 2 : else if( pszSchemaOpt == NULL || EQUAL(pszSchemaOpt,"EXTERNAL") )
476 : {
477 2 : char *pszBasename = CPLStrdup(CPLGetBasename( pszName ));
478 :
479 : PrintLine( fpOutput,
480 2 : " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
481 : PrintLine( fpOutput,
482 : " xsi:schemaLocation=\"http://ogr.maptools.org/ %s\"",
483 2 : CPLResetExtension( pszBasename, "xsd" ) );
484 2 : CPLFree( pszBasename );
485 : }
486 :
487 : PrintLine( fpOutput, "%s",
488 2 : " xmlns:ogr=\"http://ogr.maptools.org/\"" );
489 : PrintLine( fpOutput, "%s",
490 2 : " xmlns:gml=\"http://www.opengis.net/gml\">" );
491 :
492 : /* -------------------------------------------------------------------- */
493 : /* Should we initialize an area to place the boundedBy element? */
494 : /* We will need to seek back to fill it in. */
495 : /* -------------------------------------------------------------------- */
496 2 : if( CSLFetchBoolean( papszOptions, "BOUNDEDBY", TRUE ) )
497 : {
498 2 : nBoundedByLocation = VSIFTellL( fpOutput );
499 :
500 2 : if( nBoundedByLocation != -1 )
501 2 : PrintLine( fpOutput, "%280s", "" );
502 : }
503 : else
504 0 : nBoundedByLocation = -1;
505 :
506 2 : return TRUE;
507 : }
508 :
509 : /************************************************************************/
510 : /* CreateLayer() */
511 : /************************************************************************/
512 :
513 : OGRLayer *
514 : OGRGMLDataSource::CreateLayer( const char * pszLayerName,
515 : OGRSpatialReference *poSRS,
516 : OGRwkbGeometryType eType,
517 2 : char ** papszOptions )
518 :
519 : {
520 : /* -------------------------------------------------------------------- */
521 : /* Verify we are in update mode. */
522 : /* -------------------------------------------------------------------- */
523 2 : if( fpOutput == NULL )
524 : {
525 : CPLError( CE_Failure, CPLE_NoWriteAccess,
526 : "Data source %s opened for read access.\n"
527 : "New layer %s cannot be created.\n",
528 0 : pszName, pszLayerName );
529 :
530 0 : return NULL;
531 : }
532 :
533 : /* -------------------------------------------------------------------- */
534 : /* Ensure name is safe as an element name. */
535 : /* -------------------------------------------------------------------- */
536 2 : char *pszCleanLayerName = CPLStrdup( pszLayerName );
537 :
538 2 : CPLCleanXMLElementName( pszCleanLayerName );
539 2 : if( strcmp(pszCleanLayerName,pszLayerName) != 0 )
540 : {
541 : CPLError( CE_Warning, CPLE_AppDefined,
542 : "Layer name '%s' adjusted to '%s' for XML validity.",
543 0 : pszLayerName, pszCleanLayerName );
544 : }
545 :
546 : /* -------------------------------------------------------------------- */
547 : /* Create the layer object. */
548 : /* -------------------------------------------------------------------- */
549 : OGRGMLLayer *poLayer;
550 :
551 2 : poLayer = new OGRGMLLayer( pszCleanLayerName, poSRS, TRUE, eType, this );
552 :
553 2 : CPLFree( pszCleanLayerName );
554 :
555 : /* -------------------------------------------------------------------- */
556 : /* Add layer to data source layer list. */
557 : /* -------------------------------------------------------------------- */
558 : papoLayers = (OGRGMLLayer **)
559 2 : CPLRealloc( papoLayers, sizeof(OGRGMLLayer *) * (nLayers+1) );
560 :
561 2 : papoLayers[nLayers++] = poLayer;
562 :
563 2 : return poLayer;
564 : }
565 :
566 : /************************************************************************/
567 : /* TestCapability() */
568 : /************************************************************************/
569 :
570 0 : int OGRGMLDataSource::TestCapability( const char * pszCap )
571 :
572 : {
573 0 : if( EQUAL(pszCap,ODsCCreateLayer) )
574 0 : return TRUE;
575 : else
576 0 : return FALSE;
577 : }
578 :
579 : /************************************************************************/
580 : /* GetLayer() */
581 : /************************************************************************/
582 :
583 17 : OGRLayer *OGRGMLDataSource::GetLayer( int iLayer )
584 :
585 : {
586 17 : if( iLayer < 0 || iLayer >= nLayers )
587 0 : return NULL;
588 : else
589 17 : return papoLayers[iLayer];
590 : }
591 :
592 : /************************************************************************/
593 : /* GrowExtents() */
594 : /************************************************************************/
595 :
596 0 : void OGRGMLDataSource::GrowExtents( OGREnvelope *psGeomBounds )
597 :
598 : {
599 0 : sBoundingRect.Merge( *psGeomBounds );
600 0 : }
601 :
602 : /************************************************************************/
603 : /* InsertHeader() */
604 : /* */
605 : /* This method is used to update boundedby info for a */
606 : /* dataset, and insert schema descriptions depending on */
607 : /* selection options in effect. */
608 : /************************************************************************/
609 :
610 2 : void OGRGMLDataSource::InsertHeader()
611 :
612 : {
613 : FILE *fpSchema;
614 2 : int nSchemaStart = 0;
615 :
616 2 : if( fpOutput == NULL || bFpOutputIsStdout )
617 0 : return;
618 :
619 : /* -------------------------------------------------------------------- */
620 : /* Do we want to write the schema within the GML instance doc */
621 : /* or to a separate file? For now we only support external. */
622 : /* -------------------------------------------------------------------- */
623 : const char *pszSchemaURI = CSLFetchNameValue(papszCreateOptions,
624 2 : "XSISCHEMAURI");
625 : const char *pszSchemaOpt = CSLFetchNameValue( papszCreateOptions,
626 2 : "XSISCHEMA" );
627 :
628 2 : if( pszSchemaURI != NULL )
629 0 : return;
630 :
631 4 : if( pszSchemaOpt == NULL || EQUAL(pszSchemaOpt,"EXTERNAL") )
632 : {
633 2 : const char *pszXSDFilename = CPLResetExtension( pszName, "xsd" );
634 :
635 2 : fpSchema = VSIFOpenL( pszXSDFilename, "wt" );
636 2 : if( fpSchema == NULL )
637 : {
638 : CPLError( CE_Failure, CPLE_OpenFailed,
639 : "Failed to open file %.500s for schema output.",
640 0 : pszXSDFilename );
641 0 : return;
642 : }
643 2 : PrintLine( fpSchema, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" );
644 : }
645 0 : else if( EQUAL(pszSchemaOpt,"INTERNAL") )
646 : {
647 0 : nSchemaStart = VSIFTellL( fpOutput );
648 0 : fpSchema = fpOutput;
649 : }
650 : else
651 0 : return;
652 :
653 : /* ==================================================================== */
654 : /* Write the schema section at the end of the file. Once */
655 : /* complete, we will read it back in, and then move the whole */
656 : /* file "down" enough to insert the schema at the beginning. */
657 : /* ==================================================================== */
658 :
659 : /* -------------------------------------------------------------------- */
660 : /* Emit the start of the schema section. */
661 : /* -------------------------------------------------------------------- */
662 2 : const char *pszTargetNameSpace = "http://ogr.maptools.org/";
663 2 : const char *pszPrefix = "ogr";
664 :
665 : PrintLine( fpSchema,
666 : "<xs:schema targetNamespace=\"%s\" xmlns:%s=\"%s\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:gml=\"http://www.opengis.net/gml\" elementFormDefault=\"qualified\" version=\"1.0\">",
667 2 : pszTargetNameSpace, pszPrefix, pszTargetNameSpace );
668 :
669 : PrintLine( fpSchema,
670 2 : "<xs:import namespace=\"http://www.opengis.net/gml\" schemaLocation=\"http://schemas.opengeospatial.net/gml/2.1.2/feature.xsd\"/>" );
671 :
672 : /* -------------------------------------------------------------------- */
673 : /* Define the FeatureCollection */
674 : /* -------------------------------------------------------------------- */
675 : PrintLine( fpSchema,
676 : "<xs:element name=\"FeatureCollection\" type=\"%s:FeatureCollectionType\" substitutionGroup=\"gml:_FeatureCollection\"/>",
677 2 : pszPrefix );
678 :
679 2 : PrintLine( fpSchema, "<xs:complexType name=\"FeatureCollectionType\">");
680 2 : PrintLine( fpSchema, " <xs:complexContent>" );
681 2 : PrintLine( fpSchema, " <xs:extension base=\"gml:AbstractFeatureCollectionType\">" );
682 2 : PrintLine( fpSchema, " <xs:attribute name=\"lockId\" type=\"xs:string\" use=\"optional\"/>" );
683 2 : PrintLine( fpSchema, " <xs:attribute name=\"scope\" type=\"xs:string\" use=\"optional\"/>" );
684 2 : PrintLine( fpSchema, " </xs:extension>" );
685 2 : PrintLine( fpSchema, " </xs:complexContent>" );
686 2 : PrintLine( fpSchema, "</xs:complexType>" );
687 :
688 : /* ==================================================================== */
689 : /* Define the schema for each layer. */
690 : /* ==================================================================== */
691 : int iLayer;
692 :
693 4 : for( iLayer = 0; iLayer < GetLayerCount(); iLayer++ )
694 : {
695 2 : OGRFeatureDefn *poFDefn = GetLayer(iLayer)->GetLayerDefn();
696 :
697 : /* -------------------------------------------------------------------- */
698 : /* Emit initial stuff for a feature type. */
699 : /* -------------------------------------------------------------------- */
700 : PrintLine(
701 : fpSchema,
702 : "<xs:element name=\"%s\" type=\"%s:%s_Type\" substitutionGroup=\"gml:_Feature\"/>",
703 2 : poFDefn->GetName(), pszPrefix, poFDefn->GetName() );
704 :
705 2 : PrintLine( fpSchema, "<xs:complexType name=\"%s_Type\">", poFDefn->GetName());
706 2 : PrintLine( fpSchema, " <xs:complexContent>");
707 2 : PrintLine( fpSchema, " <xs:extension base=\"gml:AbstractFeatureType\">");
708 2 : PrintLine( fpSchema, " <xs:sequence>");
709 :
710 : /* -------------------------------------------------------------------- */
711 : /* Define the geometry attribute. For now I always use the */
712 : /* generic geometry type, but eventually we should express */
713 : /* particulars if available. */
714 : /* -------------------------------------------------------------------- */
715 : PrintLine( fpSchema,
716 2 : " <xs:element name=\"geometryProperty\" type=\"gml:GeometryPropertyType\" nillable=\"true\" minOccurs=\"1\" maxOccurs=\"1\"/>" );
717 :
718 : /* -------------------------------------------------------------------- */
719 : /* Emit each of the attributes. */
720 : /* -------------------------------------------------------------------- */
721 8 : for( int iField = 0; iField < poFDefn->GetFieldCount(); iField++ )
722 : {
723 6 : OGRFieldDefn *poFieldDefn = poFDefn->GetFieldDefn(iField);
724 :
725 6 : if( poFieldDefn->GetType() == OFTInteger )
726 : {
727 : int nWidth;
728 :
729 1 : if( poFieldDefn->GetWidth() > 0 )
730 1 : nWidth = poFieldDefn->GetWidth();
731 : else
732 0 : nWidth = 16;
733 :
734 1 : PrintLine( fpSchema, " <xs:element name=\"%s\" nillable=\"true\" minOccurs=\"0\" maxOccurs=\"1\">", poFieldDefn->GetNameRef());
735 1 : PrintLine( fpSchema, " <xs:simpleType>");
736 1 : PrintLine( fpSchema, " <xs:restriction base=\"xs:integer\">");
737 1 : PrintLine( fpSchema, " <xs:totalDigits value=\"%d\"/>", nWidth);
738 1 : PrintLine( fpSchema, " </xs:restriction>");
739 1 : PrintLine( fpSchema, " </xs:simpleType>");
740 1 : PrintLine( fpSchema, " </xs:element>");
741 : }
742 5 : else if( poFieldDefn->GetType() == OFTReal )
743 : {
744 : int nWidth, nDecimals;
745 :
746 2 : if( poFieldDefn->GetPrecision() == 0 )
747 1 : nDecimals = 0;
748 : else
749 1 : nDecimals = poFieldDefn->GetPrecision();
750 :
751 2 : if( poFieldDefn->GetWidth() > 0 )
752 1 : nWidth = poFieldDefn->GetWidth();
753 : else
754 1 : nWidth = 33;
755 :
756 2 : PrintLine( fpSchema, " <xs:element name=\"%s\" nillable=\"true\" minOccurs=\"0\" maxOccurs=\"1\">", poFieldDefn->GetNameRef());
757 2 : PrintLine( fpSchema, " <xs:simpleType>");
758 2 : PrintLine( fpSchema, " <xs:restriction base=\"xs:decimal\">");
759 2 : PrintLine( fpSchema, " <xs:totalDigits value=\"%d\"/>", nWidth);
760 2 : PrintLine( fpSchema, " <xs:fractionDigits value=\"%d\"/>", nDecimals);
761 2 : PrintLine( fpSchema, " </xs:restriction>");
762 2 : PrintLine( fpSchema, " </xs:simpleType>");
763 2 : PrintLine( fpSchema, " </xs:element>");
764 : }
765 3 : else if( poFieldDefn->GetType() == OFTString )
766 : {
767 : char szMaxLength[48];
768 :
769 2 : if( poFieldDefn->GetWidth() == 0 )
770 1 : sprintf( szMaxLength, "unbounded" );
771 : else
772 1 : sprintf( szMaxLength, "%d", poFieldDefn->GetWidth() );
773 :
774 2 : PrintLine( fpSchema, " <xs:element name=\"%s\" nillable=\"true\" minOccurs=\"0\" maxOccurs=\"1\">", poFieldDefn->GetNameRef());
775 2 : PrintLine( fpSchema, " <xs:simpleType>");
776 2 : PrintLine( fpSchema, " <xs:restriction base=\"xs:string\">");
777 2 : PrintLine( fpSchema, " <xs:maxLength value=\"%s\"/>", szMaxLength);
778 2 : PrintLine( fpSchema, " </xs:restriction>");
779 2 : PrintLine( fpSchema, " </xs:simpleType>");
780 2 : PrintLine( fpSchema, " </xs:element>");
781 : }
782 1 : else if( poFieldDefn->GetType() == OFTDate || poFieldDefn->GetType() == OFTDateTime )
783 : {
784 1 : PrintLine( fpSchema, " <xs:element name=\"%s\" nillable=\"true\" minOccurs=\"0\" maxOccurs=\"1\">", poFieldDefn->GetNameRef());
785 1 : PrintLine( fpSchema, " <xs:simpleType>");
786 1 : PrintLine( fpSchema, " <xs:restriction base=\"xs:string\">");
787 1 : PrintLine( fpSchema, " <xs:maxLength value=\"unbounded\"/>");
788 1 : PrintLine( fpSchema, " </xs:restriction>");
789 1 : PrintLine( fpSchema, " </xs:simpleType>");
790 1 : PrintLine( fpSchema, " </xs:element>");
791 : }
792 : else
793 : {
794 : /* TODO */
795 : }
796 : } /* next field */
797 :
798 : /* -------------------------------------------------------------------- */
799 : /* Finish off feature type. */
800 : /* -------------------------------------------------------------------- */
801 2 : PrintLine( fpSchema, " </xs:sequence>");
802 2 : PrintLine( fpSchema, " </xs:extension>");
803 2 : PrintLine( fpSchema, " </xs:complexContent>");
804 2 : PrintLine( fpSchema, "</xs:complexType>" );
805 : } /* next layer */
806 :
807 2 : PrintLine( fpSchema, "</xs:schema>" );
808 :
809 : /* ==================================================================== */
810 : /* Move schema to the start of the file. */
811 : /* ==================================================================== */
812 2 : if( bFpOutputIsStdout )
813 : {
814 : /* -------------------------------------------------------------------- */
815 : /* Read the schema into memory. */
816 : /* -------------------------------------------------------------------- */
817 0 : int nSchemaSize = VSIFTellL( fpOutput ) - nSchemaStart;
818 0 : char *pszSchema = (char *) CPLMalloc(nSchemaSize+1);
819 :
820 0 : VSIFSeekL( fpOutput, nSchemaStart, SEEK_SET );
821 :
822 0 : VSIFReadL( pszSchema, 1, nSchemaSize, fpOutput );
823 0 : pszSchema[nSchemaSize] = '\0';
824 :
825 : /* -------------------------------------------------------------------- */
826 : /* Move file data down by "schema size" bytes from after <?xml> */
827 : /* header so we have room insert the schema. Move in pretty */
828 : /* big chunks. */
829 : /* -------------------------------------------------------------------- */
830 0 : int nChunkSize = MIN(nSchemaStart-nSchemaInsertLocation,250000);
831 0 : char *pszChunk = (char *) CPLMalloc(nChunkSize);
832 0 : int nEndOfUnmovedData = nSchemaStart;
833 :
834 0 : for( nEndOfUnmovedData = nSchemaStart;
835 : nEndOfUnmovedData > nSchemaInsertLocation; )
836 : {
837 : int nBytesToMove =
838 0 : MIN(nChunkSize, nEndOfUnmovedData - nSchemaInsertLocation );
839 :
840 0 : VSIFSeekL( fpOutput, nEndOfUnmovedData - nBytesToMove, SEEK_SET );
841 0 : VSIFReadL( pszChunk, 1, nBytesToMove, fpOutput );
842 : VSIFSeekL( fpOutput, nEndOfUnmovedData - nBytesToMove + nSchemaSize,
843 0 : SEEK_SET );
844 0 : VSIFWriteL( pszChunk, 1, nBytesToMove, fpOutput );
845 :
846 0 : nEndOfUnmovedData -= nBytesToMove;
847 : }
848 :
849 0 : CPLFree( pszChunk );
850 :
851 : /* -------------------------------------------------------------------- */
852 : /* Write the schema in the opened slot. */
853 : /* -------------------------------------------------------------------- */
854 0 : VSIFSeekL( fpOutput, nSchemaInsertLocation, SEEK_SET );
855 0 : VSIFWriteL( pszSchema, 1, nSchemaSize, fpOutput );
856 :
857 0 : VSIFSeekL( fpOutput, 0, SEEK_END );
858 :
859 0 : nBoundedByLocation += nSchemaSize;
860 : }
861 : /* -------------------------------------------------------------------- */
862 : /* Close external schema files. */
863 : /* -------------------------------------------------------------------- */
864 : else
865 2 : VSIFCloseL( fpSchema );
866 : }
867 :
868 :
869 : /************************************************************************/
870 : /* PrintLine() */
871 : /************************************************************************/
872 :
873 120 : void OGRGMLDataSource::PrintLine(FILE* fp, const char *fmt, ...)
874 : {
875 120 : CPLString osWork;
876 : va_list args;
877 :
878 120 : va_start( args, fmt );
879 120 : osWork.vPrintf( fmt, args );
880 120 : va_end( args );
881 :
882 : #ifdef WIN32
883 : const char* pszEOL = "\r\n";
884 : #else
885 120 : const char* pszEOL = "\n";
886 : #endif
887 :
888 120 : VSIFPrintfL(fp, "%s%s", osWork.c_str(), pszEOL);
889 120 : }
|