From 75d32a8b83f1347badd993c1e20d8b46e06bbe79 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 7 Dec 2017 17:03:50 +0000 Subject: [PATCH] GMLAS: implement resolution of internal xlink:href='#....' references (fixes https://github.com/BRGM/gml_application_schema_toolbox/issues/31) git-svn-id: https://svn.osgeo.org/gdal/trunk@40973 f0d54148-0727-0410-94bb-9a71ac55c965 --- .../data/gmlas/gmlas_internal_xlink_href.xml | 31 ++ .../data/gmlas/gmlas_internal_xlink_href.xsd | 58 ++++ .../data/gmlas/gmlas_test_targetelement.xml | 5 +- .../data/gmlas/gmlas_test_targetelement.xsd | 12 +- .../gmlas_test_targetelement_other_ns.xsd | 14 +- .../gmlas/real_world/output/EUReg.example.txt | 6 + autotest/ogr/ogr_gmlas.py | 170 ++++++++++- gdal/data/gmlasconf.xml | 1 + gdal/data/gmlasconf.xsd | 15 + gdal/ogr/ogrsf_frmts/gmlas/GNUmakefile | 2 +- gdal/ogr/ogrsf_frmts/gmlas/makefile.vc | 2 +- gdal/ogr/ogrsf_frmts/gmlas/ogr_gmlas.h | 66 ++++- gdal/ogr/ogrsf_frmts/gmlas/ogr_gmlas_consts.h | 7 +- gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasconf.cpp | 9 +- .../ogrsf_frmts/gmlas/ogrgmlasdatasource.cpp | 52 ++-- gdal/ogr/ogrsf_frmts/gmlas/ogrgmlaslayer.cpp | 276 ++++++++++++++++++ gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasreader.cpp | 145 ++++++++- .../gmlas/ogrgmlasschemaanalyzer.cpp | 260 +++++------------ gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasutils.cpp | 190 ++++++++++++ 19 files changed, 1070 insertions(+), 251 deletions(-) create mode 100644 autotest/ogr/data/gmlas/gmlas_internal_xlink_href.xml create mode 100644 autotest/ogr/data/gmlas/gmlas_internal_xlink_href.xsd create mode 100644 gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasutils.cpp diff --git a/autotest/ogr/data/gmlas/gmlas_internal_xlink_href.xml b/autotest/ogr/data/gmlas/gmlas_internal_xlink_href.xml new file mode 100644 index 000000000000..d2350e834627 --- /dev/null +++ b/autotest/ogr/data/gmlas/gmlas_internal_xlink_href.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/autotest/ogr/data/gmlas/gmlas_internal_xlink_href.xsd b/autotest/ogr/data/gmlas/gmlas_internal_xlink_href.xsd new file mode 100644 index 000000000000..05267a3d8f7e --- /dev/null +++ b/autotest/ogr/data/gmlas/gmlas_internal_xlink_href.xsd @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/autotest/ogr/data/gmlas/gmlas_test_targetelement.xml b/autotest/ogr/data/gmlas/gmlas_test_targetelement.xml index 082110b536a5..c199ad05f803 100644 --- a/autotest/ogr/data/gmlas/gmlas_test_targetelement.xml +++ b/autotest/ogr/data/gmlas/gmlas_test_targetelement.xml @@ -1,5 +1,6 @@ - + + + diff --git a/autotest/ogr/data/gmlas/gmlas_test_targetelement.xsd b/autotest/ogr/data/gmlas/gmlas_test_targetelement.xsd index 720362b43c44..2c31e28d3367 100644 --- a/autotest/ogr/data/gmlas/gmlas_test_targetelement.xsd +++ b/autotest/ogr/data/gmlas/gmlas_test_targetelement.xsd @@ -24,10 +24,18 @@ - + - other_ns:existing_target_elt + other_ns:target_elt_with_required_id + + + + + + + + other_ns:target_elt_with_optional_id diff --git a/autotest/ogr/data/gmlas/gmlas_test_targetelement_other_ns.xsd b/autotest/ogr/data/gmlas/gmlas_test_targetelement_other_ns.xsd index 153f54c4aade..760647e6cf79 100644 --- a/autotest/ogr/data/gmlas/gmlas_test_targetelement_other_ns.xsd +++ b/autotest/ogr/data/gmlas/gmlas_test_targetelement_other_ns.xsd @@ -7,11 +7,21 @@ - + - + + + + + + + + + + + diff --git a/autotest/ogr/data/gmlas/real_world/output/EUReg.example.txt b/autotest/ogr/data/gmlas/real_world/output/EUReg.example.txt index 2cbada1215dc..bb657ded5b72 100644 --- a/autotest/ogr/data/gmlas/real_world/output/EUReg.example.txt +++ b/autotest/ogr/data/gmlas/real_world/output/EUReg.example.txt @@ -98,6 +98,7 @@ OGRFeature(productionfacility):1 beginlifespanversion (DateTime) = (null) hostingsite_href (String) = #_123456789.SITE hostingsite_owns (Integer(Boolean)) = 0 + hostingsite_productionsite_eureg_productionsite_pkid (String) = _123456789.SITE facilityname_owns (Integer(Boolean)) = 0 facilityname_featurename_nameoffeature (String) = EXAMPLE FACILITY 1 facilityname_featurename_confidentialityreason_owns (Integer(Boolean)) = 0 @@ -200,12 +201,14 @@ OGRFeature(productionfacility_groupedinstallation):1 parent_id (String) = _000000002.FACILITY href (String) = #_010101011.INSTALLATION owns (Integer(Boolean)) = 0 + productioninstallation_eureg_productioninstallation_pkid (String) = _010101011.INSTALLATION OGRFeature(productionfacility_groupedinstallation):2 ogr_pkid (String) = _000000002.FACILITY_groupedInstallation_2 parent_id (String) = _000000002.FACILITY href (String) = #_010101012.INSTALLATION owns (Integer(Boolean)) = 0 + productioninstallation_eureg_productioninstallation_pkid (String) = _010101012.INSTALLATION Layer name: productionfacility_competentauthorityeprtr @@ -368,12 +371,14 @@ OGRFeature(eureg_productioninstallation_groupedinstallationpart):1 parent_id (String) = _010101011.INSTALLATION href (String) = #_987654321.PART owns (Integer(Boolean)) = 0 + productioninstallatipart_eureg_productioninstallatipart_pkid (String) = _987654321.PART OGRFeature(eureg_productioninstallation_groupedinstallationpart):2 ogr_pkid (String) = _010101012.INSTALLATION_groupedInstallationPart_2 parent_id (String) = _010101012.INSTALLATION href (String) = #_987654322.PART owns (Integer(Boolean)) = 0 + productioninstallatipart_eureg_productioninstallatipart_pkid (String) = _987654322.PART Layer name: eureg_productioninstallation_competentauthoritypermits @@ -633,6 +638,7 @@ OGRFeature(eureg_productionsite):1 sitename_featurename_confidentialityreason_owns (Integer(Boolean)) = 0 reportdata_href (String) = #ES.RD.2017 reportdata_owns (Integer(Boolean)) = 0 + reportdata_reportdata_pkid (String) = ES.RD.2017 eureg_location = POINT (2.104334 41.991925) diff --git a/autotest/ogr/ogr_gmlas.py b/autotest/ogr/ogr_gmlas.py index 6127133fef46..beff53de231d 100755 --- a/autotest/ogr/ogr_gmlas.py +++ b/autotest/ogr/ogr_gmlas.py @@ -359,15 +359,18 @@ def ogr_gmlas_gml_Reference(): return 'skip' ds = ogr.Open('GMLAS:data/gmlas/gmlas_test_targetelement.xml') - if ds.GetLayerCount() != 2: + if ds.GetLayerCount() != 3: gdaltest.post_reason('fail') print(ds.GetLayerCount()) return 'fail' lyr = ds.GetLayerByName('main_elt') - f = lyr.GetNextFeature() - if f['reference_existing_target_elt_href'] != '#BAZ' or \ - f['reference_existing_target_elt_pkid'] != 'BAZ' or \ + with gdaltest.error_handler(): + f = lyr.GetNextFeature() + if f['reference_existing_target_elt_with_required_id_href'] != '#BAZ' or \ + f['reference_existing_target_elt_with_required_id_pkid'] != 'BAZ' or \ + f['reference_existing_target_elt_with_optional_id_href'] != '#BAZ2' or \ + f['refe_exis_targ_elt_with_opti_id_targe_elt_with_optio_id_pkid'] != 'F36BAD21BD2F14DDCA8852DBF8C90DBC_target_elt_with_optional_id_1' or \ f['reference_existing_abstract_target_elt_href'] != '#BAW' or \ f.IsFieldSet('reference_existing_abstract_target_elt_nillable_href') or \ f['reference_existing_abstract_target_elt_nillable_nil'] != 1: @@ -731,7 +734,7 @@ def ogr_gmlas_validate(): lyr.GetFeatureCount() gdal.PopErrorHandler() # Unexpected element with xpath=myns:main_elt/myns:bar (subxpath=myns:main_elt/myns:bar) found - if len(myhandler.error_list) != 2: + if len(myhandler.error_list) < 2: gdaltest.post_reason('fail') print(myhandler.error_list) return 'fail' @@ -3335,7 +3338,7 @@ def ogr_gmlas_extra_eureg(): ############################################################################### # Test a schema that has nothing interesting in it but imports another -# sceham +# schema def ogr_gmlas_no_element_in_first_choice_schema(): @@ -3352,6 +3355,160 @@ def ogr_gmlas_no_element_in_first_choice_schema(): return 'success' + +############################################################################### +# Test cross-layer links with xlink:href="#my_id" + +def ogr_gmlas_internal_xlink_href(): + + if ogr.GetDriverByName('GMLAS') is None: + return 'skip' + + with gdaltest.error_handler(): + ds = gdal.OpenEx('GMLAS:data/gmlas/gmlas_internal_xlink_href.xml') + lyr = ds.GetLayerByName('main_elt') + f = lyr.GetNextFeature() + if f['link_to_second_or_third_elt_href'] != '#does_not_exist' or \ + f.IsFieldSet('link_to_second_or_third_elt_second_elt_pkid') or \ + f.IsFieldSet('link_to_second_or_third_elt_third_elt_pkid') or \ + f.IsFieldSet('link_to_third_elt_third_elt_pkid'): + gdaltest.post_reason('fail') + f.DumpReadable() + return 'fail' + + f = lyr.GetNextFeature() + if f['link_to_second_or_third_elt_href'] != '#id2' or \ + f['link_to_second_or_third_elt_second_elt_pkid'] != 'id2' or \ + f.IsFieldSet('link_to_second_or_third_elt_third_elt_pkid') or \ + f.IsFieldSet('link_to_third_elt_third_elt_pkid'): + gdaltest.post_reason('fail') + f.DumpReadable() + return 'fail' + + f = lyr.GetNextFeature() + if f['link_to_second_or_third_elt_href'] != '#id3' or \ + f['link_to_second_or_third_elt_second_elt_pkid'] != 'id3' or \ + f.IsFieldSet('link_to_second_or_third_elt_third_elt_pkid') or \ + f.IsFieldSet('link_to_third_elt_third_elt_pkid'): + gdaltest.post_reason('fail') + f.DumpReadable() + return 'fail' + + f = lyr.GetNextFeature() + if f['link_to_second_or_third_elt_href'] != '#id4' or \ + f.IsFieldSet('link_to_second_or_third_elt_second_elt_pkid') or \ + f['link_to_second_or_third_elt_third_elt_pkid'] != 'D1013B7E44F28C976B976A4314FA4A09_third_elt_1' or \ + f.IsFieldSet('link_to_third_elt_third_elt_pkid'): + gdaltest.post_reason('fail') + f.DumpReadable() + return 'fail' + + f = lyr.GetNextFeature() + if f['link_to_third_elt_href'] != '#id4' or \ + f.IsFieldSet('link_to_second_or_third_elt_second_elt_pkid') or \ + f.IsFieldSet('link_to_second_or_third_elt_third_elt_pkid') or \ + f['link_to_third_elt_third_elt_pkid'] != 'D1013B7E44F28C976B976A4314FA4A09_third_elt_1': + gdaltest.post_reason('fail') + f.DumpReadable() + return 'fail' + + lyr = ds.GetLayerByName('_ogr_fields_metadata') + f = lyr.GetNextFeature() + if f['layer_name'] != 'main_elt' or f['field_index'] != 1 or \ + f['field_name'] != 'link_to_second_or_third_elt_href': + gdaltest.post_reason('fail') + f.DumpReadable() + return 'fail' + + f = lyr.GetNextFeature() + if f['layer_name'] != 'main_elt' or f['field_index'] != 2 or \ + f['field_name'] != 'link_to_second_or_third_elt_second_elt_pkid' or \ + f['field_xpath'] != 'main_elt/link_to_second_or_third_elt/second_elt' or \ + f['field_type'] != 'string' or \ + f['field_is_list'] != 0 or \ + f['field_min_occurs'] != 0 or \ + f['field_max_occurs'] != 1 or \ + f['field_category'] != 'PATH_TO_CHILD_ELEMENT_WITH_LINK' or \ + f['field_related_layer'] != 'second_elt': + gdaltest.post_reason('fail') + f.DumpReadable() + return 'fail' + + f = lyr.GetNextFeature() + if f['layer_name'] != 'main_elt' or f['field_index'] != 3 or \ + f['field_name'] != 'link_to_second_or_third_elt_third_elt_pkid' or \ + f['field_xpath'] != 'main_elt/link_to_second_or_third_elt/third_elt' or \ + f['field_type'] != 'string' or \ + f['field_is_list'] != 0 or \ + f['field_min_occurs'] != 0 or \ + f['field_max_occurs'] != 1 or \ + f['field_category'] != 'PATH_TO_CHILD_ELEMENT_WITH_LINK' or \ + f['field_related_layer'] != 'third_elt': + gdaltest.post_reason('fail') + f.DumpReadable() + return 'fail' + + f = lyr.GetNextFeature() + if f['layer_name'] != 'main_elt' or f['field_index'] != 4 or \ + f['field_name'] != 'link_to_second_or_third_elt': + gdaltest.post_reason('fail') + f.DumpReadable() + return 'fail' + + f = lyr.GetNextFeature() + if f['layer_name'] != 'main_elt' or f['field_index'] != 5 or \ + f['field_name'] != 'link_to_third_elt_href': + gdaltest.post_reason('fail') + f.DumpReadable() + return 'fail' + + f = lyr.GetNextFeature() + if f['layer_name'] != 'main_elt' or f['field_index'] != 6 or \ + f['field_name'] != 'link_to_third_elt_third_elt_pkid': + gdaltest.post_reason('fail') + f.DumpReadable() + return 'fail' + + f = lyr.GetNextFeature() + if f['layer_name'] != 'third_elt' or f['field_index'] != 1 or \ + f['field_name'] != 'id': + gdaltest.post_reason('fail') + f.DumpReadable() + return 'fail' + + lyr = ds.GetLayerByName('_ogr_layer_relationships') + f = lyr.GetNextFeature() + if f['parent_layer'] != 'main_elt' or \ + f['parent_pkid'] != 'ogr_pkid' or \ + f['parent_element_name'] != 'link_to_third_elt_third_elt_pkid' or \ + f['child_layer'] != 'third_elt' or \ + f['child_pkid'] != 'ogr_pkid': + gdaltest.post_reason('fail') + f.DumpReadable() + return 'fail' + + f = lyr.GetNextFeature() + if f['parent_layer'] != 'main_elt' or \ + f['parent_pkid'] != 'ogr_pkid' or \ + f['parent_element_name'] != 'link_to_second_or_third_elt_second_elt_pkid' or \ + f['child_layer'] != 'second_elt' or \ + f['child_pkid'] != 'id': + gdaltest.post_reason('fail') + f.DumpReadable() + return 'fail' + + f = lyr.GetNextFeature() + if f['parent_layer'] != 'main_elt' or \ + f['parent_pkid'] != 'ogr_pkid' or \ + f['parent_element_name'] != 'link_to_second_or_third_elt_third_elt_pkid' or \ + f['child_layer'] != 'third_elt' or \ + f['child_pkid'] != 'ogr_pkid': + gdaltest.post_reason('fail') + f.DumpReadable() + return 'fail' + + return 'success' + ############################################################################### # Cleanup @@ -3426,6 +3583,7 @@ def ogr_gmlas_cleanup(): ogr_gmlas_aux_schema_without_namespace_prefix, ogr_gmlas_geometry_as_substitutiongroup, ogr_gmlas_no_element_in_first_choice_schema, + ogr_gmlas_internal_xlink_href, ogr_gmlas_cleanup ] # gdaltest_list = [ ogr_gmlas_basic, ogr_gmlas_aux_schema_without_namespace_prefix, ogr_gmlas_cleanup ] diff --git a/gdal/data/gmlasconf.xml b/gdal/data/gmlasconf.xml index 01ba5f096ec1..8fc6b67b9eac 100644 --- a/gdal/data/gmlasconf.xml +++ b/gdal/data/gmlasconf.xml @@ -119,6 +119,7 @@ --> + true diff --git a/gdal/data/gmlasconf.xsd b/gdal/data/gmlasconf.xsd index ef3122b2d084..3e67eacdd7cf 100644 --- a/gdal/data/gmlasconf.xsd +++ b/gdal/data/gmlasconf.xsd @@ -765,6 +765,21 @@ + + + + + Whether xlink:href pointing to internal resources should be + resolved, so as to establish cross-layer relationships. + This options requires to keep in-memory xlink:href values + as well as feature ids, which in the case of really large + documents with many features and/or many cross-references + could consume a lot of RAM. + Default is true. + + + + diff --git a/gdal/ogr/ogrsf_frmts/gmlas/GNUmakefile b/gdal/ogr/ogrsf_frmts/gmlas/GNUmakefile index 7154e2094161..8f0a08a953bf 100644 --- a/gdal/ogr/ogrsf_frmts/gmlas/GNUmakefile +++ b/gdal/ogr/ogrsf_frmts/gmlas/GNUmakefile @@ -4,7 +4,7 @@ include ../../../GDALmake.opt OBJ = ogrgmlasdriver.o ogrgmlasdatasource.o ogrgmlaslayer.o \ ogrgmlasreader.o ogrgmlasschemaanalyzer.o ogrgmlasfeatureclass.o \ ogrgmlasxsdcache.o ogrgmlasconf.o ogrgmlasxpatchmatcher.o \ - ogrgmlasxlinkresolver.o ogrgmlaswriter.o + ogrgmlasxlinkresolver.o ogrgmlaswriter.o ogrgmlasutils.o CPPFLAGS := -I../mem -I../geojson $(JSON_INCLUDE) -I.. -I../.. -I../pgdump -DHAVE_XERCES=1 \ $(XERCES_INCLUDE) $(CPPFLAGS) diff --git a/gdal/ogr/ogrsf_frmts/gmlas/makefile.vc b/gdal/ogr/ogrsf_frmts/gmlas/makefile.vc index b8e5a5f6eef9..3d9bf33ed5ee 100644 --- a/gdal/ogr/ogrsf_frmts/gmlas/makefile.vc +++ b/gdal/ogr/ogrsf_frmts/gmlas/makefile.vc @@ -2,7 +2,7 @@ OBJ = ogrgmlasdriver.obj ogrgmlasdatasource.obj ogrgmlaslayer.obj \ ogrgmlasreader.obj ogrgmlasschemaanalyzer.obj ogrgmlasfeatureclass.obj \ ogrgmlasxsdcache.obj ogrgmlasconf.obj ogrgmlasxpatchmatcher.obj \ - ogrgmlasxlinkresolver.obj ogrgmlaswriter.obj + ogrgmlasxlinkresolver.obj ogrgmlaswriter.obj ogrgmlasutils.obj GDAL_ROOT = ..\..\.. diff --git a/gdal/ogr/ogrsf_frmts/gmlas/ogr_gmlas.h b/gdal/ogr/ogrsf_frmts/gmlas/ogr_gmlas.h index 2ac08caa6b76..1aa7bfeafed5 100644 --- a/gdal/ogr/ogrsf_frmts/gmlas/ogr_gmlas.h +++ b/gdal/ogr/ogrsf_frmts/gmlas/ogr_gmlas.h @@ -246,6 +246,8 @@ class GMLASXLinkResolutionConf bool m_bDefaultCacheResults; + bool m_bResolveInternalXLinks; + class URLSpecificResolution { public: @@ -882,6 +884,8 @@ class GMLASSchemaAnalyzer * GML document */ std::map m_oMapDocNSURIToPrefix; + bool m_bAlwaysGenerateOGRId; + static bool IsSame( const XSModelGroup* poModelGroup1, const XSModelGroup* poModelGroup2 ); XSModelGroupDefinition* GetGroupDefinition( const XSModelGroup* poModelGroup ); @@ -945,12 +949,6 @@ class GMLASSchemaAnalyzer bool IsIgnoredXPath(const CPLString& osXPath); - CPLString TruncateIdentifier(const CPLString& osName); - - CPLString AddSerialNumber(const CPLString& osNameIn, - int iOccurrence, - size_t nOccurrences); - void CollectClassesReferences( GMLASFeatureClass& oClass, std::vector& aoClasses ); @@ -980,6 +978,8 @@ class GMLASSchemaAnalyzer { m_nMaximumFieldsForFlattening = n; } void SetMapDocNSURIToPrefix(const std::map& oMap) { m_oMapDocNSURIToPrefix = oMap; } + void SetAlwaysGenerateOGRId(bool b) + { m_bAlwaysGenerateOGRId = b; } bool Analyze(GMLASXSDCache& oCache, const CPLString& osBaseDirname, @@ -1035,6 +1035,12 @@ class OGRGMLASDataSource: public GDALDataset /** Map from geometry field definition to its expected SRSName */ std::map m_oMapGeomFieldDefnToSRSName; + /* map the ID attribute to its belonging layer, e.g foo.1 -> layer Foo */ + std::map m_oMapElementIdToLayer; + + /* map the ID attribute to the feature PKID (when different from itself) */ + std::map m_oMapElementIdToPKID; + std::vector m_aoXSDsManuallyPassed; GMLASConfiguration m_oConf; @@ -1087,6 +1093,9 @@ class OGRGMLASDataSource: public GDALDataset static std::vector BuildXSDVector( const CPLString& osXSDFilenames); + + void InitReaderWithFirstPassElements(GMLASReader* poReader); + public: OGRGMLASDataSource(); virtual ~OGRGMLASDataSource(); @@ -1137,7 +1146,6 @@ class OGRGMLASDataSource: public GDALDataset return m_oConf.m_oMapIgnoredXPathToWarn; } const GMLASXPathMatcher& GetIgnoredXPathMatcher() const { return m_oIgnoredXPathMatcher; } - const CPLString& GetHash() const { return m_osHash; } const GMLASConfiguration& GetConf() const { return m_oConf; } const std::vector& GetXSDsManuallyPassed() const { @@ -1195,6 +1203,10 @@ class OGRGMLASLayer: public OGRLayer void SetLayerDefnFinalized(bool bVal) { m_bLayerDefnFinalized = bVal; } + CPLString LaunderFieldName(const CPLString& osFieldName); + + CPLString GetXPathFromOGRFieldIndex(int nIdx) const; + public: OGRGMLASLayer(OGRGMLASDataSource* poDS, const GMLASFeatureClass& oFC, @@ -1239,6 +1251,12 @@ class OGRGMLASLayer: public OGRLayer void InsertNewField( int nInsertPos, OGRFieldDefn& oFieldDefn, const CPLString& osXPath ); + + CPLString GetXPathOfFieldLinkForAttrToOtherLayer( + const CPLString& osFieldName, + const CPLString& osTargetLayerXPath ); + CPLString CreateLinkForAttrToOtherLayer( const CPLString& osFieldName, + const CPLString& osTargetLayerXPath ); }; /************************************************************************/ @@ -1465,6 +1483,16 @@ class GMLASReader : public DefaultHandler std::vector m_apoSWEDataArrayLayers; int m_nSWEDataArrayLayerIdx; + /* Set of 3 maps used for xlink:href="#xxxx" internal links resolution */ + /* 1) map the ID attribute to its belonging layer, e.g foo.1 -> layer Foo */ + std::map m_oMapElementIdToLayer; + /* 2) map the ID attribute to the feature PKID (when different from itself) */ + std::map m_oMapElementIdToPKID; + /* 3) map each (layer, field_xpath) to the list of ID it refers to */ + /* e.g (layer Bar, field_xpath) -> [foo.1, foo.2] */ + std::map, + std::vector > m_oMapFieldXPathToLinkValue; + void SetField( OGRFeature* poFeature, OGRGMLASLayer* poLayer, int nAttrIdx, @@ -1490,7 +1518,8 @@ class GMLASReader : public DefaultHandler void ProcessGeometry(CPLXMLNode* psRoot); void ProcessAttributes(const Attributes& attrs); - void ProcessXLinkHref( const CPLString& osAttrXPath, + void ProcessXLinkHref( int nAttrIdx, + const CPLString& osAttrXPath, const CPLString& osAttrValue ); void ExploreXMLDoc( const CPLString& osAttrXPath, const GMLASXLinkResolutionConf::URLSpecificResolution& oRule, @@ -1509,6 +1538,9 @@ class GMLASReader : public DefaultHandler bool FillTextContent() const { return !m_bInitialPass && m_nCurFieldIdx >=0; } + void ProcessInternalXLinkFirstPass(bool bRemoveUnusedFields, + std::map >&oMapUnusedFields); + public: GMLASReader(GMLASXSDCache& oCache, const GMLASXPathMatcher& oIgnoredXPathMatcher, @@ -1544,6 +1576,16 @@ class GMLASReader : public DefaultHandler void SetMapGeomFieldDefnToSRSName(const std::map& oMap ) { m_oMapGeomFieldDefnToSRSName = oMap; } + const std::map& GetMapElementIdToLayer() const + { return m_oMapElementIdToLayer; } + void SetMapElementIdToLayer(std::map& oMap) + { m_oMapElementIdToLayer = oMap; } + + const std::map& GetMapElementIdToPKID() const + { return m_oMapElementIdToPKID; } + void SetMapElementIdToPKID(const std::map& oMap ) + { m_oMapElementIdToPKID = oMap; } + void SetHash(const CPLString& osHash) { m_osHash = osHash; } void SetFileSize(vsi_l_offset nFileSize) { m_nFileSize = nFileSize; } @@ -1592,4 +1634,12 @@ class GMLASReader : public DefaultHandler { return m_apoSWEDataArrayLayers; } }; +CPLString OGRGMLASTruncateIdentifier(const CPLString& osName, + int nIdentMaxLength); + +CPLString OGRGMLASAddSerialNumber(const CPLString& osNameIn, + int iOccurrence, + size_t nOccurrences, + int nIdentMaxLength); + #endif // OGR_GMLAS_INCLUDED diff --git a/gdal/ogr/ogrsf_frmts/gmlas/ogr_gmlas_consts.h b/gdal/ogr/ogrsf_frmts/gmlas/ogr_gmlas_consts.h index 3d3f6599ad99..6b3992fcc376 100644 --- a/gdal/ogr/ogrsf_frmts/gmlas/ogr_gmlas_consts.h +++ b/gdal/ogr/ogrsf_frmts/gmlas/ogr_gmlas_consts.h @@ -28,8 +28,8 @@ * DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#ifndef OGR_GMLAS_CONSTS_INCLUDED_REFEFINABLE -#define OGR_GMLAS_CONSTS_INCLUDED_REFEFINABLE +#ifndef OGR_GMLAS_CONSTS_INCLUDED_REDEFINABLE +#define OGR_GMLAS_CONSTS_INCLUDED_REDEFINABLE #ifdef CONSTANT_DEFINITION #define STRING_CONST(x,y) const char* const x = y @@ -48,6 +48,7 @@ namespace GMLASConstants BOOL_CONST(DEFAULT_RESOLUTION_ENABLED_DEFAULT, false); BOOL_CONST(ALLOW_REMOTE_DOWNLOAD_DEFAULT, true); BOOL_CONST(CACHE_RESULTS_DEFAULT, false); + BOOL_CONST(INTERNAL_XLINK_RESOLUTION_DEFAULT, false); BOOL_CONST(ALLOW_REMOTE_SCHEMA_DOWNLOAD_DEFAULT, true); BOOL_CONST(ALWAYS_GENERATE_OGR_ID_DEFAULT, false); @@ -315,4 +316,4 @@ namespace GMLASConstants using namespace GMLASConstants; -#endif // OGR_GMLAS_CONSTS_INCLUDED_REFEFINABLE +#endif // OGR_GMLAS_CONSTS_INCLUDED_REDEFINABLE diff --git a/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasconf.cpp b/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasconf.cpp index bead8c03353c..6ab97b6ce28e 100644 --- a/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasconf.cpp +++ b/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasconf.cpp @@ -32,7 +32,7 @@ #include "ogr_gmlas.h" #define CONSTANT_DEFINITION -#undef OGR_GMLAS_CONSTS_INCLUDED_REFEFINABLE +#undef OGR_GMLAS_CONSTS_INCLUDED_REDEFINABLE #include "ogr_gmlas_consts.h" #include "cpl_minixml.h" @@ -557,7 +557,8 @@ GMLASXLinkResolutionConf::GMLASXLinkResolutionConf() : m_bDefaultAllowRemoteDownload(ALLOW_REMOTE_DOWNLOAD_DEFAULT), m_eDefaultResolutionMode(RawContent), m_nDefaultResolutionDepth(1), - m_bDefaultCacheResults(CACHE_RESULTS_DEFAULT) + m_bDefaultCacheResults(CACHE_RESULTS_DEFAULT), + m_bResolveInternalXLinks(INTERNAL_XLINK_RESOLUTION_DEFAULT) { } @@ -662,6 +663,10 @@ bool GMLASXLinkResolutionConf::LoadFromXML(CPLXMLNode* psRoot) } } + m_bResolveInternalXLinks = CPLGetXMLBoolValue( psRoot, + "ResolveInternalXLinks", + INTERNAL_XLINK_RESOLUTION_DEFAULT); + return true; } diff --git a/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasdatasource.cpp b/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasdatasource.cpp index bb1feb1d5532..6311c99978bc 100644 --- a/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasdatasource.cpp +++ b/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasdatasource.cpp @@ -227,6 +227,7 @@ OGRLayer *OGRGMLASDataSource::GetLayer(int i) const int nBaseLayers = static_cast(m_apoLayers.size()); if( i >= nBaseLayers ) { + RunFirstPassIfNeeded(NULL, NULL, NULL); if( i - nBaseLayers < static_cast(m_apoRequestedMetadataLayers.size()) ) return m_apoRequestedMetadataLayers[i - nBaseLayers]; } @@ -261,6 +262,7 @@ OGRLayer *OGRGMLASDataSource::GetLayerByName(const char* pszName) { m_apoRequestedMetadataLayers.push_back(apoLayers[i]); } + RunFirstPassIfNeeded(NULL, NULL, NULL); return apoLayers[i]; } } @@ -777,6 +779,7 @@ bool OGRGMLASDataSource::Open(GDALOpenInfo* poOpenInfo) m_oConf.m_bCaseInsensitiveIdentifier); oAnalyzer.SetPGIdentifierLaundering(m_oConf.m_bPGIdentifierLaundering); oAnalyzer.SetMaximumFieldsForFlattening(m_oConf.m_nMaximumFieldsForFlattening); + oAnalyzer.SetAlwaysGenerateOGRId(m_oConf.m_bAlwaysGenerateOGRId); m_osGMLFilename = STARTS_WITH_CI(poOpenInfo->pszFilename, szGMLAS_PREFIX) ? CPLExpandTilde(poOpenInfo->pszFilename + strlen(szGMLAS_PREFIX)) : @@ -1059,7 +1062,7 @@ GMLASReader* OGRGMLASDataSource::CreateReader( VSILFILE*& fpGML, poReader->SetMapIgnoredXPathToWarn( GetMapIgnoredXPathToWarn()); - poReader->SetHash( GetHash() ); + poReader->SetHash( m_osHash ); return poReader; } @@ -1247,6 +1250,23 @@ VSILFILE* OGRGMLASDataSource::PopUnusedGMLFilePointer() return fpGML; } +/************************************************************************/ +/* InitReaderWithFirstPassElements() */ +/************************************************************************/ + +void OGRGMLASDataSource::InitReaderWithFirstPassElements(GMLASReader* poReader) +{ + if( poReader != NULL ) + { + poReader->SetMapSRSNameToInvertedAxis(m_oMapSRSNameToInvertedAxis); + poReader->SetMapGeomFieldDefnToSRSName(m_oMapGeomFieldDefnToSRSName); + poReader->SetProcessDataRecord(m_bFoundSWE && m_oConf.m_bSWEProcessDataRecord); + poReader->SetSWEDataArrayLayers(m_apoSWEDataArrayLayers); + poReader->SetMapElementIdToLayer(m_oMapElementIdToLayer); + poReader->SetMapElementIdToPKID(m_oMapElementIdToPKID); + } +} + /************************************************************************/ /* RunFirstPassIfNeeded() */ /************************************************************************/ @@ -1257,13 +1277,7 @@ bool OGRGMLASDataSource::RunFirstPassIfNeeded( GMLASReader* poReader, { if( m_bFirstPassDone ) { - if( poReader != NULL ) - { - poReader->SetMapSRSNameToInvertedAxis(m_oMapSRSNameToInvertedAxis); - poReader->SetMapGeomFieldDefnToSRSName(m_oMapGeomFieldDefnToSRSName); - poReader->SetProcessDataRecord(m_bFoundSWE && m_oConf.m_bSWEProcessDataRecord); - poReader->SetSWEDataArrayLayers(m_apoSWEDataArrayLayers); - } + InitReaderWithFirstPassElements(poReader); return true; } @@ -1282,10 +1296,12 @@ bool OGRGMLASDataSource::RunFirstPassIfNeeded( GMLASReader* poReader, } } + bool bSuccess = true; const bool bHasURLSpecificRules = !m_oXLinkResolver.GetConf().m_aoURLSpecificRules.empty(); if( bHasGeomFields || m_bValidate || m_bRemoveUnusedLayers || m_bRemoveUnusedFields || bHasURLSpecificRules || + m_oXLinkResolver.GetConf().m_bResolveInternalXLinks || (m_bFoundSWE && (m_oConf.m_bSWEProcessDataRecord || m_oConf.m_bSWEProcessDataArray)) ) { @@ -1322,11 +1338,14 @@ bool OGRGMLASDataSource::RunFirstPassIfNeeded( GMLASReader* poReader, poReaderFirstPass->SetMapIgnoredXPathToWarn( m_oConf.m_oMapIgnoredXPathToWarn); + + poReaderFirstPass->SetHash( m_osHash ); + // No need to warn afterwards m_oConf.m_oMapIgnoredXPathToWarn.clear(); std::set aoSetRemovedLayerNames; - m_bFirstPassDone = poReaderFirstPass->RunFirstPass( + bSuccess = poReaderFirstPass->RunFirstPass( pfnProgress, pProgressData, m_bRemoveUnusedLayers, @@ -1410,26 +1429,23 @@ bool OGRGMLASDataSource::RunFirstPassIfNeeded( GMLASReader* poReader, m_poRelationshipsLayer->ResetReading(); } - // Store 2 maps to reinject them in real readers + // Store maps to reinject them in real readers m_oMapSRSNameToInvertedAxis = poReaderFirstPass->GetMapSRSNameToInvertedAxis(); m_oMapGeomFieldDefnToSRSName = poReaderFirstPass->GetMapGeomFieldDefnToSRSName(); + m_oMapElementIdToLayer = poReaderFirstPass->GetMapElementIdToLayer(); + m_oMapElementIdToPKID = poReaderFirstPass->GetMapElementIdToPKID(); + delete poReaderFirstPass; VSIFSeekL(fp, 0, SEEK_SET); if( bJustOpenedFiled ) PushUnusedGMLFilePointer(fp); - if( poReader != NULL ) - { - poReader->SetMapSRSNameToInvertedAxis(m_oMapSRSNameToInvertedAxis); - poReader->SetMapGeomFieldDefnToSRSName(m_oMapGeomFieldDefnToSRSName); - poReader->SetProcessDataRecord(m_bFoundSWE && m_oConf.m_bSWEProcessDataRecord); - poReader->SetSWEDataArrayLayers(m_apoSWEDataArrayLayers); - } + InitReaderWithFirstPassElements(poReader); } - return m_bFirstPassDone; + return bSuccess; } diff --git a/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlaslayer.cpp b/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlaslayer.cpp index 8c4c6856d867..01214922fc8f 100644 --- a/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlaslayer.cpp +++ b/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlaslayer.cpp @@ -30,6 +30,7 @@ // Must be first for DEBUG_BOOL case #include "ogr_gmlas.h" +#include "ogr_pgdump.h" #include "cpl_minixml.h" CPL_CVSID("$Id$") @@ -1063,6 +1064,24 @@ bool OGRGMLASLayer::RemoveField( int nIdx ) m_oMapOGRFieldIdxtoFCFieldIdx = oMapOGRFieldIdxtoFCFieldIdx; } + OGRLayer* poFieldsMetadataLayer = m_poDS->GetFieldsMetadataLayer(); + OGRFeature* poFeature; + poFieldsMetadataLayer->ResetReading(); + while( (poFeature = poFieldsMetadataLayer->GetNextFeature()) != NULL ) + { + if( strcmp( poFeature->GetFieldAsString( szLAYER_NAME ), GetName() ) == 0 && + poFeature->GetFieldAsInteger( szFIELD_INDEX ) == nIdx ) + { + poFeature->SetField( szFIELD_INDEX, -1 ); + CPL_IGNORE_RET_VAL( + poFieldsMetadataLayer->SetFeature( poFeature ) ); + delete poFeature; + break; + } + delete poFeature; + } + poFieldsMetadataLayer->ResetReading(); + return true; } @@ -1120,6 +1139,25 @@ void OGRGMLASLayer::InsertNewField( int nInsertPos, } m_oMapOGRFieldIdxtoFCFieldIdx = oMapOGRFieldIdxtoFCFieldIdx; } + + OGRLayer* poFieldsMetadataLayer = m_poDS->GetFieldsMetadataLayer(); + OGRFeature* poFeature; + poFieldsMetadataLayer->ResetReading(); + while( (poFeature = poFieldsMetadataLayer->GetNextFeature()) != NULL ) + { + if( strcmp( poFeature->GetFieldAsString( szLAYER_NAME ), GetName() ) == 0 ) + { + int nFieldIndex = poFeature->GetFieldAsInteger( szFIELD_INDEX ); + if( nFieldIndex >= nInsertPos ) + { + poFeature->SetField( szFIELD_INDEX, nFieldIndex + 1 ); + CPL_IGNORE_RET_VAL( + poFieldsMetadataLayer->SetFeature( poFeature ) ); + } + } + delete poFeature; + } + poFieldsMetadataLayer->ResetReading(); } /************************************************************************/ @@ -1135,6 +1173,26 @@ int OGRGMLASLayer::GetOGRFieldIndexFromXPath(const CPLString& osXPath) const return oIter->second; } +/************************************************************************/ +/* GetXPathFromOGRFieldIndex() */ +/************************************************************************/ + +CPLString OGRGMLASLayer::GetXPathFromOGRFieldIndex(int nIdx) const +{ + const int nFCIdx = GetFCFieldIndexFromOGRFieldIdx(nIdx); + if( nFCIdx >= 0 ) + return m_oFC.GetFields()[nFCIdx].GetXPath(); + + std::map::const_iterator oIter = + m_oMapFieldXPathToOGRFieldIdx.begin(); + for( ; oIter != m_oMapFieldXPathToOGRFieldIdx.end(); ++oIter ) + { + if( oIter->second == nIdx ) + return oIter->first; + } + return CPLString(); +} + /************************************************************************/ /* GetOGRGeomFieldIndexFromXPath() */ /************************************************************************/ @@ -1187,6 +1245,223 @@ int OGRGMLASLayer::GetFCFieldIndexFromOGRGeomFieldIdx(int iOGRGeomFieldIdx) cons return oIter->second; } +/************************************************************************/ +/* GetXPathOfFieldLinkForAttrToOtherLayer() */ +/************************************************************************/ + +CPLString OGRGMLASLayer::GetXPathOfFieldLinkForAttrToOtherLayer( + const CPLString& osFieldName, + const CPLString& osTargetLayerXPath ) +{ + const int nOGRFieldIdx = GetLayerDefn()->GetFieldIndex(osFieldName); + CPLAssert(nOGRFieldIdx >= 0); + const int nFCFieldIdx = GetFCFieldIndexFromOGRFieldIdx(nOGRFieldIdx); + CPLAssert(nFCFieldIdx >= 0); + CPLString osXPath(m_oFC.GetFields()[nFCFieldIdx].GetXPath()); + size_t nPos = osXPath.find(szAT_XLINK_HREF); + CPLAssert(nPos != std::string::npos); + CPLAssert(nPos + strlen(szAT_XLINK_HREF) == osXPath.size()); + CPLString osTargetFieldXPath(osXPath.substr(0, nPos) + osTargetLayerXPath); + return osTargetFieldXPath; +} + +/************************************************************************/ +/* LaunderFieldName() */ +/************************************************************************/ + +CPLString OGRGMLASLayer::LaunderFieldName(const CPLString& osFieldName) +{ + int nCounter = 1; + CPLString osLaunderedName(osFieldName); + while( m_poFeatureDefn->GetFieldIndex(osLaunderedName) >= 0 ) + { + nCounter ++; + osLaunderedName = osFieldName + CPLSPrintf("%d", nCounter); + } + + const int nIdentifierMaxLength = m_poDS->GetConf().m_nIdentifierMaxLength; + if( nIdentifierMaxLength >= MIN_VALUE_OF_MAX_IDENTIFIER_LENGTH && + osLaunderedName.size() > static_cast(nIdentifierMaxLength) ) + { + osLaunderedName = OGRGMLASTruncateIdentifier(osLaunderedName, + nIdentifierMaxLength); + } + + if( m_poDS->GetConf().m_bPGIdentifierLaundering ) + { + char* pszLaundered = OGRPGCommonLaunderName( osLaunderedName, + "GMLAS" ); + osLaunderedName = pszLaundered; + CPLFree( pszLaundered ); + } + + if( m_poFeatureDefn->GetFieldIndex(osLaunderedName) >= 0 ) + { + nCounter = 1; + CPLString osCandidate; + do + { + osCandidate = OGRGMLASAddSerialNumber( osLaunderedName, + nCounter, + nCounter + 1, + nIdentifierMaxLength ); + } while( nCounter < 100 && + m_poFeatureDefn->GetFieldIndex(osCandidate) >= 0 ); + osLaunderedName = osCandidate; + } + + return osLaunderedName; +} + +/************************************************************************/ +/* CreateLinkForAttrToOtherLayer() */ +/************************************************************************/ + +/* Create a new field to contain the PKID of the feature pointed by this */ +/* osFieldName (a xlink:href attribute), when it is an internal link to */ +/* another layer whose xpath is given by osTargetLayerXPath */ + +CPLString OGRGMLASLayer::CreateLinkForAttrToOtherLayer( + const CPLString& osFieldName, + const CPLString& osTargetLayerXPath ) +{ + CPLString osTargetFieldXPath = GetXPathOfFieldLinkForAttrToOtherLayer( + osFieldName, osTargetLayerXPath); + const int nExistingTgtOGRFieldIdx = + GetOGRFieldIndexFromXPath(osTargetFieldXPath); + if( nExistingTgtOGRFieldIdx >= 0 ) + { + return GetLayerDefn()->GetFieldDefn( + nExistingTgtOGRFieldIdx)->GetNameRef(); + } + + const int nOGRFieldIdx = GetLayerDefn()->GetFieldIndex(osFieldName); + CPLAssert(nOGRFieldIdx >= 0); + const int nFCFieldIdx = GetFCFieldIndexFromOGRFieldIdx(nOGRFieldIdx); + CPLAssert(nFCFieldIdx >= 0); + CPLString osXPath(m_oFC.GetFields()[nFCFieldIdx].GetXPath()); + size_t nPos = osXPath.find(szAT_XLINK_HREF); + CPLString osXPathStart( osXPath.substr(0, nPos) ); + + // Find at which position to insert the new field in the layer definition + // (we could happen at the end, but it will be nicer to insert close to + // the href field) + int nInsertPos = -1; + for( int i = 0; i < m_poFeatureDefn->GetFieldCount(); i++ ) + { + if( GetXPathFromOGRFieldIndex(i).find(osXPathStart) == 0 ) + { + nInsertPos = i + 1; + } + else if( nInsertPos >= 0 ) + break; + } + + CPLString osNewFieldName(osFieldName); + nPos = osFieldName.find(szHREF_SUFFIX); + if( nPos != std::string::npos ) + { + osNewFieldName.resize(nPos); + } + osNewFieldName += "_"; + OGRGMLASLayer* poTargetLayer = m_poDS->GetLayerByXPath(osTargetLayerXPath); + CPLAssert(poTargetLayer); + osNewFieldName += poTargetLayer->GetName(); + osNewFieldName += "_pkid"; + osNewFieldName = LaunderFieldName(osNewFieldName); + OGRFieldDefn oFieldDefn( osNewFieldName, OFTString ); + InsertNewField( nInsertPos, oFieldDefn, osTargetFieldXPath ); + + + OGRLayer* poFieldsMetadataLayer = m_poDS->GetFieldsMetadataLayer(); + OGRLayer* poRelationshipsLayer = m_poDS->GetRelationshipsLayer(); + + // Find a relevant location of the field metadata layer into which to + // insert the new feature (same as above, we could potentially insert just + // at the end) + GIntBig nFieldsMetadataIdxPos = -1; + poFieldsMetadataLayer->ResetReading(); + OGRFeature* poFeature; + while( (poFeature = poFieldsMetadataLayer->GetNextFeature()) != NULL ) + { + if( strcmp( poFeature->GetFieldAsString( szLAYER_NAME ), GetName() ) == 0 ) + { + if (poFeature->GetFieldAsInteger( szFIELD_INDEX ) > nInsertPos ) + { + delete poFeature; + break; + } + nFieldsMetadataIdxPos = poFeature->GetFID() + 1; + } + else if( nFieldsMetadataIdxPos >= 0 ) + { + delete poFeature; + break; + } + delete poFeature; + } + poFieldsMetadataLayer->ResetReading(); + + // Move down all features beyond that insertion point + for(GIntBig nFID = poFieldsMetadataLayer->GetFeatureCount() - 1; + nFID >= nFieldsMetadataIdxPos; nFID-- ) + { + poFeature = poFieldsMetadataLayer->GetFeature(nFID); + if( poFeature ) + { + poFeature->SetFID(nFID+1); + CPL_IGNORE_RET_VAL( poFieldsMetadataLayer->SetFeature(poFeature) ); + delete poFeature; + } + } + if( nFieldsMetadataIdxPos >= 0 ) + { + CPL_IGNORE_RET_VAL( + poFieldsMetadataLayer->DeleteFeature(nFieldsMetadataIdxPos)); + } + + // Register field in _ogr_fields_metadata + OGRFeature* poFieldDescFeature = + new OGRFeature(poFieldsMetadataLayer->GetLayerDefn()); + poFieldDescFeature->SetField( szLAYER_NAME, GetName() ); + poFieldDescFeature->SetField( szFIELD_INDEX, nInsertPos ); + poFieldDescFeature->SetField( szFIELD_XPATH, osTargetFieldXPath ); + poFieldDescFeature->SetField( szFIELD_NAME, + oFieldDefn.GetNameRef() ); + poFieldDescFeature->SetField( szFIELD_TYPE, szXS_STRING ); + poFieldDescFeature->SetField( szFIELD_IS_LIST, 0 ); + poFieldDescFeature->SetField( szFIELD_MIN_OCCURS, 0 ); + poFieldDescFeature->SetField( szFIELD_MAX_OCCURS, 1 ); + poFieldDescFeature->SetField( szFIELD_CATEGORY, szPATH_TO_CHILD_ELEMENT_WITH_LINK ); + poFieldDescFeature->SetField( szFIELD_RELATED_LAYER, + poTargetLayer->GetName() ); + if( nFieldsMetadataIdxPos >= 0 ) + poFieldDescFeature->SetFID( nFieldsMetadataIdxPos ); + CPL_IGNORE_RET_VAL( + poFieldsMetadataLayer->CreateFeature(poFieldDescFeature)); + delete poFieldDescFeature; + + // Register relationship in _ogr_layer_relationships + OGRFeature* poRelationshipsFeature = + new OGRFeature(poRelationshipsLayer->GetLayerDefn()); + poRelationshipsFeature->SetField( szPARENT_LAYER, GetName() ); + poRelationshipsFeature->SetField( szPARENT_PKID, + GetLayerDefn()->GetFieldDefn( + GetIDFieldIdx())->GetNameRef() ); + poRelationshipsFeature->SetField( szPARENT_ELEMENT_NAME, + osNewFieldName ); + poRelationshipsFeature->SetField(szCHILD_LAYER, + poTargetLayer->GetName() ); + poRelationshipsFeature->SetField( szCHILD_PKID, + poTargetLayer->GetLayerDefn()->GetFieldDefn( + poTargetLayer->GetIDFieldIdx())->GetNameRef() ); + CPL_IGNORE_RET_VAL(poRelationshipsLayer->CreateFeature( + poRelationshipsFeature)); + delete poRelationshipsFeature; + + return osNewFieldName; +} + /************************************************************************/ /* GetLayerDefn() */ /************************************************************************/ @@ -1198,6 +1473,7 @@ OGRFeatureDefn* OGRGMLASLayer::GetLayerDefn() // If we haven't yet determined the SRS of geometry columns, do it now m_bLayerDefnFinalized = true; if( m_poFeatureDefn->GetGeomFieldCount() > 0 || + m_poDS->GetConf().m_oXLinkResolution.m_bResolveInternalXLinks || !m_poDS->GetConf().m_oXLinkResolution.m_aoURLSpecificRules.empty() ) { if( m_poReader == NULL ) diff --git a/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasreader.cpp b/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasreader.cpp index 99fbdf31a178..5b18a6fc42b8 100644 --- a/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasreader.cpp +++ b/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasreader.cpp @@ -1869,7 +1869,32 @@ void GMLASReader::ProcessAttributes(const Attributes& attrs) osAttrLocalname == szHREF && !osAttrValue.empty() ) { - ProcessXLinkHref( osAttrXPath, osAttrValue ); + ProcessXLinkHref( nAttrIdx, osAttrXPath, osAttrValue ); + } + + if( m_oXLinkResolver.GetConf().m_bResolveInternalXLinks && + m_bInitialPass ) + { + nFCIdx = m_oCurCtxt.m_poLayer-> + GetFCFieldIndexFromXPath(osAttrXPath); + if( nFCIdx >= 0 && + m_oCurCtxt.m_poLayer->GetFeatureClass(). + GetFields()[nFCIdx].GetType() == GMLAS_FT_ID ) + { + // We don't check that there's no existing id in the map + // This is normally forbidden by the xs:ID rules + // If not respected by the document, this should not lead to + // crashes + m_oMapElementIdToLayer[ osAttrValue ] = m_oCurCtxt.m_poLayer; + + if( m_oCurCtxt.m_poLayer->IsGeneratedIDField() ) + { + CPLString osFeaturePKID( + m_oCurCtxt.m_poFeature->GetFieldAsString( + m_oCurCtxt.m_poLayer->GetIDFieldIdx() )); + m_oMapElementIdToPKID[ osAttrValue ] = osFeaturePKID; + } + } } } @@ -2027,7 +2052,8 @@ void GMLASReader::ProcessAttributes(const Attributes& attrs) /* ProcessXLinkHref() */ /************************************************************************/ -void GMLASReader::ProcessXLinkHref( const CPLString& osAttrXPath, +void GMLASReader::ProcessXLinkHref( int nAttrIdx, + const CPLString& osAttrXPath, const CPLString& osAttrValue ) { // If we are a xlink:href attribute, and that the link value is @@ -2039,12 +2065,52 @@ void GMLASReader::ProcessXLinkHref( const CPLString& osAttrXPath, m_oCurCtxt.m_poLayer->GetOGRFieldIndexFromXPath( GMLASField::MakePKIDFieldXPathFromXLinkHrefXPath( osAttrXPath)); - if( nAttrIdx2 >= 0 ) + if( nAttrIdx2 >= 0 ) { SetField( m_oCurCtxt.m_poFeature, m_oCurCtxt.m_poLayer, nAttrIdx2, osAttrValue.substr(1) ); } + else if( m_oXLinkResolver.GetConf().m_bResolveInternalXLinks ) + { + const CPLString osReferingField( + m_oCurCtxt.m_poLayer->GetLayerDefn()-> + GetFieldDefn(nAttrIdx)->GetNameRef()); + const CPLString osId(osAttrValue.substr(1)); + if( m_bInitialPass ) + { + std::pair oReferingPair( + m_oCurCtxt.m_poLayer, osReferingField); + m_oMapFieldXPathToLinkValue[oReferingPair].push_back(osId); + } + else + { + std::map::const_iterator oIter = + m_oMapElementIdToLayer.find(osId); + if( oIter != m_oMapElementIdToLayer.end() ) + { + OGRGMLASLayer* poTargetLayer = oIter->second; + const CPLString osLinkFieldXPath = + m_oCurCtxt.m_poLayer->GetXPathOfFieldLinkForAttrToOtherLayer( + osReferingField, + poTargetLayer->GetFeatureClass().GetXPath()); + const int nLinkFieldOGRId = + m_oCurCtxt.m_poLayer->GetOGRFieldIndexFromXPath( + osLinkFieldXPath); + std::map::const_iterator oIter2 = + m_oMapElementIdToPKID.find(osId); + if( oIter2 != m_oMapElementIdToPKID.end() ) + { + m_oCurCtxt.m_poFeature->SetField(nLinkFieldOGRId, + oIter2->second); + } + else + { + m_oCurCtxt.m_poFeature->SetField(nLinkFieldOGRId, osId); + } + } + } + } } else { @@ -3177,7 +3243,7 @@ bool GMLASReader::RunFirstPass(GDALProgressFunc pfnProgress, // Store in m_oSetGeomFieldsWithUnknownSRS the geometry fields std::set oSetUnreferencedLayers; - std::map > oMapUnusedFields; + std::map > oMapUnusedFields; for(size_t i=0; i < m_papoLayers->size(); i++ ) { OGRGMLASLayer* poLayer = (*m_papoLayers)[i]; @@ -3190,7 +3256,8 @@ bool GMLASReader::RunFirstPass(GDALProgressFunc pfnProgress, } for(int j=0; j< poFDefn->GetFieldCount(); j++ ) { - oMapUnusedFields[poLayer].insert(j); + oMapUnusedFields[poLayer].insert( + poFDefn->GetFieldDefn(j)->GetNameRef()); } } @@ -3204,7 +3271,8 @@ bool GMLASReader::RunFirstPass(GDALProgressFunc pfnProgress, bRemoveUnusedLayers || bRemoveUnusedFields || bHasURLSpecificRules || - bProcessSWEDataArray ); + bProcessSWEDataArray || + m_oXLinkResolver.GetConf().m_bResolveInternalXLinks); // Loop on features until we have determined the SRS of all geometry // columns, or potentially on the whole file for the above reasons @@ -3217,13 +3285,13 @@ bool GMLASReader::RunFirstPass(GDALProgressFunc pfnProgress, oSetUnreferencedLayers.erase( poLayer ); if( bRemoveUnusedFields ) { - std::set& oSetUnusedFields = oMapUnusedFields[poLayer]; + std::set& oSetUnusedFields = oMapUnusedFields[poLayer]; OGRFeatureDefn* poFDefn = poLayer->GetLayerDefn(); int nFieldCount = poFDefn->GetFieldCount(); for(int j=0; j< nFieldCount; j++ ) { if( poFeature->IsFieldSetAndNotNull(j) ) - oSetUnusedFields.erase(j); + oSetUnusedFields.erase(poFDefn->GetFieldDefn(j)->GetNameRef()); } } delete poFeature; @@ -3231,6 +3299,8 @@ bool GMLASReader::RunFirstPass(GDALProgressFunc pfnProgress, CPLDebug("GMLAS", "End of first pass"); + ProcessInternalXLinkFirstPass(bRemoveUnusedFields, oMapUnusedFields); + if( bRemoveUnusedLayers ) { std::vector apoNewLayers; @@ -3255,15 +3325,12 @@ bool GMLASReader::RunFirstPass(GDALProgressFunc pfnProgress, for(size_t i=0; i < m_papoLayers->size(); i++ ) { poLayer = (*m_papoLayers)[i]; - std::set& oSetUnusedFields = oMapUnusedFields[poLayer]; - std::set::iterator oIter = oSetUnusedFields.begin(); - int nShiftIndex = 0; + std::set& oSetUnusedFields = oMapUnusedFields[poLayer]; + std::set::iterator oIter = oSetUnusedFields.begin(); for( ; oIter != oSetUnusedFields.end(); ++oIter ) { - if( poLayer->RemoveField( (*oIter) - nShiftIndex ) ) - { - nShiftIndex ++; - } + poLayer->RemoveField( + poLayer->GetLayerDefn()->GetFieldIndex(*oIter) ); } // We need to run this again since we may have delete the @@ -3286,6 +3353,54 @@ bool GMLASReader::RunFirstPass(GDALProgressFunc pfnProgress, return !m_bInterrupted; } +/************************************************************************/ +/* ProcessInternalXLinkFirstPass() */ +/************************************************************************/ + +void GMLASReader::ProcessInternalXLinkFirstPass( + bool bRemoveUnusedFields, + std::map >&oMapUnusedFields) +{ + std::map, + std::vector >::const_iterator + oIter = m_oMapFieldXPathToLinkValue.begin(); + for( ; oIter != m_oMapFieldXPathToLinkValue.end(); ++oIter ) + { + OGRGMLASLayer* poReferingLayer = oIter->first.first; + const CPLString& osReferingField = oIter->first.second; + const std::vector& aosLinks = oIter->second; + std::set oSetTargetLayers; + for( size_t i = 0; i < aosLinks.size(); i++ ) + { + std::map::const_iterator oIter2 = + m_oMapElementIdToLayer.find(aosLinks[i]); + if( oIter2 == m_oMapElementIdToLayer.end() ) + { + CPLError(CE_Warning, CPLE_AppDefined, + "%s:%s = '#%s' has no corresponding target " + "element in this document", + poReferingLayer->GetName(), + osReferingField.c_str(), + aosLinks[i].c_str()); + } + else if( oSetTargetLayers.find(oIter2->second) == + oSetTargetLayers.end() ) + { + OGRGMLASLayer* poTargetLayer = oIter2->second; + oSetTargetLayers.insert(poTargetLayer); + CPLString osLinkFieldName = + poReferingLayer->CreateLinkForAttrToOtherLayer( + osReferingField, + poTargetLayer->GetFeatureClass().GetXPath()); + if( bRemoveUnusedFields ) + { + oMapUnusedFields[poReferingLayer].erase(osLinkFieldName); + } + } + } + } +} + /************************************************************************/ /* CreateFieldsForURLSpecificRules() */ /************************************************************************/ diff --git a/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasschemaanalyzer.cpp b/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasschemaanalyzer.cpp index f981939ca171..e83f91caa12f 100644 --- a/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasschemaanalyzer.cpp +++ b/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasschemaanalyzer.cpp @@ -257,6 +257,7 @@ GMLASSchemaAnalyzer::GMLASSchemaAnalyzer( , m_bCaseInsensitiveIdentifier(CASE_INSENSITIVE_IDENTIFIER_DEFAULT) , m_bPGIdentifierLaundering(PG_IDENTIFIER_LAUNDERING_DEFAULT) , m_nMaximumFieldsForFlattening(MAXIMUM_FIELDS_FLATTENING_DEFAULT) + , m_bAlwaysGenerateOGRId(ALWAYS_GENERATE_OGR_ID_DEFAULT) { // A few hardcoded namespace uri->prefix mappings m_oMapURIToPrefix[ szXMLNS_URI ] = szXMLNS_PREFIX; @@ -382,7 +383,7 @@ void GMLASSchemaAnalyzer::LaunderFieldNames( GMLASFeatureClass& oClass ) { const CPLString oClassNS = GetNSOfLastXPathComponent(oClass.GetXPath()); - bool bHasDoneRemnamingForThatCase = false; + bool bHasDoneRenamingForThatCase = false; for(size_t i=0; i(aoFields[i].GetName().size()); if( nNameSize > m_nIdentifierMaxLength ) { - aoFields[i].SetName(TruncateIdentifier(aoFields[i].GetName())); + aoFields[i].SetName( + OGRGMLASTruncateIdentifier(aoFields[i].GetName(), + m_nIdentifierMaxLength)); } } } @@ -484,9 +487,10 @@ void GMLASSchemaAnalyzer::LaunderFieldNames( GMLASFeatureClass& oClass ) for(size_t i=0; isecond[i]]; - oField.SetName( AddSerialNumber( oField.GetName(), + oField.SetName( OGRGMLASAddSerialNumber( oField.GetName(), static_cast(i+1), - nOccurrences) ); + nOccurrences, + m_nIdentifierMaxLength) ); } } } @@ -534,7 +538,9 @@ void GMLASSchemaAnalyzer::LaunderClassNames() int nNameSize = static_cast(aoClasses[i]->GetName().size()); if( nNameSize > m_nIdentifierMaxLength ) { - aoClasses[i]->SetName(TruncateIdentifier(aoClasses[i]->GetName())); + aoClasses[i]->SetName(OGRGMLASTruncateIdentifier( + aoClasses[i]->GetName(), + m_nIdentifierMaxLength)); } } } @@ -575,170 +581,15 @@ void GMLASSchemaAnalyzer::LaunderClassNames() for(size_t i=0; isecond[i]]; - poClass->SetName( AddSerialNumber(poClass->GetName(), + poClass->SetName( OGRGMLASAddSerialNumber(poClass->GetName(), static_cast(i+1), - nOccurrences) ); + nOccurrences, + m_nIdentifierMaxLength) ); } } } } -/************************************************************************/ -/* AddSerialNumber() */ -/************************************************************************/ - -CPLString GMLASSchemaAnalyzer::AddSerialNumber(const CPLString& osNameIn, - int iOccurrence, - size_t nOccurrences) -{ - CPLString osName(osNameIn); - const int nDigitsSize = (nOccurrences < 10) ? 1: - (nOccurrences < 100) ? 2 : 3; - char szDigits[4]; - snprintf(szDigits, sizeof(szDigits), "%0*d", - nDigitsSize, iOccurrence); - if( m_nIdentifierMaxLength >= MIN_VALUE_OF_MAX_IDENTIFIER_LENGTH ) - { - if( static_cast(osName.size()) < m_nIdentifierMaxLength ) - { - if( static_cast(osName.size()) + nDigitsSize < - m_nIdentifierMaxLength ) - { - osName += szDigits; - } - else - { - osName.resize(m_nIdentifierMaxLength - nDigitsSize); - osName += szDigits; - } - } - else - { - osName.resize(osName.size() - nDigitsSize); - osName += szDigits; - } - } - else - { - osName += szDigits; - } - return osName; -} - -/************************************************************************/ -/* TruncateIdentifier() */ -/************************************************************************/ - -CPLString GMLASSchemaAnalyzer::TruncateIdentifier(const CPLString& osName) -{ - int nExtra = static_cast(osName.size()) - m_nIdentifierMaxLength; - CPLAssert(nExtra > 0); - - // Decompose in tokens - char** papszTokens = CSLTokenizeString2(osName, "_", - CSLT_ALLOWEMPTYTOKENS ); - std::vector< char > achDelimiters; - std::vector< CPLString > aosTokens; - for( int j=0; papszTokens[j] != NULL; ++j ) - { - const char* pszToken = papszTokens[j]; - bool bIsCamelCase = false; - // Split parts like camelCase or CamelCase into several tokens - if( pszToken[0] != '\0' && islower(pszToken[1]) ) - { - bIsCamelCase = true; - bool bLastIsLower = true; - std::vector aoParts; - CPLString osCurrentPart; - osCurrentPart += pszToken[0]; - osCurrentPart += pszToken[1]; - for( int k=2; pszToken[k]; ++k) - { - if( isupper(pszToken[k]) ) - { - if( !bLastIsLower ) - { - bIsCamelCase = false; - break; - } - aoParts.push_back(osCurrentPart); - osCurrentPart.clear(); - bLastIsLower = false; - } - else - { - bLastIsLower = true; - } - osCurrentPart += pszToken[k]; - } - if( bIsCamelCase ) - { - if( !osCurrentPart.empty() ) - aoParts.push_back(osCurrentPart); - for( size_t k=0; k 0 && k == 0) ? '_' : '\0' ); - aosTokens.push_back( aoParts[k] ); - } - } - } - if( !bIsCamelCase ) - { - achDelimiters.push_back( (j > 0) ? '_' : '\0' ); - aosTokens.push_back( pszToken ); - } - } - CSLDestroy(papszTokens); - - // Truncate identifier by removing last character of longest part - bool bHasDoneSomething = true; - while( nExtra > 0 && bHasDoneSomething ) - { - bHasDoneSomething = false; - int nMaxSize = 0; - size_t nIdxMaxSize = 0; - for( size_t j=0; j < aosTokens.size(); ++j ) - { - int nTokenLen = static_cast(aosTokens[j].size()); - if( nTokenLen > nMaxSize ) - { - // Avoid truncating last token unless it is excessively longer - // than previous ones. - if( j < aosTokens.size() - 1 || - nTokenLen > 2 * nMaxSize ) - { - nMaxSize = nTokenLen; - nIdxMaxSize = j; - } - } - } - - if( nMaxSize > 1 ) - { - aosTokens[nIdxMaxSize].resize( nMaxSize - 1 ); - bHasDoneSomething = true; - nExtra --; - } - } - - // Reassemble truncated parts - CPLString osNewName; - for( size_t j=0; j < aosTokens.size(); ++j ) - { - if( achDelimiters[j] ) - osNewName += achDelimiters[j]; - osNewName += aosTokens[j]; - } - - // If we are still longer than max allowed, truncate beginning of name - if( nExtra > 0 ) - { - osNewName = osNewName.substr(nExtra); - } - CPLAssert( static_cast(osNewName.size()) == m_nIdentifierMaxLength ); - return osNewName; -} - /************************************************************************/ /* GMLASUniquePtr() */ /************************************************************************/ @@ -3276,35 +3127,60 @@ bool GMLASSchemaAnalyzer::ExploreModelGroup( // handle substitutions if( poTargetElt != NULL && !poTargetElt->getAbstract() ) { - // If the element is nillable, then we - // need an extra field to be able to distinguish between the - // case of the missing element or the element with - // xsi:nil="true" - if( poElt->getNillable() && !m_bUseNullState ) + bool bHasRequiredId = false; + XSComplexTypeDefinition* poTargetEltCT = + IsEltCompatibleOfFC(poTargetElt); + if( poTargetEltCT ) { - GMLASField oFieldNil; - oFieldNil.SetName( osPrefixedEltName + "_" + szNIL ); - oFieldNil.SetXPath( osElementXPath + "/" + - szAT_XSI_NIL ); - oFieldNil.SetType( GMLAS_FT_BOOLEAN, "boolean" ); - oFieldNil.SetMinOccurs( 0 ); - oFieldNil.SetMaxOccurs( 1 ); - aoFields.push_back(oFieldNil); + XSAttributeUseList* poTargetEltAttrList = + poTargetEltCT->getAttributeUses(); + const size_t nTEAttrListSize = (poTargetEltAttrList != NULL) ? + poTargetEltAttrList->size(): 0; + for(size_t j=0; j< nTEAttrListSize; ++j ) + { + XSAttributeUse* poTEAttr = poTargetEltAttrList->elementAt(j); + XSAttributeDeclaration* poTEAttrDecl = poTEAttr->getAttrDeclaration(); + XSSimpleTypeDefinition* poTEAttrType = poTEAttrDecl->getTypeDefinition(); + if( transcode(poTEAttrType->getName()) == szXS_ID && + poTEAttr->getRequired() ) + { + bHasRequiredId = true; + break; + } + } } + if( bHasRequiredId && !m_bAlwaysGenerateOGRId ) + { + // If the element is nillable, then we + // need an extra field to be able to distinguish between the + // case of the missing element or the element with + // xsi:nil="true" + if( poElt->getNillable() && !m_bUseNullState ) + { + GMLASField oFieldNil; + oFieldNil.SetName( osPrefixedEltName + "_" + szNIL ); + oFieldNil.SetXPath( osElementXPath + "/" + + szAT_XSI_NIL ); + oFieldNil.SetType( GMLAS_FT_BOOLEAN, "boolean" ); + oFieldNil.SetMinOccurs( 0 ); + oFieldNil.SetMaxOccurs( 1 ); + aoFields.push_back(oFieldNil); + } - GMLASField oField; - // Fake xpath - oField.SetXPath( - GMLASField::MakePKIDFieldXPathFromXLinkHrefXPath( - osElementXPath + "/" + szAT_XLINK_HREF)); - oField.SetName( osPrefixedEltName + szPKID_SUFFIX ); - oField.SetMinOccurs(0); - oField.SetMaxOccurs(1); - oField.SetType( GMLAS_FT_STRING, szXS_STRING ); - oField.SetCategory( - GMLASField::PATH_TO_CHILD_ELEMENT_WITH_LINK ); - oField.SetRelatedClassXPath(osTargetElement); - aoFields.push_back( oField ); + GMLASField oField; + // Fake xpath + oField.SetXPath( + GMLASField::MakePKIDFieldXPathFromXLinkHrefXPath( + osElementXPath + "/" + szAT_XLINK_HREF)); + oField.SetName( osPrefixedEltName + szPKID_SUFFIX ); + oField.SetMinOccurs(0); + oField.SetMaxOccurs(1); + oField.SetType( GMLAS_FT_STRING, szXS_STRING ); + oField.SetCategory( + GMLASField::PATH_TO_CHILD_ELEMENT_WITH_LINK ); + oField.SetRelatedClassXPath(osTargetElement); + aoFields.push_back( oField ); + } } else if( poTargetElt != NULL && poTargetElt->getAbstract() ) { diff --git a/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasutils.cpp b/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasutils.cpp new file mode 100644 index 000000000000..7bb2d66ce11c --- /dev/null +++ b/gdal/ogr/ogrsf_frmts/gmlas/ogrgmlasutils.cpp @@ -0,0 +1,190 @@ +/****************************************************************************** + * Project: OGR + * Purpose: OGRGMLASDriver implementation + * Author: Even Rouault, + * + * Initial development funded by the European Earth observation programme + * Copernicus + * + ****************************************************************************** + * Copyright (c) 2016, Even Rouault, + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "ogr_gmlas.h" + +/************************************************************************/ +/* OGRGMLASTruncateIdentifier() */ +/************************************************************************/ + +CPLString OGRGMLASTruncateIdentifier(const CPLString& osName, + int nIdentMaxLength) +{ + int nExtra = static_cast(osName.size()) - nIdentMaxLength; + CPLAssert(nExtra > 0); + + // Decompose in tokens + char** papszTokens = CSLTokenizeString2(osName, "_", + CSLT_ALLOWEMPTYTOKENS ); + std::vector< char > achDelimiters; + std::vector< CPLString > aosTokens; + for( int j=0; papszTokens[j] != NULL; ++j ) + { + const char* pszToken = papszTokens[j]; + bool bIsCamelCase = false; + // Split parts like camelCase or CamelCase into several tokens + if( pszToken[0] != '\0' && islower(pszToken[1]) ) + { + bIsCamelCase = true; + bool bLastIsLower = true; + std::vector aoParts; + CPLString osCurrentPart; + osCurrentPart += pszToken[0]; + osCurrentPart += pszToken[1]; + for( int k=2; pszToken[k]; ++k) + { + if( isupper(pszToken[k]) ) + { + if( !bLastIsLower ) + { + bIsCamelCase = false; + break; + } + aoParts.push_back(osCurrentPart); + osCurrentPart.clear(); + bLastIsLower = false; + } + else + { + bLastIsLower = true; + } + osCurrentPart += pszToken[k]; + } + if( bIsCamelCase ) + { + if( !osCurrentPart.empty() ) + aoParts.push_back(osCurrentPart); + for( size_t k=0; k 0 && k == 0) ? '_' : '\0' ); + aosTokens.push_back( aoParts[k] ); + } + } + } + if( !bIsCamelCase ) + { + achDelimiters.push_back( (j > 0) ? '_' : '\0' ); + aosTokens.push_back( pszToken ); + } + } + CSLDestroy(papszTokens); + + // Truncate identifier by removing last character of longest part + bool bHasDoneSomething = true; + while( nExtra > 0 && bHasDoneSomething ) + { + bHasDoneSomething = false; + int nMaxSize = 0; + size_t nIdxMaxSize = 0; + for( size_t j=0; j < aosTokens.size(); ++j ) + { + int nTokenLen = static_cast(aosTokens[j].size()); + if( nTokenLen > nMaxSize ) + { + // Avoid truncating last token unless it is excessively longer + // than previous ones. + if( j < aosTokens.size() - 1 || + nTokenLen > 2 * nMaxSize ) + { + nMaxSize = nTokenLen; + nIdxMaxSize = j; + } + } + } + + if( nMaxSize > 1 ) + { + aosTokens[nIdxMaxSize].resize( nMaxSize - 1 ); + bHasDoneSomething = true; + nExtra --; + } + } + + // Reassemble truncated parts + CPLString osNewName; + for( size_t j=0; j < aosTokens.size(); ++j ) + { + if( achDelimiters[j] ) + osNewName += achDelimiters[j]; + osNewName += aosTokens[j]; + } + + // If we are still longer than max allowed, truncate beginning of name + if( nExtra > 0 ) + { + osNewName = osNewName.substr(nExtra); + } + CPLAssert( static_cast(osNewName.size()) == nIdentMaxLength ); + return osNewName; +} + + +/************************************************************************/ +/* OGRGMLASAddSerialNumber() */ +/************************************************************************/ + +CPLString OGRGMLASAddSerialNumber(const CPLString& osNameIn, + int iOccurrence, + size_t nOccurrences, + int nIdentMaxLength) +{ + CPLString osName(osNameIn); + const int nDigitsSize = (nOccurrences < 10) ? 1: + (nOccurrences < 100) ? 2 : 3; + char szDigits[4]; + snprintf(szDigits, sizeof(szDigits), "%0*d", + nDigitsSize, iOccurrence); + if( nIdentMaxLength >= MIN_VALUE_OF_MAX_IDENTIFIER_LENGTH ) + { + if( static_cast(osName.size()) < nIdentMaxLength ) + { + if( static_cast(osName.size()) + nDigitsSize < + nIdentMaxLength ) + { + osName += szDigits; + } + else + { + osName.resize(nIdentMaxLength - nDigitsSize); + osName += szDigits; + } + } + else + { + osName.resize(osName.size() - nDigitsSize); + osName += szDigits; + } + } + else + { + osName += szDigits; + } + return osName; +}