1 /******************************************************************************
2  *
3  * Project:  Interlis 1/2 Translator
4  * Purpose:  IlisMeta model reader.
5  * Author:   Pirmin Kalberer, Sourcepole AG
6  *
7  ******************************************************************************
8  * Copyright (c) 2014, Pirmin Kalberer, Sourcepole AG
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  ****************************************************************************/
28 
29 // IlisMeta model: http://www.interlis.ch/models/core/IlisMeta07-20111222.ili
30 
31 #include "cpl_minixml.h"
32 #include "imdreader.h"
33 
34 #include <set>
35 #include <vector>
36 #include <algorithm>
37 
38 CPL_CVSID("$Id: imdreader.cpp f0237483886cb50fd93f6295263546017ab95d41 2021-03-11 11:42:22 +0100 Even Rouault $")
39 
40 typedef std::map<CPLString,CPLXMLNode*> StrNodeMap;
41 typedef std::vector<CPLXMLNode*> NodeVector;
42 typedef std::map<CPLXMLNode*,int> NodeCountMap;
43 class IliClass;
44 // All classes with XML node for lookup.
45 typedef std::map<CPLXMLNode*,IliClass*> ClassesMap;
46 
47 /* Helper class for collection class infos */
48 class IliClass
49 {
50 public:
51     CPLXMLNode* node;
52     int iliVersion;
53     OGRFeatureDefn* poTableDefn;
54     StrNodeMap& oTidLookup;
55     ClassesMap& oClasses;
56     NodeCountMap& oAxisCount;
57     GeomFieldInfos poGeomFieldInfos;
58     StructFieldInfos poStructFieldInfos;
59     NodeVector oFields;
60     bool isAssocClass;
61     bool hasDerivedClasses;
62 
IliClass(CPLXMLNode * node_,int iliVersion_,StrNodeMap & oTidLookup_,ClassesMap & oClasses_,NodeCountMap & oAxisCount_)63     IliClass( CPLXMLNode* node_, int iliVersion_, StrNodeMap& oTidLookup_,
64               ClassesMap& oClasses_, NodeCountMap& oAxisCount_) :
65         node(node_), iliVersion(iliVersion_), oTidLookup(oTidLookup_),
66         oClasses(oClasses_), oAxisCount(oAxisCount_),
67         poGeomFieldInfos(), poStructFieldInfos(), oFields(),
68         isAssocClass(false), hasDerivedClasses(false)
69     {
70         char* layerName = LayerName();
71         poTableDefn = new OGRFeatureDefn(layerName);
72         poTableDefn->Reference();
73         CPLFree(layerName);
74     }
75 
~IliClass()76     ~IliClass()
77     {
78         poTableDefn->Release();
79     }
80 
GetName()81     const char* GetName() {
82         return poTableDefn->GetName();
83     }
GetIliName()84     const char* GetIliName() {
85         return CPLGetXMLValue( node, "TID", nullptr );
86     }
LayerName()87     char* LayerName() {
88         const char* psClassTID = GetIliName();
89         if (iliVersion == 1)
90         {
91             //Skip topic and replace . with __
92             char **papszTokens =
93                 CSLTokenizeString2( psClassTID, ".", CSLT_ALLOWEMPTYTOKENS );
94 
95             CPLString layername;
96             for(int i = 1; papszTokens != nullptr && papszTokens[i] != nullptr; i++)
97             {
98                 if (i>1) layername += "__";
99                 layername += papszTokens[i];
100             }
101             CSLDestroy( papszTokens );
102             return CPLStrdup(layername);
103         }
104 
105         return CPLStrdup(psClassTID);
106     }
AddFieldNode(CPLXMLNode * nodeIn,int iOrderPos)107     void AddFieldNode(CPLXMLNode* nodeIn, int iOrderPos)
108     {
109         if (iOrderPos >= (int)oFields.size())
110             oFields.resize(iOrderPos+1);
111 #ifdef DEBUG_VERBOSE
112         CPLDebug( "OGR_ILI", "Register field with OrderPos %d to Class %s",
113                   iOrderPos, GetName() );
114 #endif
115         oFields[iOrderPos] = nodeIn;
116     }
AddRoleNode(CPLXMLNode * nodeIn,int iOrderPos)117     void AddRoleNode(CPLXMLNode* nodeIn, int iOrderPos)
118     {
119         isAssocClass = true;
120         AddFieldNode(nodeIn, iOrderPos);
121     }
isEmbedded()122     bool isEmbedded()
123     {
124         if (isAssocClass)
125             for( NodeVector::const_iterator it = oFields.begin();
126                  it != oFields.end();
127                  ++it )
128             {
129                 if (*it == nullptr) continue;
130                 if( CPLTestBool(
131                         CPLGetXMLValue( *it, "EmbeddedTransfer", "FALSE" ) ) )
132                     return true;
133             }
134         return false;
135     }
136     // Add additional Geometry table for Interlis 1
AddGeomTable(CPLString layerName,const char * psFieldName,OGRwkbGeometryType eType,bool bRefTIDField=false)137     void AddGeomTable( CPLString layerName, const char* psFieldName,
138                        OGRwkbGeometryType eType, bool bRefTIDField = false )
139     {
140         OGRFeatureDefn* poGeomTableDefn = new OGRFeatureDefn(layerName);
141         OGRFieldDefn fieldDef("_TID", OFTString);
142         poGeomTableDefn->AddFieldDefn(&fieldDef);
143         if (bRefTIDField)
144         {
145             OGRFieldDefn fieldDefRef("_RefTID", OFTString);
146             poGeomTableDefn->AddFieldDefn(&fieldDefRef);
147         }
148         poGeomTableDefn->DeleteGeomFieldDefn(0);
149         OGRGeomFieldDefn fieldDefGeom(psFieldName, eType);
150         poGeomTableDefn->AddGeomFieldDefn(&fieldDefGeom);
151         CPLDebug( "OGR_ILI", "Adding geometry table %s for field %s",
152                   poGeomTableDefn->GetName(), psFieldName);
153         poGeomFieldInfos[psFieldName].SetGeomTableDefn(poGeomTableDefn);
154     }
AddField(const char * psName,OGRFieldType fieldType)155     void AddField(const char* psName, OGRFieldType fieldType)
156     {
157         OGRFieldDefn fieldDef(psName, fieldType);
158         poTableDefn->AddFieldDefn(&fieldDef);
159         CPLDebug( "OGR_ILI", "Adding field '%s' to Class %s", psName, GetName());
160     }
AddGeomField(const char * psName,OGRwkbGeometryType geomType)161     void AddGeomField(const char* psName, OGRwkbGeometryType geomType)
162     {
163         OGRGeomFieldDefn fieldDef(psName, geomType);
164         //oGFld.SetSpatialRef(geomlayer->GetSpatialRef());
165         poTableDefn->AddGeomFieldDefn(&fieldDef);
166         CPLDebug( "OGR_ILI", "Adding geometry field '%s' to Class %s",
167                   psName, GetName());
168     }
AddCoord(const char * psName,CPLXMLNode * psTypeNode)169     void AddCoord(const char* psName, CPLXMLNode* psTypeNode)
170     {
171         int dim = oAxisCount[psTypeNode];
172         if (dim == 0) dim = 2; //Area center points have no Axis spec
173         if (iliVersion == 1)
174         {
175             for (int i=0; i<dim; i++)
176             {
177                 AddField(CPLSPrintf("%s_%d", psName, i), OFTReal);
178             }
179         }
180         OGRwkbGeometryType geomType = (dim > 2) ? wkbPoint25D : wkbPoint;
181         AddGeomField(psName, geomType);
182     }
GetFormattedType(CPLXMLNode * nodeIn)183     OGRFieldType GetFormattedType(CPLXMLNode* nodeIn)
184     {
185         const char* psRefSuper = CPLGetXMLValue( nodeIn, "Super.REF", nullptr );
186         if (psRefSuper)
187             return GetFormattedType(oTidLookup[psRefSuper]);
188 
189         return OFTString; //TODO: Time, Date, etc. if possible
190     }
InitFieldDefinitions()191     void InitFieldDefinitions()
192     {
193         // Delete default geometry field
194         poTableDefn->DeleteGeomFieldDefn(0);
195 
196         const char* psKind = CPLGetXMLValue( node, "Kind", "" );
197 #ifdef DEBUG_VERBOSE
198         CPLDebug( "OGR_ILI", "InitFieldDefinitions of '%s' kind: %s",
199                   GetName(), psKind );
200 #endif
201         if (EQUAL(psKind, "Structure"))
202         {
203             // add foreign_key field
204             OGRFieldDefn ofieldDefn1("REF_NAME", OFTString);
205             poTableDefn->AddFieldDefn(&ofieldDefn1);
206             OGRFieldDefn ofieldDefn2("REF_ID", OFTString);
207             poTableDefn->AddFieldDefn(&ofieldDefn2);
208         } else { // Class
209             // add TID field
210             const char* psTidColName = (iliVersion == 1) ? "_TID" : "TID";
211             OGRFieldDefn ofieldDefn(psTidColName, OFTString);
212             poTableDefn->AddFieldDefn(&ofieldDefn);
213         }
214         if (CPLTestBool(CPLGetXMLValue( node, "Abstract", "FALSE" )))
215             hasDerivedClasses = true;
216     }
AddFieldDefinitions(NodeVector oArcLineTypes)217     void AddFieldDefinitions(NodeVector oArcLineTypes)
218     {
219         for( NodeVector::const_iterator it = oFields.begin();
220              it != oFields.end();
221              ++it)
222         {
223             if (*it == nullptr) continue;
224             const char* psName = CPLGetXMLValue( *it, "Name", nullptr );
225             if( psName == nullptr )
226                 continue;
227             const char* psTypeRef = CPLGetXMLValue( *it, "Type.REF", nullptr );
228             if (psTypeRef == nullptr) //Assoc Role
229                 AddField(psName, OFTString); //FIXME: numeric?
230             else
231             {
232                 CPLXMLNode* psElementNode = oTidLookup[psTypeRef];
233                 const char* typeName = psElementNode->pszValue;
234                 if (EQUAL(typeName, "IlisMeta07.ModelData.TextType"))
235                 { //Kind Text,MText
236                     AddField(psName, OFTString);
237                 }
238                 else if (EQUAL(typeName, "IlisMeta07.ModelData.EnumType"))
239                 {
240                     AddField(psName, (iliVersion == 1) ? OFTInteger : OFTString);
241                 }
242                 else if (EQUAL(typeName, "IlisMeta07.ModelData.BooleanType"))
243                 {
244                     AddField(psName, OFTString); //??
245                 }
246                 else if (EQUAL(typeName, "IlisMeta07.ModelData.NumType"))
247                 { //// Unit INTERLIS.ANYUNIT, INTERLIS.TIME, INTERLIS.h, INTERLIS.min, INTERLIS.s, INTERLIS.M, INTERLIS.d
248                     AddField(psName, OFTReal);
249                 }
250                 else if (EQUAL(typeName, "IlisMeta07.ModelData.BlackboxType"))
251                 {
252                     AddField(psName, OFTString);
253                 }
254                 else if (EQUAL(typeName, "IlisMeta07.ModelData.FormattedType"))
255                 {
256                     AddField(psName, GetFormattedType(*it));
257                 }
258                 else if (EQUAL(typeName, "IlisMeta07.ModelData.MultiValue"))
259                 {
260                     //min -> Multiplicity/IlisMeta07.ModelData.Multiplicity/Min
261                     //max -> Multiplicity/IlisMeta07.ModelData.Multiplicity/Max
262                     const char* psClassRef = CPLGetXMLValue( psElementNode, "BaseType.REF", nullptr );
263                     if (psClassRef)
264                     {
265                         IliClass* psParentClass = oClasses[oTidLookup[psClassRef]];
266                         poStructFieldInfos[psName] = psParentClass->GetName();
267                         CPLDebug( "OGR_ILI", "Register table %s for struct field '%s'", poStructFieldInfos[psName].c_str(), psName);
268                         /* Option: Embed fields if max == 1
269                         CPLDebug( "OGR_ILI", "Adding embedded struct members of MultiValue field '%s' from Class %s", psName, psClassRef);
270                         AddFieldDefinitions(psParentClass->oFields);
271                         */
272                     }
273                 }
274                 else if (EQUAL(typeName, "IlisMeta07.ModelData.CoordType"))
275                 {
276                     AddCoord(psName, psElementNode);
277                 }
278                 else if (EQUAL(typeName, "IlisMeta07.ModelData.LineType"))
279                 {
280                     const char* psKind = CPLGetXMLValue( psElementNode, "Kind", "" );
281                     poGeomFieldInfos[psName].iliGeomType = psKind;
282                     bool isLinearType = (std::find(oArcLineTypes.begin(), oArcLineTypes.end(), psElementNode) == oArcLineTypes.end());
283                     bool linearGeom = isLinearType || CPLTestBool(CPLGetConfigOption("OGR_STROKE_CURVE", "FALSE"));
284                     OGRwkbGeometryType multiLineType = linearGeom ? wkbMultiLineString : wkbMultiCurve;
285                     OGRwkbGeometryType polyType = linearGeom ? wkbPolygon : wkbCurvePolygon;
286                     if (iliVersion == 1)
287                     {
288                         if (EQUAL(psKind, "Area"))
289                         {
290                             CPLString lineLayerName = GetName() + CPLString("_") + psName;
291                             AddGeomTable(lineLayerName, psName, multiLineType);
292 
293                             //Add geometry field for polygonized areas
294                             AddGeomField(psName, wkbPolygon);
295 
296                             //We add the area helper point geometry after polygon
297                             //for better behavior of clients with limited multi geometry support
298                             CPLString areaPointGeomName = psName + CPLString("__Point");
299                             AddCoord(areaPointGeomName, psElementNode);
300                         } else if (EQUAL(psKind, "Surface"))
301                         {
302                             CPLString geomLayerName = GetName() + CPLString("_") + psName;
303                             AddGeomTable(geomLayerName, psName, multiLineType, true);
304                             AddGeomField(psName, polyType);
305                         } else { // Polyline, DirectedPolyline
306                             AddGeomField(psName, multiLineType);
307                         }
308                     } else {
309                         if (EQUAL(psKind, "Area") || EQUAL(psKind, "Surface"))
310                         {
311                             AddGeomField(psName, polyType);
312                         } else { // Polyline, DirectedPolyline
313                             AddGeomField(psName, multiLineType);
314                         }
315                     }
316                 }
317                 else
318                 {
319                     //ClassRefType
320                     CPLError(CE_Warning, CPLE_NotSupported,
321                         "Field '%s' of class %s has unsupported type %s", psName, GetName(), typeName);
322                 }
323             }
324         }
325     }
tableDefs()326     FeatureDefnInfo tableDefs()
327     {
328         FeatureDefnInfo poLayerInfo;
329         if (!hasDerivedClasses && !isEmbedded())
330         {
331             poLayerInfo.SetTableDefn(poTableDefn);
332             poLayerInfo.poGeomFieldInfos = poGeomFieldInfos;
333         }
334         return poLayerInfo;
335     }
336 
337   private:
338     CPL_DISALLOW_COPY_ASSIGN(IliClass)
339 };
340 
ImdReader(int iliVersionIn)341 ImdReader::ImdReader(int iliVersionIn) :
342     iliVersion(iliVersionIn),
343     modelInfos(),  // TODO(schwehr): Remove.  No need for default ctor, correct?
344     mainModelName("OGR"),
345     mainTopicName("OGR"),
346     codeBlank('_'),
347     codeUndefined('@'),
348     codeContinue('\\')
349 {}
350 
~ImdReader()351 ImdReader::~ImdReader() {}
352 
ReadModel(const char * pszFilename)353 void ImdReader::ReadModel(const char *pszFilename) {
354     CPLDebug( "OGR_ILI", "Reading model '%s'", pszFilename);
355 
356     CPLXMLNode* psRootNode = CPLParseXMLFile(pszFilename);
357     if( psRootNode == nullptr )
358         return;
359     CPLXMLNode *psSectionNode
360         = CPLGetXMLNode( psRootNode, "=TRANSFER.DATASECTION" );
361     if( psSectionNode == nullptr )
362     {
363         CPLDestroyXMLNode(psRootNode);
364         return;
365     }
366 
367     StrNodeMap oTidLookup; /* for fast lookup of REF relations */
368     ClassesMap oClasses;
369     NodeCountMap oAxisCount;
370     NodeVector oArcLineTypes;
371 
372     /* Fill TID lookup map and IliClasses lookup map */
373     CPLXMLNode* psModel = psSectionNode->psChild;
374     while( psModel != nullptr )
375     {
376         const char *modelName = CPLGetXMLValue( psModel, "BID", nullptr );
377 #ifdef DEBUG_VERBOSE
378         CPLDebug( "OGR_ILI", "Model: '%s'", modelName);
379 #endif
380 
381         CPLXMLNode* psEntry = psModel->psChild;
382         while( psEntry != nullptr )
383                 {
384             if (psEntry->eType != CXT_Attribute) //ignore BID
385             {
386 #ifdef DEBUG_VERBOSE
387                 CPLDebug( "OGR_ILI", "Node tag: '%s'", psEntry->pszValue);
388 #endif
389                 const char* psTID = CPLGetXMLValue( psEntry, "TID", nullptr );
390                 if( psTID != nullptr )
391                     oTidLookup[psTID] = psEntry;
392 
393                 if( EQUAL(psEntry->pszValue, "IlisMeta07.ModelData.Model") &&
394                     !EQUAL(modelName, "MODEL.INTERLIS"))
395                 {
396                     IliModelInfo modelInfo;
397                     modelInfo.name = CPLGetXMLValue( psEntry, "Name", "OGR" );
398                     modelInfo.version = CPLGetXMLValue( psEntry, "Version", "" );
399                     modelInfo.uri = CPLGetXMLValue( psEntry, "At", "" );
400                     modelInfos.push_back(modelInfo);
401                     mainModelName = modelInfo.name; // FIXME: check model inheritance
402                     //version = CPLGetXMLValue(psEntry, "iliVersion", "0"); //1 or 2.3
403 
404                     CPLXMLNode *psFormatNode
405                         = CPLGetXMLNode( psEntry, "ili1Format" );
406                     if (psFormatNode != nullptr)
407                     {
408                         psFormatNode = psFormatNode->psChild;
409                         codeBlank = static_cast<char>(
410                             atoi( CPLGetXMLValue(
411                                 psFormatNode, "blankCode", "95")));
412                         codeUndefined = static_cast<char>(
413                             atoi( CPLGetXMLValue(
414                                 psFormatNode, "undefinedCode", "64")));
415                         codeContinue = static_cast<char>(
416                             atoi( CPLGetXMLValue(
417                                 psFormatNode, "continueCode", "92")));
418                     }
419                 }
420                 else if( EQUAL(psEntry->pszValue, "IlisMeta07.ModelData.SubModel"))
421                 {
422                     mainBasketName = CPLGetXMLValue(psEntry, "TID", "OGR");
423                     mainTopicName = CPLGetXMLValue(psEntry, "Name", "OGR");
424                 }
425                 else if( EQUAL(psEntry->pszValue, "IlisMeta07.ModelData.Class") )
426                 {
427                     CPLDebug( "OGR_ILI", "Class name: '%s'", psTID);
428                     oClasses[psEntry] = new IliClass(
429                       psEntry, iliVersion, oTidLookup, oClasses, oAxisCount);
430                 }
431             }
432             psEntry = psEntry->psNext;
433         }
434 
435         // 2nd pass: add fields via TransferElement entries & role associations
436         psEntry = psModel->psChild;
437         while( psEntry != nullptr )
438         {
439             if (psEntry->eType != CXT_Attribute) //ignore BID
440             {
441 #ifdef DEBUG_VERBOSE
442                 CPLDebug( "OGR_ILI", "Node tag: '%s'", psEntry->pszValue);
443 #endif
444                 if( iliVersion == 1 &&
445                     EQUAL( psEntry->pszValue,
446                            "IlisMeta07.ModelData.Ili1TransferElement"))
447                 {
448                     const char* psClassRef = CPLGetXMLValue(
449                       psEntry, "Ili1TransferClass.REF", nullptr );
450                     const char* psElementRef
451                         = CPLGetXMLValue( psEntry, "Ili1RefAttr.REF", nullptr );
452                     int iOrderPos = atoi(CPLGetXMLValue(
453                         psEntry, "Ili1RefAttr.ORDER_POS", "0" ))-1;
454                     IliClass* psParentClass = oClasses[oTidLookup[psClassRef]];
455                     CPLXMLNode* psElementNode = oTidLookup[psElementRef];
456                     psParentClass->AddFieldNode(psElementNode, iOrderPos);
457                 }
458                 else if( EQUAL( psEntry->pszValue,
459                                 "IlisMeta07.ModelData.TransferElement"))
460                 {
461                     const char* psClassRef
462                         = CPLGetXMLValue( psEntry, "TransferClass.REF", nullptr );
463                     const char* psElementRef = CPLGetXMLValue(
464                         psEntry, "TransferElement.REF", nullptr );
465                     int iOrderPos = atoi(CPLGetXMLValue(
466                         psEntry, "TransferElement.ORDER_POS", "0" ))-1;
467                     IliClass* psParentClass = oClasses[oTidLookup[psClassRef]];
468                     CPLXMLNode* psElementNode = oTidLookup[psElementRef];
469                     psParentClass->AddFieldNode(psElementNode, iOrderPos);
470                 }
471                 else if( EQUAL(psEntry->pszValue, "IlisMeta07.ModelData.Role"))
472                 {
473                     const char* psRefParent
474                         = CPLGetXMLValue( psEntry, "Association.REF", nullptr );
475                     int iOrderPos = atoi(
476                         CPLGetXMLValue( psEntry,
477                                         "Association.ORDER_POS", "0" ))-1;
478                     IliClass* psParentClass = oClasses[oTidLookup[psRefParent]];
479                     if (psParentClass)
480                         psParentClass->AddRoleNode(psEntry, iOrderPos);
481                 }
482                 else if( EQUAL(psEntry->pszValue, "IlisMeta07.ModelData.AxisSpec"))
483                 {
484                     const char* psClassRef
485                         = CPLGetXMLValue( psEntry, "CoordType.REF", nullptr );
486                     // int iOrderPos = atoi(
487                     //     CPLGetXMLValue( psEntry, "Axis.ORDER_POS", "0" ))-1;
488                     CPLXMLNode* psCoordTypeNode = oTidLookup[psClassRef];
489                     oAxisCount[psCoordTypeNode] += 1;
490                 }
491                 else if( EQUAL( psEntry->pszValue,
492                                 "IlisMeta07.ModelData.LinesForm"))
493                 {
494                     const char* psLineForm
495                         = CPLGetXMLValue( psEntry, "LineForm.REF", nullptr );
496                     if (EQUAL(psLineForm, "INTERLIS.ARCS")) {
497                         const char* psElementRef
498                             = CPLGetXMLValue( psEntry, "LineType.REF", nullptr );
499                         CPLXMLNode* psElementNode = oTidLookup[psElementRef];
500                         oArcLineTypes.push_back(psElementNode);
501                     }
502                 }
503             }
504             psEntry = psEntry->psNext;
505         }
506 
507         psModel = psModel->psNext;
508     }
509 
510     /* Analyze class inheritance & add fields to class table defn */
511     for( ClassesMap::const_iterator it = oClasses.begin();
512          it != oClasses.end();
513          ++it )
514     {
515 #ifdef DEBUG_VERBOSE
516         CPLDebug( "OGR_ILI", "Class: '%s'", it->second->GetName());
517 #endif
518         const char* psRefSuper = CPLGetXMLValue( it->first, "Super.REF", nullptr );
519         if (psRefSuper) {
520             if (oTidLookup.find(psRefSuper) != oTidLookup.end() &&
521                 oClasses.find(oTidLookup[psRefSuper]) != oClasses.end()) {
522                 oClasses[oTidLookup[psRefSuper]]->hasDerivedClasses = true;
523             } else {
524                 CPLError(CE_Warning, CPLE_AppDefined,
525                     "Couldn't reference super class '%s'", psRefSuper);
526             }
527         }
528         it->second->InitFieldDefinitions();
529         it->second->AddFieldDefinitions(oArcLineTypes);
530     }
531 
532     /* Filter relevant classes */
533     for( ClassesMap::const_iterator it = oClasses.begin();
534          it != oClasses.end();
535          ++it)
536     {
537         const char* className = it->second->GetIliName();
538         FeatureDefnInfo oClassInfo = it->second->tableDefs();
539         if (!STARTS_WITH_CI(className, "INTERLIS.") &&
540             oClassInfo.GetTableDefnRef())
541             featureDefnInfos.push_back(oClassInfo);
542     }
543 
544     for (ClassesMap::iterator it = oClasses.begin(); it != oClasses.end(); ++it)
545     {
546         delete it->second;
547     }
548 
549     CPLDestroyXMLNode(psRootNode);
550 }
551 
GetFeatureDefnInfo(const char * pszLayerName)552 FeatureDefnInfo ImdReader::GetFeatureDefnInfo(const char *pszLayerName) {
553     FeatureDefnInfo featureDefnInfo;
554     for( FeatureDefnInfos::const_iterator it = featureDefnInfos.begin();
555          it != featureDefnInfos.end();
556          ++it )
557     {
558         OGRFeatureDefn* fdefn = it->GetTableDefnRef();
559         if (EQUAL(fdefn->GetName(), pszLayerName)) featureDefnInfo = *it;
560     }
561     return featureDefnInfo;
562 }
563