Passed
Push — develop ( c8cae2...54ad1f )
by Greg
13:40
created

Gedcom   A

Complexity

Total Complexity 9

Size/Duplication

Total Lines 910
Duplicated Lines 0 %

Importance

Changes 31
Bugs 0 Features 3
Metric Value
eloc 801
c 31
b 0
f 3
dl 0
loc 910
rs 9.799
wmc 9

5 Methods

Rating   Name   Duplication   Size   Complexity  
B webtreesSubTags() 0 65 1
B customSubTags() 0 67 3
B gedcom551Tags() 0 491 1
A webtreesTags() 0 45 1
A registerTags() 0 39 3
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2022 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees;
21
22
use Fisharebest\Webtrees\Contracts\ElementFactoryInterface;
23
use Fisharebest\Webtrees\Contracts\ElementInterface;
24
use Fisharebest\Webtrees\CustomTags\Aldfaer;
25
use Fisharebest\Webtrees\CustomTags\Ancestry;
26
use Fisharebest\Webtrees\CustomTags\BrothersKeeper;
27
use Fisharebest\Webtrees\CustomTags\FamilySearch;
28
use Fisharebest\Webtrees\CustomTags\FamilyTreeBuilder;
29
use Fisharebest\Webtrees\CustomTags\FamilyTreeMaker;
30
use Fisharebest\Webtrees\CustomTags\Gedcom7;
31
use Fisharebest\Webtrees\CustomTags\GedcomL;
32
use Fisharebest\Webtrees\CustomTags\Geneatique;
33
use Fisharebest\Webtrees\CustomTags\GenPlusWin;
34
use Fisharebest\Webtrees\CustomTags\Heredis;
35
use Fisharebest\Webtrees\CustomTags\Legacy;
36
use Fisharebest\Webtrees\CustomTags\MyHeritage;
37
use Fisharebest\Webtrees\CustomTags\PersonalAncestralFile;
38
use Fisharebest\Webtrees\CustomTags\PhpGedView;
39
use Fisharebest\Webtrees\CustomTags\ProGen;
40
use Fisharebest\Webtrees\CustomTags\Reunion;
41
use Fisharebest\Webtrees\CustomTags\RootsMagic;
42
use Fisharebest\Webtrees\Elements\AddressCity;
43
use Fisharebest\Webtrees\Elements\AddressCountry;
44
use Fisharebest\Webtrees\Elements\AddressEmail;
45
use Fisharebest\Webtrees\Elements\AddressFax;
46
use Fisharebest\Webtrees\Elements\AddressLine;
47
use Fisharebest\Webtrees\Elements\AddressLine1;
48
use Fisharebest\Webtrees\Elements\AddressLine2;
49
use Fisharebest\Webtrees\Elements\AddressLine3;
50
use Fisharebest\Webtrees\Elements\AddressPostalCode;
51
use Fisharebest\Webtrees\Elements\AddressState;
52
use Fisharebest\Webtrees\Elements\AddressWebPage;
53
use Fisharebest\Webtrees\Elements\AdoptedByWhichParent;
54
use Fisharebest\Webtrees\Elements\Adoption;
55
use Fisharebest\Webtrees\Elements\AdultChristening;
56
use Fisharebest\Webtrees\Elements\AgeAtEvent;
57
use Fisharebest\Webtrees\Elements\AncestralFileNumber;
58
use Fisharebest\Webtrees\Elements\Annulment;
59
use Fisharebest\Webtrees\Elements\ApprovedSystemId;
60
use Fisharebest\Webtrees\Elements\AutomatedRecordId;
61
use Fisharebest\Webtrees\Elements\Baptism;
62
use Fisharebest\Webtrees\Elements\BarMitzvah;
63
use Fisharebest\Webtrees\Elements\BasMitzvah;
64
use Fisharebest\Webtrees\Elements\Birth;
65
use Fisharebest\Webtrees\Elements\Blessing;
66
use Fisharebest\Webtrees\Elements\Burial;
67
use Fisharebest\Webtrees\Elements\CasteName;
68
use Fisharebest\Webtrees\Elements\CauseOfEvent;
69
use Fisharebest\Webtrees\Elements\Census;
70
use Fisharebest\Webtrees\Elements\CertaintyAssessment;
71
use Fisharebest\Webtrees\Elements\Change;
72
use Fisharebest\Webtrees\Elements\ChangeDate;
73
use Fisharebest\Webtrees\Elements\CharacterSet;
74
use Fisharebest\Webtrees\Elements\ChildLinkageStatus;
75
use Fisharebest\Webtrees\Elements\Christening;
76
use Fisharebest\Webtrees\Elements\Confirmation;
77
use Fisharebest\Webtrees\Elements\ContentDescription;
78
use Fisharebest\Webtrees\Elements\Coordinates;
79
use Fisharebest\Webtrees\Elements\CopyrightFile;
80
use Fisharebest\Webtrees\Elements\CopyrightSourceData;
81
use Fisharebest\Webtrees\Elements\CountOfChildren;
82
use Fisharebest\Webtrees\Elements\CountOfMarriages;
83
use Fisharebest\Webtrees\Elements\Creation;
84
use Fisharebest\Webtrees\Elements\Cremation;
85
use Fisharebest\Webtrees\Elements\CustomElement;
86
use Fisharebest\Webtrees\Elements\CustomEvent;
87
use Fisharebest\Webtrees\Elements\CustomFact;
88
use Fisharebest\Webtrees\Elements\CustomFamilyEvent;
89
use Fisharebest\Webtrees\Elements\CustomIndividualEvent;
90
use Fisharebest\Webtrees\Elements\DateLdsOrd;
91
use Fisharebest\Webtrees\Elements\DateValue;
92
use Fisharebest\Webtrees\Elements\DateValueExact;
93
use Fisharebest\Webtrees\Elements\DateValueToday;
94
use Fisharebest\Webtrees\Elements\Death;
95
use Fisharebest\Webtrees\Elements\DescriptiveTitle;
96
use Fisharebest\Webtrees\Elements\Divorce;
97
use Fisharebest\Webtrees\Elements\DivorceFiled;
98
use Fisharebest\Webtrees\Elements\Emigration;
99
use Fisharebest\Webtrees\Elements\EmptyElement;
100
use Fisharebest\Webtrees\Elements\Engagement;
101
use Fisharebest\Webtrees\Elements\EventAttributeType;
102
use Fisharebest\Webtrees\Elements\EventOrFactClassification;
103
use Fisharebest\Webtrees\Elements\EventsRecorded;
104
use Fisharebest\Webtrees\Elements\EventTypeCitedFrom;
105
use Fisharebest\Webtrees\Elements\ExternalIdentifier;
106
use Fisharebest\Webtrees\Elements\ExternalIdentifierType;
107
use Fisharebest\Webtrees\Elements\FamilyCensus;
108
use Fisharebest\Webtrees\Elements\FamilyEvent;
109
use Fisharebest\Webtrees\Elements\FamilyFact;
110
use Fisharebest\Webtrees\Elements\FamilyRecord;
111
use Fisharebest\Webtrees\Elements\FamilyResidence;
112
use Fisharebest\Webtrees\Elements\FamilySearchFamilyTreeId;
113
use Fisharebest\Webtrees\Elements\FamilyStatusText;
114
use Fisharebest\Webtrees\Elements\FileName;
115
use Fisharebest\Webtrees\Elements\FirstCommunion;
116
use Fisharebest\Webtrees\Elements\Form;
117
use Fisharebest\Webtrees\Elements\GedcomElement;
118
use Fisharebest\Webtrees\Elements\GenerationsOfAncestors;
119
use Fisharebest\Webtrees\Elements\GenerationsOfDescendants;
120
use Fisharebest\Webtrees\Elements\GovIdentifier;
121
use Fisharebest\Webtrees\Elements\Graduation;
122
use Fisharebest\Webtrees\Elements\HeaderRecord;
123
use Fisharebest\Webtrees\Elements\HierarchicalRelationship;
124
use Fisharebest\Webtrees\Elements\Immigration;
125
use Fisharebest\Webtrees\Elements\IndividualEvent;
126
use Fisharebest\Webtrees\Elements\IndividualFact;
127
use Fisharebest\Webtrees\Elements\IndividualRecord;
128
use Fisharebest\Webtrees\Elements\LanguageId;
129
use Fisharebest\Webtrees\Elements\LdsBaptism;
130
use Fisharebest\Webtrees\Elements\LdsBaptismDateStatus;
131
use Fisharebest\Webtrees\Elements\LdsChildSealing;
132
use Fisharebest\Webtrees\Elements\LdsChildSealingDateStatus;
133
use Fisharebest\Webtrees\Elements\LdsConfirmation;
134
use Fisharebest\Webtrees\Elements\LdsEndowment;
135
use Fisharebest\Webtrees\Elements\LdsEndowmentDateStatus;
136
use Fisharebest\Webtrees\Elements\LdsInitiatory;
137
use Fisharebest\Webtrees\Elements\LdsOrdinanceStatus;
138
use Fisharebest\Webtrees\Elements\LdsSpouseSealing;
139
use Fisharebest\Webtrees\Elements\LdsSpouseSealingDateStatus;
140
use Fisharebest\Webtrees\Elements\LocationRecord;
141
use Fisharebest\Webtrees\Elements\MaidenheadLocator;
142
use Fisharebest\Webtrees\Elements\Marriage;
143
use Fisharebest\Webtrees\Elements\MarriageBanns;
144
use Fisharebest\Webtrees\Elements\MarriageContract;
145
use Fisharebest\Webtrees\Elements\MarriageLicence;
146
use Fisharebest\Webtrees\Elements\MarriageSettlement;
147
use Fisharebest\Webtrees\Elements\MarriageType;
148
use Fisharebest\Webtrees\Elements\MediaRecord;
149
use Fisharebest\Webtrees\Elements\MultimediaFileReference;
150
use Fisharebest\Webtrees\Elements\MultimediaFormat;
151
use Fisharebest\Webtrees\Elements\NameOfBusiness;
152
use Fisharebest\Webtrees\Elements\NameOfFamilyFile;
153
use Fisharebest\Webtrees\Elements\NameOfProduct;
154
use Fisharebest\Webtrees\Elements\NameOfRepository;
155
use Fisharebest\Webtrees\Elements\NameOfSourceData;
156
use Fisharebest\Webtrees\Elements\NamePersonal;
157
use Fisharebest\Webtrees\Elements\NamePhoneticVariation;
158
use Fisharebest\Webtrees\Elements\NamePieceGiven;
159
use Fisharebest\Webtrees\Elements\NamePieceNickname;
160
use Fisharebest\Webtrees\Elements\NamePiecePrefix;
161
use Fisharebest\Webtrees\Elements\NamePieceSuffix;
162
use Fisharebest\Webtrees\Elements\NamePieceSurname;
163
use Fisharebest\Webtrees\Elements\NamePieceSurnamePrefix;
164
use Fisharebest\Webtrees\Elements\NameRomanizedVariation;
165
use Fisharebest\Webtrees\Elements\NameType;
166
use Fisharebest\Webtrees\Elements\NationalIdNumber;
167
use Fisharebest\Webtrees\Elements\NationalOrTribalOrigin;
168
use Fisharebest\Webtrees\Elements\Naturalization;
169
use Fisharebest\Webtrees\Elements\NobilityTypeTitle;
170
use Fisharebest\Webtrees\Elements\NonEvent;
171
use Fisharebest\Webtrees\Elements\NoteRecord;
172
use Fisharebest\Webtrees\Elements\NoteStructure;
173
use Fisharebest\Webtrees\Elements\Occupation;
174
use Fisharebest\Webtrees\Elements\OrdinanceProcessFlag;
175
use Fisharebest\Webtrees\Elements\Ordination;
176
use Fisharebest\Webtrees\Elements\PafUid;
177
use Fisharebest\Webtrees\Elements\PedigreeLinkageType;
178
use Fisharebest\Webtrees\Elements\PermanentRecordFileNumber;
179
use Fisharebest\Webtrees\Elements\PhoneNumber;
180
use Fisharebest\Webtrees\Elements\PhoneticType;
181
use Fisharebest\Webtrees\Elements\PhysicalDescription;
182
use Fisharebest\Webtrees\Elements\PlaceHierarchy;
183
use Fisharebest\Webtrees\Elements\PlaceLatitude;
184
use Fisharebest\Webtrees\Elements\PlaceLivingOrdinance;
185
use Fisharebest\Webtrees\Elements\PlaceLongtitude;
186
use Fisharebest\Webtrees\Elements\PlaceName;
187
use Fisharebest\Webtrees\Elements\PlacePhoneticVariation;
188
use Fisharebest\Webtrees\Elements\PlaceRomanizedVariation;
189
use Fisharebest\Webtrees\Elements\Possessions;
190
use Fisharebest\Webtrees\Elements\Probate;
191
use Fisharebest\Webtrees\Elements\PublicationDate;
192
use Fisharebest\Webtrees\Elements\ReceivingSystemName;
193
use Fisharebest\Webtrees\Elements\RelationIsDescriptor;
194
use Fisharebest\Webtrees\Elements\ReligiousAffiliation;
195
use Fisharebest\Webtrees\Elements\RepositoryRecord;
196
use Fisharebest\Webtrees\Elements\ResearchTask;
197
use Fisharebest\Webtrees\Elements\ResearchTaskPriority;
198
use Fisharebest\Webtrees\Elements\ResearchTaskStatus;
199
use Fisharebest\Webtrees\Elements\ResearchTaskType;
200
use Fisharebest\Webtrees\Elements\Residence;
201
use Fisharebest\Webtrees\Elements\ResponsibleAgency;
202
use Fisharebest\Webtrees\Elements\RestrictionNotice;
203
use Fisharebest\Webtrees\Elements\Retirement;
204
use Fisharebest\Webtrees\Elements\RoleInEvent;
205
use Fisharebest\Webtrees\Elements\RomanizedType;
206
use Fisharebest\Webtrees\Elements\ScholasticAchievement;
207
use Fisharebest\Webtrees\Elements\SexValue;
208
use Fisharebest\Webtrees\Elements\SexXValue;
209
use Fisharebest\Webtrees\Elements\SocialSecurityNumber;
210
use Fisharebest\Webtrees\Elements\SourceCallNumber;
211
use Fisharebest\Webtrees\Elements\SourceData;
212
use Fisharebest\Webtrees\Elements\SourceFiledByEntry;
213
use Fisharebest\Webtrees\Elements\SourceJurisdictionPlace;
214
use Fisharebest\Webtrees\Elements\SourceMediaType;
215
use Fisharebest\Webtrees\Elements\SourceOriginator;
216
use Fisharebest\Webtrees\Elements\SourcePublicationFacts;
217
use Fisharebest\Webtrees\Elements\SourceRecord;
218
use Fisharebest\Webtrees\Elements\SubmissionRecord;
219
use Fisharebest\Webtrees\Elements\SubmitterName;
220
use Fisharebest\Webtrees\Elements\SubmitterRecord;
221
use Fisharebest\Webtrees\Elements\SubmitterRegisteredRfn;
222
use Fisharebest\Webtrees\Elements\SubmitterText;
223
use Fisharebest\Webtrees\Elements\TempleCode;
224
use Fisharebest\Webtrees\Elements\TextFromSource;
225
use Fisharebest\Webtrees\Elements\TimeValue;
226
use Fisharebest\Webtrees\Elements\TimeValueNow;
227
use Fisharebest\Webtrees\Elements\TransmissionDate;
228
use Fisharebest\Webtrees\Elements\Uid;
229
use Fisharebest\Webtrees\Elements\UserReferenceNumber;
230
use Fisharebest\Webtrees\Elements\UserReferenceType;
231
use Fisharebest\Webtrees\Elements\VersionNumber;
232
use Fisharebest\Webtrees\Elements\WebtreesUser;
233
use Fisharebest\Webtrees\Elements\WhereWithinSource;
234
use Fisharebest\Webtrees\Elements\Will;
235
use Fisharebest\Webtrees\Elements\XrefAssociate;
236
use Fisharebest\Webtrees\Elements\XrefFamily;
237
use Fisharebest\Webtrees\Elements\XrefIndividual;
238
use Fisharebest\Webtrees\Elements\XrefLocation;
239
use Fisharebest\Webtrees\Elements\XrefMedia;
240
use Fisharebest\Webtrees\Elements\XrefRepository;
241
use Fisharebest\Webtrees\Elements\XrefSharedNote;
242
use Fisharebest\Webtrees\Elements\XrefSource;
243
use Fisharebest\Webtrees\Elements\XrefSubmission;
244
use Fisharebest\Webtrees\Elements\XrefSubmitter;
245
246
/**
247
 * GEDCOM 5.5.1 specification
248
 */
249
class Gedcom
250
{
251
    // 255 less the EOL character.
252
    public const LINE_LENGTH = 253;
253
254
    // Gedcom tags which indicate the start of life.
255
    public const BIRTH_EVENTS = ['BIRT', 'CHR', 'BAPM'];
256
257
    // Gedcom tags which indicate the end of life.
258
    public const DEATH_EVENTS = ['DEAT', 'BURI', 'CREM'];
259
260
    // Gedcom tags which indicate the start of a relationship.
261
    public const MARRIAGE_EVENTS = ['MARR', '_NMR'];
262
263
    // Gedcom tags which indicate the end of a relationship.
264
    public const DIVORCE_EVENTS = ['DIV', 'ANUL', '_SEPR'];
265
266
    // Regular expression to match a GEDCOM tag.
267
    public const REGEX_TAG = '[_A-Z][_A-Z0-9]*';
268
269
    // Regular expression to match a GEDCOM XREF.
270
    public const REGEX_XREF = '[A-Za-z0-9:_.-]{1,20}';
271
272
    // Regular expression to match a GEDCOM fact/event for editing raw GEDCOM.
273
    private const REGEX_VALUE   = '( .+)?';
274
    private const REGEX_LEVEL_9 = '\n9 ' . self::REGEX_TAG . self::REGEX_VALUE;
275
    private const REGEX_LEVEL_8 = '\n8 ' . self::REGEX_TAG . self::REGEX_VALUE . '(' . self::REGEX_LEVEL_9 . ')*';
276
    private const REGEX_LEVEL_7 = '\n7 ' . self::REGEX_TAG . self::REGEX_VALUE . '(' . self::REGEX_LEVEL_8 . ')*';
277
    private const REGEX_LEVEL_6 = '\n6 ' . self::REGEX_TAG . self::REGEX_VALUE . '(' . self::REGEX_LEVEL_7 . ')*';
278
    private const REGEX_LEVEL_5 = '\n5 ' . self::REGEX_TAG . self::REGEX_VALUE . '(' . self::REGEX_LEVEL_6 . ')*';
279
    private const REGEX_LEVEL_4 = '\n4 ' . self::REGEX_TAG . self::REGEX_VALUE . '(' . self::REGEX_LEVEL_5 . ')*';
280
    private const REGEX_LEVEL_3 = '\n3 ' . self::REGEX_TAG . self::REGEX_VALUE . '(' . self::REGEX_LEVEL_4 . ')*';
281
    private const REGEX_LEVEL_2 = '\n2 ' . self::REGEX_TAG . self::REGEX_VALUE . '(' . self::REGEX_LEVEL_3 . ')*';
282
    public const REGEX_FACT     = '1 ' . self::REGEX_TAG . self::REGEX_VALUE . '(' . self::REGEX_LEVEL_2 . ')*\n?';
283
284
    // Separates the parts of a place name.
285
    public const PLACE_SEPARATOR = ', ';
286
287
    // Regex to match a (badly formed) GEDCOM place separator.
288
    public const PLACE_SEPARATOR_REGEX = '/ *,[, ]*/';
289
290
    // LATI and LONG tags
291
    public const LATITUDE_NORTH = 'N';
292
    public const LATITUDE_SOUTH = 'S';
293
    public const LONGITUDE_EAST = 'E';
294
    public const LONGITUDE_WEST = 'W';
295
296
    // Not all record types allow a CHAN event.
297
    public const RECORDS_WITH_CHAN = [
298
        Family::RECORD_TYPE,
299
        Individual::RECORD_TYPE,
300
        Media::RECORD_TYPE,
301
        Note::RECORD_TYPE,
302
        Repository::RECORD_TYPE,
303
        Source::RECORD_TYPE,
304
        Submitter::RECORD_TYPE,
305
    ];
306
307
    // These preferences control multiple tag definitions
308
    public const HIDDEN_TAGS = [
309
        // Individual names
310
        'NAME_NPFX'  => ['INDI:NAME:NPFX', 'INDI:NAME:FONE:NPFX', 'INDI:NAME:ROMN:NPFX'],
311
        'NAME_SPFX'  => ['INDI:NAME:SPFX', 'INDI:NAME:FONE:SPFX', 'INDI:NAME:ROMN:SPFX'],
312
        'NAME_NSFX'  => ['INDI:NAME:NSFX', 'INDI:NAME:FONE:NSFX', 'INDI:NAME:ROMN:NSFX'],
313
        'NAME_NICK'  => ['INDI:NAME:NICK', 'INDI:NAME:FONE:NICK', 'INDI:NAME:ROMN:NICK'],
314
        'NAME_FONE'  => ['INDI:NAME:FONE'],
315
        'NAME_ROMN'  => ['INDI:NAME:ROMN'],
316
        'NAME_NOTE'  => ['INDI:NAME:NOTE', 'INDI:NAME:FONE:NOTE', 'INDI:NAME:ROMN:NOTE'],
317
        'NAME_SOUR'  => ['INDI:NAME:SOUR', 'INDI:NAME:FONE:SOUR', 'INDI:NAME:ROMN:SOUR'],
318
        // Places
319
        'PLAC_MAP'   => [':PLAC:MAP'],
320
        'PLAC_FONE'  => [':PLAC:FONE'],
321
        'PLAC_ROMN'  => [':PLAC:ROMN'],
322
        'PLAC_FORM'  => [':PLAC:FORM', 'HEAD:PLAC'],
323
        'PLAC_NOTE'  => [':PLAC:NOTE'],
324
        // Addresses
325
        'ADDR_EMAIL' => [':EMAIL'],
326
        'ADDR_PHON'  => [':PHON'],
327
        'ADDR_WWW'   => [':WWW'],
328
        // Source citations
329
        'SOUR_EVEN'  => [':SOUR:EVEN'],
330
        'SOUR_DATE'  => [':SOUR:DATA:DATE'],
331
        'SOUR_NOTE'  => [':SOUR:NOTE'],
332
        'SOUR_QUAY'  => [':SOUR:QUAY'],
333
        // Sources
334
        'SOUR_DATA'  => ['SOUR:DATA:EVEN', 'SOUR:DATA:AGNC', 'SOUR:DATA:NOTE'],
335
        // Individuals
336
        'BIRT_FAMC'  => ['INDI:BIRT:FAMC'],
337
        'RELI'       => ['INDI:RELI'],
338
        'BAPM'       => ['INDI:BAPM'],
339
        'CHR'        => ['INDI:CHR', 'INDI:CHRA'],
340
        'FCOM'       => ['INDI:FCOM', 'INDI:CONF'],
341
        'ORDN'       => ['INDI:ORDN'],
342
        'BARM'       => ['INDI:BARM', 'INDI:BASM'],
343
        'ALIA'       => ['INDI:ALIA'],
344
        'ASSO'       => ['INDI:ASSO'],
345
        // Families
346
        'ENGA'       => ['FAM:ENGA'],
347
        'MARB'       => ['FAM:MARB'],
348
        'MARC'       => ['FAM:MARC'],
349
        'MARL'       => ['FAM:MARL'],
350
        'MARS'       => ['FAM:MARS'],
351
        'ANUL'       => ['FAM:ANUL'],
352
        'DIVF'       => ['FAM:DIVF'],
353
        'FAM_RESI'   => ['FAM:RESI'],
354
        'FAM_CENS'   => ['FAM:CENS'],
355
        // LDS church
356
        'LDS'        => ['INDI:BAPL', 'INDI:CONL', 'INDI:ENDL', 'INDI:SLGC', 'FAM:SLGS', 'HEAD:SUBN'],
357
        // Identifiers
358
        'AFN'        => ['INDI:AFN'],
359
        'IDNO'       => ['INDI:IDNO'],
360
        'SSN'        => ['INDI:SSN'],
361
        'RFN'        => [':RFN'],
362
        'REFN'       => [':REFN'],
363
        'RIN'        => [':RIN'],
364
        // Submitters
365
        'SUBM'       => ['INDI:SUBM', 'FAM:SUBM'],
366
        'ANCI'       => ['INDI:ANCI', 'INDI:DESI'],
367
    ];
368
369
    // Custom GEDCOM tags that can be created in webtrees.
370
    public const CUSTOM_FAMILY_TAGS = [
371
        'FACT',
372
        '_COML',
373
        '_MARI',
374
        '_MBON',
375
        '_NMR',
376
        '_SEPR',
377
    ];
378
379
    public const CUSTOM_INDIVIDUAL_TAGS = [
380
        '_BRTM',
381
        '_CIRC',
382
        '_DEG',
383
        '_DNA',
384
        '_EXCM',
385
        '_EYEC',
386
        '_FNRL',
387
        '_FSFTID',
388
        '_HAIR',
389
        '_HEIG',
390
        '_INTE',
391
        '_MDCL',
392
        '_MEDC',
393
        '_MILI',
394
        '_MILT',
395
        '_NAMS',
396
        '_NMAR',
397
        '_PRMN',
398
        '_WEIG',
399
        '_YART',
400
    ];
401
402
    // Some applications create GEDCOM files containing records without XREFS.
403
    // We cannot process these.
404
    public const CUSTOM_RECORDS_WITHOUT_XREFS = [
405
        'EMOTIONALRELATIONSHIP', // GenoPro
406
        'GENOMAP', // GenoPro
407
        'GLOBAL', // GenoPro
408
        'LABEL', // GenoPro
409
        'PEDIGREELINK', // GenoPro
410
        'SOCIALRELATIONSHIP', // GenoPro
411
        '_EVDEF', // RootsMagic
412
        '_EVENT_DEFN', // PAF and Legacy
413
        '_HASHTAG_DEFN', // Legacy
414
        '_PUBLISH', // MyHeritage
415
        '_TASK', // Ages
416
        '_TODO', // Legacy
417
    ];
418
419
    /**
420
     * Definitions for GEDCOM 5.5.1.
421
     *
422
     * @return array<string,ElementInterface>
423
     */
424
    private function gedcom551Tags(): array
425
    {
426
        return [
427
            'FAM'                        => new FamilyRecord(I18N::translate('Family')),
428
            'FAM:*:ADDR'                 => new AddressLine(I18N::translate('Address')),
429
            'FAM:*:ADDR:ADR1'            => new AddressLine1(I18N::translate('Address line 1')),
430
            'FAM:*:ADDR:ADR2'            => new AddressLine2(I18N::translate('Address line 2')),
431
            'FAM:*:ADDR:ADR3'            => new AddressLine3(I18N::translate('Address line 3')),
432
            'FAM:*:ADDR:CITY'            => new AddressCity(I18N::translate('City')),
433
            'FAM:*:ADDR:CTRY'            => new AddressCountry(I18N::translate('Country')),
434
            'FAM:*:ADDR:POST'            => new AddressPostalCode(I18N::translate('Postal code')),
435
            'FAM:*:ADDR:STAE'            => new AddressState(I18N::translate('State')),
436
            'FAM:*:AGNC'                 => new ResponsibleAgency(I18N::translate('Agency')),
437
            'FAM:*:CAUS'                 => new CauseOfEvent(I18N::translate('Cause')),
438
            'FAM:*:DATE'                 => new DateValue(I18N::translate('Date')),
439
            'FAM:*:EMAIL'                => new AddressEmail(I18N::translate('Email address')),
440
            'FAM:*:FAX'                  => new AddressFax(I18N::translate('Fax')),
441
            'FAM:*:HUSB'                 => new EmptyElement(I18N::translate('Husband'), ['AGE' => '0:1']),
442
            'FAM:*:HUSB:AGE'             => new AgeAtEvent(I18N::translate('Husband’s age')),
443
            'FAM:*:NOTE'                 => new NoteStructure(I18N::translate('Note')),
444
            'FAM:*:OBJE'                 => new XrefMedia(I18N::translate('Media object')),
445
            'FAM:*:PHON'                 => new PhoneNumber(I18N::translate('Phone')),
446
            'FAM:*:PLAC'                 => new PlaceName(I18N::translate('Place')),
447
            'FAM:*:PLAC:FONE'            => new PlacePhoneticVariation(I18N::translate('Phonetic place')),
448
            'FAM:*:PLAC:FONE:TYPE'       => new PhoneticType(I18N::translate('Type')),
449
            'FAM:*:PLAC:FORM'            => new PlaceHierarchy(I18N::translate('Format')),
450
            'FAM:*:PLAC:MAP'             => new Coordinates(I18N::translate('Coordinates')),
451
            'FAM:*:PLAC:MAP:LATI'        => new PlaceLatitude(I18N::translate('Latitude')),
452
            'FAM:*:PLAC:MAP:LONG'        => new PlaceLongtitude(I18N::translate('Longitude')),
453
            'FAM:*:PLAC:NOTE'            => new NoteStructure(I18N::translate('Note on place')),
454
            'FAM:*:PLAC:ROMN'            => new PlaceRomanizedVariation(I18N::translate('Romanized place')),
455
            'FAM:*:PLAC:ROMN:TYPE'       => new RomanizedType(I18N::translate('Type')),
456
            'FAM:*:RELI'                 => new ReligiousAffiliation(I18N::translate('Religion'), []),
457
            'FAM:*:RESN'                 => new RestrictionNotice(I18N::translate('Restriction')),
458
            'FAM:*:SOUR'                 => new XrefSource(I18N::translate('Source citation')),
459
            'FAM:*:SOUR:DATA'            => new SourceData(I18N::translate('Data')),
460
            'FAM:*:SOUR:DATA:DATE'       => new DateValue(I18N::translate('Date of entry in original source')),
461
            'FAM:*:SOUR:DATA:TEXT'       => new TextFromSource(I18N::translate('Text')),
462
            'FAM:*:SOUR:EVEN'            => new EventTypeCitedFrom(I18N::translate('Event')),
463
            'FAM:*:SOUR:EVEN:ROLE'       => new RoleInEvent(I18N::translate('Role')),
464
            'FAM:*:SOUR:NOTE'            => new NoteStructure(I18N::translate('Note on source citation')),
465
            'FAM:*:SOUR:OBJE'            => new XrefMedia(I18N::translate('Media object')),
466
            'FAM:*:SOUR:PAGE'            => new WhereWithinSource(I18N::translate('Citation details')),
467
            'FAM:*:SOUR:QUAY'            => new CertaintyAssessment(I18N::translate('Quality of data')),
468
            'FAM:*:TYPE'                 => new EventOrFactClassification(I18N::translate('Type')),
469
            'FAM:*:WIFE'                 => new EmptyElement(I18N::translate('Wife'), ['AGE' => '0:1']),
470
            'FAM:*:WIFE:AGE'             => new AgeAtEvent(I18N::translate('Wife’s age')),
471
            'FAM:*:WWW'                  => new AddressWebPage(I18N::translate('URL')),
472
            'FAM:ANUL'                   => new Annulment(I18N::translate('Annulment')),
473
            'FAM:CENS'                   => new FamilyCensus(I18N::translate('Family census')),
474
            'FAM:CHAN'                   => new Change(I18N::translate('Last change')),
475
            'FAM:CHAN:DATE'              => new ChangeDate(I18N::translate('Date of last change')),
476
            'FAM:CHAN:DATE:TIME'         => new TimeValueNow(I18N::translate('Time of last change')),
477
            'FAM:CHAN:NOTE'              => new NoteStructure(I18N::translate('Note on last change')),
478
            'FAM:CHIL'                   => new XrefIndividual(I18N::translate('Child')),
479
            'FAM:DIV'                    => new Divorce(I18N::translate('Divorce')),
480
            'FAM:DIV:DATE'               => new DateValue(I18N::translate('Date of divorce')),
481
            'FAM:DIVF'                   => new DivorceFiled(I18N::translate('Divorce filed')),
482
            'FAM:ENGA'                   => new Engagement(I18N::translate('Engagement')),
483
            'FAM:ENGA:DATE'              => new DateValue(I18N::translate('Date of engagement')),
484
            'FAM:ENGA:PLAC'              => new PlaceName(I18N::translate('Place of engagement')),
485
            'FAM:EVEN'                   => new FamilyEvent(I18N::translate('Event')),
486
            'FAM:EVEN:TYPE'              => new EventOrFactClassification(I18N::translate('Type of event')),
487
            'FAM:HUSB'                   => new XrefIndividual(I18N::translate('Husband')),
488
            'FAM:MARB'                   => new MarriageBanns(I18N::translate('Marriage banns')),
489
            'FAM:MARB:DATE'              => new DateValue(I18N::translate('Date of marriage banns')),
490
            'FAM:MARB:PLAC'              => new PlaceName(I18N::translate('Place of marriage banns')),
491
            'FAM:MARC'                   => new MarriageContract(I18N::translate('Marriage contract')),
492
            'FAM:MARL'                   => new MarriageLicence(I18N::translate('Marriage license')),
493
            'FAM:MARR'                   => new Marriage(I18N::translate('Marriage')),
494
            'FAM:MARR:DATE'              => new DateValue(I18N::translate('Date of marriage')),
495
            'FAM:MARR:PLAC'              => new PlaceName(I18N::translate('Place of marriage')),
496
            'FAM:MARR:TYPE'              => new MarriageType(I18N::translate('Type of marriage')),
497
            'FAM:MARS'                   => new MarriageSettlement(I18N::translate('Marriage settlement')),
498
            'FAM:NCHI'                   => new CountOfChildren(I18N::translate('Number of children')),
499
            'FAM:NOTE'                   => new NoteStructure(I18N::translate('Note')),
500
            'FAM:OBJE'                   => new XrefMedia(I18N::translate('Media object')),
501
            'FAM:REFN'                   => new UserReferenceNumber(I18N::translate('Reference number')),
502
            'FAM:REFN:TYPE'              => new UserReferenceType(I18N::translate('Type of reference number')),
503
            'FAM:RESI'                   => new FamilyResidence(I18N::translate('Family residence')),
504
            'FAM:RESN'                   => new RestrictionNotice(I18N::translate('Restriction')),
505
            'FAM:RIN'                    => new AutomatedRecordId(I18N::translate('Record ID number')),
506
            'FAM:SLGS'                   => new LdsSpouseSealing(I18N::translate('LDS spouse sealing')),
507
            'FAM:SLGS:DATE'              => new DateLdsOrd(I18N::translate('Date of LDS spouse sealing')),
508
            'FAM:SLGS:PLAC'              => new PlaceLivingOrdinance(I18N::translate('Place of LDS spouse sealing')),
509
            'FAM:SLGS:STAT'              => new LdsSpouseSealingDateStatus(I18N::translate('Status')),
510
            'FAM:SLGS:STAT:DATE'         => new DateValueExact(I18N::translate('Status change date')),
511
            'FAM:SLGS:TEMP'              => new TempleCode(I18N::translate('Temple')),
512
            'FAM:SOUR'                   => new XrefSource(I18N::translate('Source citation')),
513
            'FAM:SOUR:DATA'              => new SourceData(I18N::translate('Data')),
514
            'FAM:SOUR:DATA:DATE'         => new DateValue(I18N::translate('Date of entry in original source')),
515
            'FAM:SOUR:DATA:TEXT'         => new TextFromSource(I18N::translate('Text')),
516
            'FAM:SOUR:EVEN'              => new EventTypeCitedFrom(I18N::translate('Event')),
517
            'FAM:SOUR:EVEN:ROLE'         => new RoleInEvent(I18N::translate('Role')),
518
            'FAM:SOUR:NOTE'              => new NoteStructure(I18N::translate('Note on source citation')),
519
            'FAM:SOUR:OBJE'              => new XrefMedia(I18N::translate('Media object')),
520
            'FAM:SOUR:PAGE'              => new WhereWithinSource(I18N::translate('Citation details')),
521
            'FAM:SOUR:QUAY'              => new CertaintyAssessment(I18N::translate('Quality of data')),
522
            'FAM:SUBM'                   => new XrefSubmitter(I18N::translate('Submitter')),
523
            'FAM:WIFE'                   => new XrefIndividual(I18N::translate('Wife')),
524
            'HEAD'                       => new HeaderRecord(I18N::translate('Header')),
525
            'HEAD:CHAR'                  => new CharacterSet(I18N::translate('Character set')),
526
            'HEAD:CHAR:VERS'             => new VersionNumber(I18N::translate('Version')),
527
            'HEAD:COPR'                  => new CopyrightFile(I18N::translate('Copyright')),
528
            'HEAD:DATE'                  => new TransmissionDate(I18N::translate('Date')),
529
            'HEAD:DATE:TIME'             => new TimeValueNow(I18N::translate('Time')),
530
            'HEAD:DEST'                  => new ReceivingSystemName(I18N::translate('Destination')),
531
            'HEAD:FILE'                  => new FileName(I18N::translate('Filename')),
532
            'HEAD:GEDC'                  => new GedcomElement(I18N::translate('GEDCOM')),
533
            'HEAD:GEDC:FORM'             => new Form(I18N::translate('Format')),
534
            'HEAD:GEDC:VERS'             => new VersionNumber(I18N::translate('Version')),
535
            'HEAD:LANG'                  => new LanguageId(I18N::translate('Language')),
536
            'HEAD:NOTE'                  => new ContentDescription(I18N::translate('Note')),
537
            'HEAD:PLAC'                  => new EmptyElement(I18N::translate('Place hierarchy'), ['FORM' => '1:1']),
538
            'HEAD:PLAC:FORM'             => new PlaceHierarchy(I18N::translate('Format')),
539
            'HEAD:SOUR'                  => new ApprovedSystemId(I18N::translate('Application ID')),
540
            'HEAD:SOUR:CORP'             => new NameOfBusiness(I18N::translate('Corporation')),
541
            'HEAD:SOUR:CORP:ADDR'        => new AddressLine(I18N::translate('Address')),
542
            'HEAD:SOUR:CORP:ADDR:ADR1'   => new AddressLine1(I18N::translate('Address line 1')),
543
            'HEAD:SOUR:CORP:ADDR:ADR2'   => new AddressLine2(I18N::translate('Address line 2')),
544
            'HEAD:SOUR:CORP:ADDR:ADR3'   => new AddressLine3(I18N::translate('Address line 3')),
545
            'HEAD:SOUR:CORP:ADDR:CITY'   => new AddressCity(I18N::translate('City')),
546
            'HEAD:SOUR:CORP:ADDR:CTRY'   => new AddressCountry(I18N::translate('Country')),
547
            'HEAD:SOUR:CORP:ADDR:POST'   => new AddressPostalCode(I18N::translate('Postal code')),
548
            'HEAD:SOUR:CORP:ADDR:STAE'   => new AddressState(I18N::translate('State')),
549
            'HEAD:SOUR:CORP:EMAIL'       => new AddressEmail(I18N::translate('Email address')),
550
            'HEAD:SOUR:CORP:FAX'         => new AddressFax(I18N::translate('Fax')),
551
            'HEAD:SOUR:CORP:PHON'        => new PhoneNumber(I18N::translate('Phone')),
552
            'HEAD:SOUR:CORP:WWW'         => new AddressWebPage(I18N::translate('URL')),
553
            'HEAD:SOUR:DATA'             => new NameOfSourceData(I18N::translate('Data')),
554
            'HEAD:SOUR:DATA:COPR'        => new CopyrightSourceData(I18N::translate('Copyright')),
555
            'HEAD:SOUR:DATA:DATE'        => new PublicationDate(I18N::translate('Date')),
556
            'HEAD:SOUR:NAME'             => new NameOfProduct(I18N::translate('Application name')),
557
            'HEAD:SOUR:VERS'             => new VersionNumber(I18N::translate('Version')),
558
            'HEAD:SUBM'                  => new XrefSubmitter(I18N::translate('Submitter')),
559
            'HEAD:SUBN'                  => new XrefSubmission(I18N::translate('Submission')),
560
            'INDI'                       => new IndividualRecord(I18N::translate('Individual')),
561
            'INDI:*:ADDR'                => new AddressLine(I18N::translate('Address')),
562
            'INDI:*:ADDR:ADR1'           => new AddressLine1(I18N::translate('Address line 1')),
563
            'INDI:*:ADDR:ADR2'           => new AddressLine2(I18N::translate('Address line 2')),
564
            'INDI:*:ADDR:ADR3'           => new AddressLine3(I18N::translate('Address line 3')),
565
            'INDI:*:ADDR:CITY'           => new AddressCity(I18N::translate('City')),
566
            'INDI:*:ADDR:CTRY'           => new AddressCountry(I18N::translate('Country')),
567
            'INDI:*:ADDR:POST'           => new AddressPostalCode(I18N::translate('Postal code')),
568
            'INDI:*:ADDR:STAE'           => new AddressState(I18N::translate('State')),
569
            'INDI:*:AGE'                 => new AgeAtEvent(I18N::translate('Age')),
570
            'INDI:*:AGNC'                => new ResponsibleAgency(I18N::translate('Agency')),
571
            'INDI:*:CAUS'                => new CauseOfEvent(I18N::translate('Cause')),
572
            'INDI:*:DATE'                => new DateValue(I18N::translate('Date')),
573
            'INDI:*:EMAIL'               => new AddressEmail(I18N::translate('Email address')),
574
            'INDI:*:FAX'                 => new AddressFax(I18N::translate('Fax')),
575
            'INDI:*:NOTE'                => new NoteStructure(I18N::translate('Note')),
576
            'INDI:*:OBJE'                => new XrefMedia(I18N::translate('Media object')),
577
            'INDI:*:PHON'                => new PhoneNumber(I18N::translate('Phone')),
578
            'INDI:*:PLAC'                => new PlaceName(I18N::translate('Place')),
579
            'INDI:*:PLAC:FONE'           => new PlacePhoneticVariation(I18N::translate('Phonetic place')),
580
            'INDI:*:PLAC:FONE:TYPE'      => new PhoneticType(I18N::translate('Type')),
581
            'INDI:*:PLAC:FORM'           => new PlaceHierarchy(I18N::translate('Format')),
582
            'INDI:*:PLAC:MAP'            => new Coordinates(I18N::translate('Coordinates')),
583
            'INDI:*:PLAC:MAP:LATI'       => new PlaceLatitude(I18N::translate('Latitude')),
584
            'INDI:*:PLAC:MAP:LONG'       => new PlaceLongtitude(I18N::translate('Longitude')),
585
            'INDI:*:PLAC:NOTE'           => new NoteStructure(I18N::translate('Note on place')),
586
            'INDI:*:PLAC:ROMN'           => new PlaceRomanizedVariation(I18N::translate('Romanized place')),
587
            'INDI:*:PLAC:ROMN:TYPE'      => new RomanizedType(I18N::translate('Type')),
588
            'INDI:*:RELI'                => new ReligiousAffiliation(I18N::translate('Religion'), []),
589
            'INDI:*:RESN'                => new RestrictionNotice(I18N::translate('Restriction')),
590
            'INDI:*:SOUR'                => new XrefSource(I18N::translate('Source citation')),
591
            'INDI:*:SOUR:DATA'           => new SourceData(I18N::translate('Data')),
592
            'INDI:*:SOUR:DATA:DATE'      => new DateValue(I18N::translate('Date of entry in original source')),
593
            'INDI:*:SOUR:DATA:TEXT'      => new TextFromSource(I18N::translate('Text')),
594
            'INDI:*:SOUR:EVEN'           => new EventTypeCitedFrom(I18N::translate('Event')),
595
            'INDI:*:SOUR:EVEN:ROLE'      => new RoleInEvent(I18N::translate('Role')),
596
            'INDI:*:SOUR:NOTE'           => new NoteStructure(I18N::translate('Note on source citation')),
597
            'INDI:*:SOUR:OBJE'           => new XrefMedia(I18N::translate('Media object')),
598
            'INDI:*:SOUR:PAGE'           => new WhereWithinSource(I18N::translate('Citation details')),
599
            'INDI:*:SOUR:QUAY'           => new CertaintyAssessment(I18N::translate('Quality of data')),
600
            'INDI:*:TYPE'                => new EventOrFactClassification(I18N::translate('Type')),
601
            'INDI:*:WWW'                 => new AddressWebPage(I18N::translate('URL')),
602
            'INDI:ADOP'                  => new Adoption(I18N::translate('Adoption')),
603
            'INDI:ADOP:DATE'             => new DateValue(I18N::translate('Date of adoption')),
604
            'INDI:ADOP:FAMC'             => new XrefFamily(I18N::translate('Adoptive parents')),
605
            'INDI:ADOP:FAMC:ADOP'        => new AdoptedByWhichParent(I18N::translate('Adoption')),
606
            'INDI:ADOP:PLAC'             => new PlaceName(I18N::translate('Place of adoption')),
607
            'INDI:AFN'                   => new AncestralFileNumber(I18N::translate('Ancestral file number')),
608
            'INDI:ALIA'                  => new XrefIndividual(I18N::translate('Alias')),
609
            'INDI:ANCI'                  => new XrefSubmitter(I18N::translate('Ancestors interest')),
610
            'INDI:ASSO'                  => new XrefAssociate(I18N::translate('Associate')),
611
            'INDI:ASSO:RELA'             => new RelationIsDescriptor(I18N::translate('Relationship')),
612
            'INDI:BAPL'                  => new LdsBaptism(I18N::translate('LDS baptism')),
613
            'INDI:BAPL:DATE'             => new DateLdsOrd(I18N::translate('Date of LDS baptism')),
614
            'INDI:BAPL:PLAC'             => new PlaceLivingOrdinance(I18N::translate('Place of LDS baptism')),
615
            'INDI:BAPL:STAT'             => new LdsBaptismDateStatus(I18N::translate('Status')),
616
            'INDI:BAPL:STAT:DATE'        => new DateValueExact(I18N::translate('Status change date')),
617
            'INDI:BAPL:TEMP'             => new TempleCode(I18N::translate('Temple')),
618
            'INDI:BAPM'                  => new Baptism(I18N::translate('Baptism')),
619
            'INDI:BAPM:DATE'             => new DateValue(I18N::translate('Date of baptism')),
620
            'INDI:BAPM:PLAC'             => new PlaceName(I18N::translate('Place of baptism')),
621
            'INDI:BARM'                  => new BarMitzvah(I18N::translate('Bar mitzvah')),
622
            'INDI:BARM:DATE'             => new DateValue(I18N::translate('Date of bar mitzvah')),
623
            'INDI:BARM:PLAC'             => new PlaceName(I18N::translate('Place of bar mitzvah')),
624
            'INDI:BASM'                  => new BasMitzvah(I18N::translate('Bat mitzvah')),
625
            'INDI:BASM:DATE'             => new BasMitzvah(I18N::translate('Date of bat mitzvah')),
626
            'INDI:BASM:PLAC'             => new DateValue(I18N::translate('Place of bat mitzvah')),
627
            'INDI:BIRT'                  => new Birth(I18N::translate('Birth')),
628
            'INDI:BIRT:DATE'             => new DateValue(I18N::translate('Date of birth')),
629
            'INDI:BIRT:FAMC'             => new XrefFamily(I18N::translate('Birth parents')),
630
            'INDI:BIRT:PLAC'             => new PlaceName(I18N::translate('Place of birth')),
631
            'INDI:BLES'                  => new Blessing(I18N::translate('Blessing')),
632
            'INDI:BLES:DATE'             => new DateValue(I18N::translate('Date of blessing')),
633
            'INDI:BLES:PLAC'             => new PlaceName(I18N::translate('Place of blessing')),
634
            'INDI:BURI'                  => new Burial(I18N::translate('Burial')),
635
            'INDI:BURI:DATE'             => new DateValue(I18N::translate('Date of burial')),
636
            'INDI:BURI:PLAC'             => new PlaceName(I18N::translate('Place of burial')),
637
            'INDI:CAST'                  => new CasteName(I18N::translate('Caste')),
638
            'INDI:CENS'                  => new Census(I18N::translate('Census')),
639
            'INDI:CENS:DATE'             => new DateValue(I18N::translate('Census date')),
640
            'INDI:CENS:PLAC'             => new PlaceName(I18N::translate('Census place')),
641
            'INDI:CHAN'                  => new Change(I18N::translate('Last change')),
642
            'INDI:CHAN:DATE'             => new ChangeDate(I18N::translate('Date of last change')),
643
            'INDI:CHAN:DATE:TIME'        => new TimeValueNow(I18N::translate('Time of last change')),
644
            'INDI:CHAN:NOTE'             => new NoteStructure(I18N::translate('Note on last change')),
645
            'INDI:CHR'                   => new Christening(I18N::translate('Christening')),
646
            'INDI:CHR:DATE'              => new DateValue(I18N::translate('Date of christening')),
647
            'INDI:CHR:FAMC'              => new XrefFamily(I18N::translate('Godparents')),
648
            'INDI:CHR:PLAC'              => new PlaceName(I18N::translate('Place of christening')),
649
            'INDI:CHRA'                  => new AdultChristening(I18N::translate('Adult christening')),
650
            'INDI:CHRA:PLAC'             => new PlaceName(I18N::translate('Place of christening')),
651
            'INDI:CONF'                  => new Confirmation(I18N::translate('Confirmation')),
652
            'INDI:CONF:DATE'             => new DateValue(I18N::translate('Date of confirmation')),
653
            'INDI:CONF:PLAC'             => new PlaceName(I18N::translate('Place of confirmation')),
654
            'INDI:CONL'                  => new LdsConfirmation(I18N::translate('LDS confirmation')),
655
            'INDI:CONL:DATE'             => new DateLdsOrd(I18N::translate('Date of LDS confirmation')),
656
            'INDI:CONL:PLAC'             => new PlaceLivingOrdinance(I18N::translate('Place of LDS confirmation')),
657
            'INDI:CONL:STAT'             => new LdsBaptismDateStatus(I18N::translate('Status')),
658
            'INDI:CONL:STAT:DATE'        => new DateValueExact(I18N::translate('Status change date')),
659
            'INDI:CONL:TEMP'             => new TempleCode(I18N::translate('Temple')),
660
            'INDI:CREM'                  => new Cremation(I18N::translate('Cremation')),
661
            'INDI:CREM:DATE'             => new DateValue(I18N::translate('Date of cremation')),
662
            'INDI:CREM:PLAC'             => new PlaceName(I18N::translate('Place of cremation')),
663
            'INDI:DEAT'                  => new Death(I18N::translate('Death')),
664
            'INDI:DEAT:CAUS'             => new CauseOfEvent(I18N::translate('Cause of death')),
665
            'INDI:DEAT:DATE'             => new DateValue(I18N::translate('Date of death')),
666
            'INDI:DEAT:PLAC'             => new PlaceName(I18N::translate('Place of death')),
667
            'INDI:DESI'                  => new XrefSubmitter(I18N::translate('Descendants interest')),
668
            'INDI:DSCR'                  => new PhysicalDescription(I18N::translate('Description')),
669
            'INDI:EDUC'                  => new ScholasticAchievement(I18N::translate('Education')),
670
            'INDI:EDUC:AGNC'             => new ResponsibleAgency(I18N::translate('School or college')),
671
            'INDI:EMIG'                  => new Emigration(I18N::translate('Emigration')),
672
            'INDI:EMIG:DATE'             => new DateValue(I18N::translate('Date of emigration')),
673
            'INDI:EMIG:PLAC'             => new PlaceName(I18N::translate('Place of emigration')),
674
            'INDI:ENDL'                  => new LdsEndowment(I18N::translate('LDS endowment')),
675
            'INDI:ENDL:DATE'             => new DateLdsOrd(I18N::translate('Date of LDS endowment')),
676
            'INDI:ENDL:PLAC'             => new PlaceLivingOrdinance(I18N::translate('Place of LDS endowment')),
677
            'INDI:ENDL:STAT'             => new LdsEndowmentDateStatus(I18N::translate('Status')),
678
            'INDI:ENDL:STAT:DATE'        => new DateValueExact(I18N::translate('Status change date')),
679
            'INDI:ENDL:TEMP'             => new TempleCode(I18N::translate('Temple')),
680
            'INDI:EVEN'                  => new IndividualEvent(I18N::translate('Event')),
681
            'INDI:EVEN:DATE'             => new DateValue(I18N::translate('Date of event')),
682
            'INDI:EVEN:PLAC'             => new PlaceName(I18N::translate('Place of event')),
683
            'INDI:EVEN:TYPE'             => new EventOrFactClassification(I18N::translate('Type of event')),
684
            'INDI:FACT'                  => new IndividualFact(I18N::translate('Fact')),
685
            'INDI:FACT:TYPE'             => new EventOrFactClassification(I18N::translate('Type of fact')),
686
            'INDI:FAMC'                  => new XrefFamily(I18N::translate('Family as a child'), ['NOTE' => '0:1', 'PEDI' => '0:1', 'STAT' => '0:1']),
687
            'INDI:FAMC:PEDI'             => new PedigreeLinkageType(I18N::translate('Relationship to parents')),
688
            'INDI:FAMC:STAT'             => new ChildLinkageStatus(I18N::translate('Status')),
689
            'INDI:FAMS'                  => new XrefFamily(I18N::translate('Family as a spouse')),
690
            'INDI:FCOM'                  => new FirstCommunion(I18N::translate('First communion')),
691
            'INDI:FCOM:DATE'             => new DateValue(I18N::translate('Date of first communion')),
692
            'INDI:FCOM:PLAC'             => new PlaceName(I18N::translate('Place of first communion')),
693
            'INDI:GRAD'                  => new Graduation(I18N::translate('Graduation')),
694
            'INDI:GRAD:AGNC'             => new ResponsibleAgency(I18N::translate('School or college')),
695
            'INDI:IDNO'                  => new NationalIdNumber(I18N::translate('Identification number')),
696
            'INDI:IDNO:TYPE'             => new EventOrFactClassification(I18N::translate('Type of identification number')),
697
            'INDI:IMMI'                  => new Immigration(I18N::translate('Immigration')),
698
            'INDI:IMMI:DATE'             => new DateValue(I18N::translate('Date of immigration')),
699
            'INDI:IMMI:PLAC'             => new PlaceName(I18N::translate('Place of immigration')),
700
            'INDI:NAME'                  => new NamePersonal(I18N::translate('Name')),
701
            'INDI:NAME:*:SOUR'           => new XrefSource(I18N::translate('Source citation')),
702
            'INDI:NAME:*:SOUR:DATA'      => new SourceData(I18N::translate('Data')),
703
            'INDI:NAME:*:SOUR:DATA:DATE' => new DateValue(I18N::translate('Date of entry in original source')),
704
            'INDI:NAME:*:SOUR:DATA:TEXT' => new TextFromSource(I18N::translate('Text')),
705
            'INDI:NAME:*:SOUR:EVEN'      => new EventTypeCitedFrom(I18N::translate('Event')),
706
            'INDI:NAME:*:SOUR:EVEN:ROLE' => new RoleInEvent(I18N::translate('Role')),
707
            'INDI:NAME:*:SOUR:NOTE'      => new NoteStructure(I18N::translate('Note on source citation')),
708
            'INDI:NAME:*:SOUR:OBJE'      => new XrefMedia(I18N::translate('Media object')),
709
            'INDI:NAME:*:SOUR:PAGE'      => new WhereWithinSource(I18N::translate('Citation details')),
710
            'INDI:NAME:*:SOUR:QUAY'      => new CertaintyAssessment(I18N::translate('Quality of data')),
711
            'INDI:NAME:FONE'             => new NamePhoneticVariation(I18N::translate('Phonetic name')),
712
            'INDI:NAME:FONE:GIVN'        => new NamePieceGiven(I18N::translate('Given names')),
713
            'INDI:NAME:FONE:NICK'        => new NamePieceNickname(I18N::translate('Nickname')),
714
            'INDI:NAME:FONE:NOTE'        => new NoteStructure(I18N::translate('Note on phonetic name')),
715
            'INDI:NAME:FONE:NPFX'        => new NamePiecePrefix(I18N::translate('Name prefix')),
716
            'INDI:NAME:FONE:NSFX'        => new NamePieceSuffix(I18N::translate('Name suffix')),
717
            'INDI:NAME:FONE:SOUR'        => new XrefSource(I18N::translate('Source citation')),
718
            'INDI:NAME:FONE:SPFX'        => new NamePieceSurnamePrefix(I18N::translate('Surname prefix')),
719
            'INDI:NAME:FONE:SURN'        => new NamePieceSurname(I18N::translate('Surname')),
720
            'INDI:NAME:FONE:TYPE'        => new PhoneticType(I18N::translate('Phonetic type')),
721
            'INDI:NAME:GIVN'             => new NamePieceGiven(I18N::translate('Given names')),
722
            'INDI:NAME:NICK'             => new NamePieceNickname(I18N::translate('Nickname')),
723
            'INDI:NAME:NPFX'             => new NamePiecePrefix(I18N::translate('Name prefix')),
724
            'INDI:NAME:NSFX'             => new NamePieceSuffix(I18N::translate('Name suffix')),
725
            'INDI:NAME:ROMN'             => new NameRomanizedVariation(I18N::translate('Romanized name')),
726
            'INDI:NAME:ROMN:GIVN'        => new NamePieceGiven(I18N::translate('Given names')),
727
            'INDI:NAME:ROMN:NICK'        => new NamePieceNickname(I18N::translate('Nickname')),
728
            'INDI:NAME:ROMN:NOTE'        => new NoteStructure(I18N::translate('Note on romanized name')),
729
            'INDI:NAME:ROMN:NPFX'        => new NamePiecePrefix(I18N::translate('Name prefix')),
730
            'INDI:NAME:ROMN:NSFX'        => new NamePieceSuffix(I18N::translate('Name suffix')),
731
            'INDI:NAME:ROMN:SOUR'        => new XrefSource(I18N::translate('Source citation')),
732
            'INDI:NAME:ROMN:SPFX'        => new NamePieceSurnamePrefix(I18N::translate('Surname prefix')),
733
            'INDI:NAME:ROMN:SURN'        => new NamePieceSurname(I18N::translate('Surname')),
734
            'INDI:NAME:ROMN:TYPE'        => new RomanizedType(I18N::translate('Romanized type')),
735
            'INDI:NAME:SPFX'             => new NamePieceSurnamePrefix(I18N::translate('Surname prefix')),
736
            'INDI:NAME:SURN'             => new NamePieceSurname(I18N::translate('Surname')),
737
            'INDI:NAME:TYPE'             => new NameType(I18N::translate('Type of name')),
738
            'INDI:NATI'                  => new NationalOrTribalOrigin(I18N::translate('Nationality')),
739
            'INDI:NATU'                  => new Naturalization(I18N::translate('Naturalization')),
740
            'INDI:NATU:DATE'             => new DateValue(I18N::translate('Date of naturalization')),
741
            'INDI:NATU:PLAC'             => new PlaceName(I18N::translate('Place of naturalization')),
742
            'INDI:NCHI'                  => new CountOfChildren(I18N::translate('Number of children')),
743
            'INDI:NMR'                   => new CountOfMarriages(I18N::translate('Number of marriages')),
744
            'INDI:NOTE'                  => new NoteStructure(I18N::translate('Note')),
745
            'INDI:OBJE'                  => new XrefMedia(I18N::translate('Media object')),
746
            'INDI:OCCU'                  => new Occupation(I18N::translate('Occupation')),
747
            'INDI:OCCU:AGNC'             => new ResponsibleAgency(I18N::translate('Employer')),
748
            'INDI:ORDN'                  => new Ordination(I18N::translate('Ordination')),
749
            'INDI:ORDN:AGNC'             => new Ordination(I18N::translate('Religious institution')),
750
            'INDI:ORDN:DATE'             => new Ordination(I18N::translate('Date of ordination')),
751
            'INDI:ORDN:PLAC'             => new Ordination(I18N::translate('Place of ordination')),
752
            'INDI:PROB'                  => new Probate(I18N::translate('Probate')),
753
            'INDI:PROP'                  => new Possessions(I18N::translate('Property')),
754
            'INDI:REFN'                  => new UserReferenceNumber(I18N::translate('Reference number')),
755
            'INDI:REFN:TYPE'             => new UserReferenceType(I18N::translate('Type of reference number')),
756
            'INDI:RELI'                  => new ReligiousAffiliation(I18N::translate('Religion')),
757
            'INDI:RESI'                  => new Residence(I18N::translate('Residence')),
758
            'INDI:RESI:DATE'             => new DateValue(I18N::translate('Date of residence')),
759
            'INDI:RESI:PLAC'             => new PlaceName(I18N::translate('Place of residence')),
760
            'INDI:RESN'                  => new RestrictionNotice(I18N::translate('Restriction')),
761
            'INDI:RETI'                  => new Retirement(I18N::translate('Retirement')),
762
            'INDI:RETI:AGNC'             => new ResponsibleAgency(I18N::translate('Employer')),
763
            'INDI:RFN'                   => new PermanentRecordFileNumber(I18N::translate('Record file number')),
764
            'INDI:RIN'                   => new AutomatedRecordId(I18N::translate('Record ID number')),
765
            'INDI:SEX'                   => new SexValue(I18N::translate('Gender')),
766
            'INDI:SLGC'                  => new LdsChildSealing(I18N::translate('LDS child sealing')),
767
            'INDI:SLGC:DATE'             => new DateLdsOrd(I18N::translate('Date of LDS child sealing')),
768
            'INDI:SLGC:FAMC'             => new XrefFamily(I18N::translate('Parents')),
769
            'INDI:SLGC:PLAC'             => new PlaceLivingOrdinance(I18N::translate('Place of LDS child sealing')),
770
            'INDI:SLGC:STAT'             => new LdsChildSealingDateStatus(I18N::translate('Status')),
771
            'INDI:SLGC:STAT:DATE'        => new DateValueExact(I18N::translate('Status change date')),
772
            'INDI:SLGC:TEMP'             => new TempleCode(I18N::translate('Temple')),
773
            'INDI:SOUR'                  => new XrefSource(I18N::translate('Source citation')),
774
            'INDI:SOUR:DATA'             => new SourceData(I18N::translate('Data')),
775
            'INDI:SOUR:DATA:DATE'        => new DateValue(I18N::translate('Date of entry in original source')),
776
            'INDI:SOUR:DATA:TEXT'        => new TextFromSource(I18N::translate('Text')),
777
            'INDI:SOUR:EVEN'             => new EventTypeCitedFrom(I18N::translate('Event')),
778
            'INDI:SOUR:EVEN:ROLE'        => new RoleInEvent(I18N::translate('Role')),
779
            'INDI:SOUR:NOTE'             => new NoteStructure(I18N::translate('Note on source citation')),
780
            'INDI:SOUR:OBJE'             => new XrefMedia(I18N::translate('Media object')),
781
            'INDI:SOUR:PAGE'             => new WhereWithinSource(I18N::translate('Citation details')),
782
            'INDI:SOUR:QUAY'             => new CertaintyAssessment(I18N::translate('Quality of data')),
783
            'INDI:SSN'                   => new SocialSecurityNumber(I18N::translate('Social security number')),
784
            'INDI:SUBM'                  => new XrefSubmitter(I18N::translate('Submitter')),
785
            'INDI:TITL'                  => new NobilityTypeTitle(I18N::translate('Title')),
786
            'INDI:WILL'                  => new Will(I18N::translate('Will')),
787
            'NOTE'                       => new NoteRecord(I18N::translate('Shared note')),
788
            'NOTE:CHAN'                  => new Change(I18N::translate('Last change')),
789
            'NOTE:CHAN:DATE'             => new ChangeDate(I18N::translate('Date of last change')),
790
            'NOTE:CHAN:DATE:TIME'        => new TimeValueNow(I18N::translate('Time of last change')),
791
            'NOTE:CHAN:NOTE'             => new NoteStructure(I18N::translate('Note on last change')),
792
            'NOTE:CONC'                  => new SubmitterText(I18N::translate('Note')),
793
            'NOTE:CONT'                  => new SubmitterText(I18N::translate('Continuation')),
794
            'NOTE:REFN'                  => new UserReferenceNumber(I18N::translate('Reference number')),
795
            'NOTE:REFN:TYPE'             => new UserReferenceType(I18N::translate('Type of reference number')),
796
            'NOTE:RIN'                   => new AutomatedRecordId(I18N::translate('Record ID number')),
797
            'NOTE:SOUR'                  => new XrefSource(I18N::translate('Source citation')),
798
            'NOTE:SOUR:DATA'             => new SourceData(I18N::translate('Data')),
799
            'NOTE:SOUR:DATA:DATE'        => new DateValue(I18N::translate('Date of entry in original source')),
800
            'NOTE:SOUR:DATA:TEXT'        => new TextFromSource(I18N::translate('Text')),
801
            'NOTE:SOUR:EVEN'             => new EventTypeCitedFrom(I18N::translate('Event')),
802
            'NOTE:SOUR:EVEN:ROLE'        => new RoleInEvent(I18N::translate('Role')),
803
            'NOTE:SOUR:NOTE'             => new NoteStructure(I18N::translate('Note on source citation')),
804
            'NOTE:SOUR:OBJE'             => new XrefMedia(I18N::translate('Media object')),
805
            'NOTE:SOUR:PAGE'             => new WhereWithinSource(I18N::translate('Citation details')),
806
            'NOTE:SOUR:QUAY'             => new CertaintyAssessment(I18N::translate('Quality of data')),
807
            'OBJE'                       => new MediaRecord(I18N::translate('Media object')),
808
            'OBJE:BLOB'                  => new CustomElement(I18N::translate('Binary data object')),
809
            'OBJE:CHAN'                  => new Change(I18N::translate('Last change')),
810
            'OBJE:CHAN:DATE'             => new ChangeDate(I18N::translate('Date of last change')),
811
            'OBJE:CHAN:DATE:TIME'        => new TimeValueNow(I18N::translate('Time of last change')),
812
            'OBJE:CHAN:NOTE'             => new NoteStructure(I18N::translate('Note on last change')),
813
            'OBJE:FILE'                  => new MultimediaFileReference(I18N::translate('Filename')),
814
            'OBJE:FILE:FORM'             => new MultimediaFormat(I18N::translate('Format')),
815
            'OBJE:FILE:FORM:TYPE'        => new SourceMediaType(I18N::translate('Media type')),
816
            'OBJE:FILE:TITL'             => new DescriptiveTitle(I18N::translate('Title')),
817
            'OBJE:NOTE'                  => new NoteStructure(I18N::translate('Note')),
818
            'OBJE:REFN'                  => new UserReferenceNumber(I18N::translate('Reference number')),
819
            'OBJE:REFN:TYPE'             => new UserReferenceType(I18N::translate('Type of reference number')),
820
            'OBJE:RIN'                   => new AutomatedRecordId(I18N::translate('Record ID number')),
821
            'OBJE:SOUR'                  => new XrefSource(I18N::translate('Source citation')),
822
            'OBJE:SOUR:DATA'             => new SourceData(I18N::translate('Data')),
823
            'OBJE:SOUR:DATA:DATE'        => new DateValue(I18N::translate('Date of entry in original source')),
824
            'OBJE:SOUR:DATA:TEXT'        => new TextFromSource(I18N::translate('Text')),
825
            'OBJE:SOUR:EVEN'             => new EventTypeCitedFrom(I18N::translate('Event')),
826
            'OBJE:SOUR:EVEN:ROLE'        => new RoleInEvent(I18N::translate('Role')),
827
            'OBJE:SOUR:NOTE'             => new NoteStructure(I18N::translate('Note on source citation')),
828
            'OBJE:SOUR:OBJE'             => new XrefMedia(I18N::translate('Media object')),
829
            'OBJE:SOUR:PAGE'             => new WhereWithinSource(I18N::translate('Citation details')),
830
            'OBJE:SOUR:QUAY'             => new CertaintyAssessment(I18N::translate('Quality of data')),
831
            'REPO'                       => new RepositoryRecord(I18N::translate('Repository')),
832
            'REPO:ADDR'                  => new AddressLine(I18N::translate('Address')),
833
            'REPO:ADDR:ADR1'             => new AddressLine1(I18N::translate('Address line 1')),
834
            'REPO:ADDR:ADR2'             => new AddressLine2(I18N::translate('Address line 2')),
835
            'REPO:ADDR:ADR3'             => new AddressLine3(I18N::translate('Address line 3')),
836
            'REPO:ADDR:CITY'             => new AddressCity(I18N::translate('City')),
837
            'REPO:ADDR:CTRY'             => new AddressCountry(I18N::translate('Country')),
838
            'REPO:ADDR:POST'             => new AddressPostalCode(I18N::translate('Postal code')),
839
            'REPO:ADDR:STAE'             => new AddressState(I18N::translate('State')),
840
            'REPO:CHAN'                  => new Change(I18N::translate('Last change')),
841
            'REPO:CHAN:DATE'             => new ChangeDate(I18N::translate('Date of last change')),
842
            'REPO:CHAN:DATE:TIME'        => new TimeValueNow(I18N::translate('Time of last change')),
843
            'REPO:CHAN:NOTE'             => new NoteStructure(I18N::translate('Note on last change')),
844
            'REPO:EMAIL'                 => new AddressEmail(I18N::translate('Email address')),
845
            'REPO:FAX'                   => new AddressFax(I18N::translate('Fax')),
846
            'REPO:NAME'                  => new NameOfRepository(I18N::translateContext('Repository', 'Name')),
847
            'REPO:NOTE'                  => new NoteStructure(I18N::translate('Note')),
848
            'REPO:PHON'                  => new PhoneNumber(I18N::translate('Phone')),
849
            'REPO:REFN'                  => new UserReferenceNumber(I18N::translate('Reference number')),
850
            'REPO:REFN:TYPE'             => new UserReferenceType(I18N::translate('Type of reference number')),
851
            'REPO:RIN'                   => new AutomatedRecordId(I18N::translate('Record ID number')),
852
            'REPO:WWW'                   => new AddressWebPage(I18N::translate('URL')),
853
            'SOUR'                       => new SourceRecord(I18N::translate('Source')),
854
            'SOUR:ABBR'                  => new SourceFiledByEntry(I18N::translate('Abbreviation')),
855
            'SOUR:AUTH'                  => new SourceOriginator(I18N::translate('Author')),
856
            'SOUR:CHAN'                  => new Change(I18N::translate('Last change')),
857
            'SOUR:CHAN:DATE'             => new ChangeDate(I18N::translate('Date of last change')),
858
            'SOUR:CHAN:DATE:TIME'        => new TimeValueNow(I18N::translate('Time of last change')),
859
            'SOUR:CHAN:NOTE'             => new NoteStructure(I18N::translate('Note on last change')),
860
            'SOUR:DATA'                  => new EmptyElement(I18N::translate('Data'), ['EVEN' => '0:M', 'AGNC' => '0:1', 'NOTE' => '0:M']),
861
            'SOUR:DATA:AGNC'             => new ResponsibleAgency(I18N::translate('Agency')),
862
            'SOUR:DATA:EVEN'             => new EventsRecorded(I18N::translate('Events')),
863
            'SOUR:DATA:EVEN:DATE'        => new DateValue(I18N::translate('Date range')),
864
            'SOUR:DATA:EVEN:PLAC'        => new SourceJurisdictionPlace(I18N::translate('Place'), []),
865
            'SOUR:DATA:NOTE'             => new NoteStructure(I18N::translate('Note on source data')),
866
            'SOUR:NOTE'                  => new NoteStructure(I18N::translate('Note on source')),
867
            'SOUR:OBJE'                  => new XrefMedia(I18N::translate('Media object')),
868
            'SOUR:PUBL'                  => new SourcePublicationFacts(I18N::translate('Publication')),
869
            'SOUR:REFN'                  => new UserReferenceNumber(I18N::translate('Reference number')),
870
            'SOUR:REFN:TYPE'             => new UserReferenceType(I18N::translate('Type of reference number')),
871
            'SOUR:REPO'                  => new XrefRepository(I18N::translate('Repository')),
872
            'SOUR:REPO:CALN'             => new SourceCallNumber(I18N::translate('Call number')),
873
            'SOUR:REPO:CALN:MEDI'        => new SourceMediaType(I18N::translate('Media type')),
874
            'SOUR:REPO:NOTE'             => new NoteStructure(I18N::translate('Note on repository reference')),
875
            'SOUR:RIN'                   => new AutomatedRecordId(I18N::translate('Record ID number')),
876
            'SOUR:TEXT'                  => new TextFromSource(I18N::translate('Text')),
877
            'SOUR:TITL'                  => new DescriptiveTitle(I18N::translate('Title')),
878
            'SUBM'                       => new SubmitterRecord(I18N::translate('Submitter')),
879
            'SUBM:ADDR'                  => new AddressLine(I18N::translate('Address')),
880
            'SUBM:ADDR:ADR1'             => new AddressLine1(I18N::translate('Address line 1')),
881
            'SUBM:ADDR:ADR2'             => new AddressLine2(I18N::translate('Address line 2')),
882
            'SUBM:ADDR:ADR3'             => new AddressLine3(I18N::translate('Address line 3')),
883
            'SUBM:ADDR:CITY'             => new AddressCity(I18N::translate('City')),
884
            'SUBM:ADDR:CTRY'             => new AddressCountry(I18N::translate('Country')),
885
            'SUBM:ADDR:POST'             => new AddressPostalCode(I18N::translate('Postal code')),
886
            'SUBM:ADDR:STAE'             => new AddressState(I18N::translate('State')),
887
            'SUBM:CHAN'                  => new Change(I18N::translate('Last change')),
888
            'SUBM:CHAN:DATE'             => new ChangeDate(I18N::translate('Date of last change')),
889
            'SUBM:CHAN:DATE:TIME'        => new TimeValueNow(I18N::translate('Time of last change')),
890
            'SUBM:CHAN:NOTE'             => new NoteStructure(I18N::translate('Note on last change')),
891
            'SUBM:EMAIL'                 => new AddressEmail(I18N::translate('Email address')),
892
            'SUBM:FAX'                   => new AddressFax(I18N::translate('Fax')),
893
            'SUBM:LANG'                  => new LanguageId(I18N::translate('Language')),
894
            'SUBM:NAME'                  => new SubmitterName(I18N::translate('Name')),
895
            'SUBM:NOTE'                  => new NoteStructure(I18N::translate('Note')),
896
            'SUBM:OBJE'                  => new XrefMedia(I18N::translate('Media object')),
897
            'SUBM:PHON'                  => new PhoneNumber(I18N::translate('Phone')),
898
            'SUBM:RFN'                   => new SubmitterRegisteredRfn(I18N::translate('Record file number')),
899
            'SUBM:RIN'                   => new AutomatedRecordId(I18N::translate('Record ID number')),
900
            'SUBM:WWW'                   => new AddressWebPage(I18N::translate('URL')),
901
            'SUBN'                       => new SubmissionRecord(I18N::translate('Submission')),
902
            'SUBN:ANCE'                  => new GenerationsOfAncestors(I18N::translate('Generations of ancestors')),
903
            'SUBN:CHAN'                  => new Change(I18N::translate('Last change')),
904
            'SUBN:CHAN:DATE'             => new ChangeDate(I18N::translate('Date of last change')),
905
            'SUBN:CHAN:DATE:TIME'        => new TimeValueNow(I18N::translate('Time of last change')),
906
            'SUBN:CHAN:NOTE'             => new NoteStructure(I18N::translate('Note on last change')),
907
            'SUBN:DESC'                  => new GenerationsOfDescendants(I18N::translate('Generations of descendants')),
908
            'SUBN:FAMF'                  => new NameOfFamilyFile(I18N::translate('Family file')),
909
            'SUBN:NOTE'                  => new NoteStructure(I18N::translate('Note')),
910
            'SUBN:ORDI'                  => new OrdinanceProcessFlag(I18N::translate('Ordinance')),
911
            'SUBN:RIN'                   => new AutomatedRecordId(I18N::translate('Record ID number')),
912
            'SUBN:SUBM'                  => new XrefSubmitter(I18N::translate('Submitter')),
913
            'SUBN:TEMP'                  => new TempleCode(/* I18N: https://en.wikipedia.org/wiki/Temple_(LDS_Church)*/ I18N::translate('Temple')),
914
            'TRLR'                       => new EmptyElement(I18N::translate('Trailer')),
915
        ];
916
    }
917
918
    /**
919
     * Custom tags for webtrees.
920
     *
921
     * @return array<string,ElementInterface>
922
     */
923
    private function webtreesTags(): array
924
    {
925
        return [
926
            'FAM:CHAN:_WT_USER'           => new WebtreesUser(I18N::translate('Author of last change')),
927
            'FAM:FACT'                    => new FamilyFact(I18N::translate('Fact')),
928
            'FAM:FACT:TYPE'               => new EventOrFactClassification(I18N::translate('Type of fact')),
929
            'FAM:*:_ASSO'                 => new XrefAssociate(I18N::translate('Associate')),
930
            'FAM:*:_ASSO:NOTE'            => new NoteStructure(I18N::translate('Note on association')),
931
            'FAM:*:_ASSO:RELA'            => new RelationIsDescriptor(I18N::translate('Relationship')),
932
            'FAM:*:_ASSO:SOUR'            => new XrefSource(I18N::translate('Source citation')),
933
            'FAM:*:_ASSO:SOUR:DATA'       => new SourceData(I18N::translate('Data')),
934
            'FAM:*:_ASSO:SOUR:DATA:DATE'  => new DateValue(I18N::translate('Date of entry in original source')),
935
            'FAM:*:_ASSO:SOUR:DATA:TEXT'  => new TextFromSource(I18N::translate('Text')),
936
            'FAM:*:_ASSO:SOUR:EVEN'       => new EventTypeCitedFrom(I18N::translate('Event')),
937
            'FAM:*:_ASSO:SOUR:EVEN:ROLE'  => new RoleInEvent(I18N::translate('Role')),
938
            'FAM:*:_ASSO:SOUR:NOTE'       => new NoteStructure(I18N::translate('Note on source citation')),
939
            'FAM:*:_ASSO:SOUR:OBJE'       => new XrefMedia(I18N::translate('Media object')),
940
            'FAM:*:_ASSO:SOUR:PAGE'       => new WhereWithinSource(I18N::translate('Citation details')),
941
            'FAM:*:_ASSO:SOUR:QUAY'       => new CertaintyAssessment(I18N::translate('Quality of data')),
942
            'INDI:CHAN:_WT_USER'          => new WebtreesUser(I18N::translate('Author of last change')),
943
            'INDI:*:_ASSO'                => new XrefAssociate(I18N::translate('Associate')),
944
            'INDI:*:_ASSO:NOTE'           => new NoteStructure(I18N::translate('Note on association')),
945
            'INDI:*:_ASSO:RELA'           => new RelationIsDescriptor(I18N::translate('Relationship')),
946
            'INDI:*:_ASSO:SOUR'           => new XrefSource(I18N::translate('Source citation')),
947
            'INDI:*:_ASSO:SOUR:DATA'      => new SourceData(I18N::translate('Data')),
948
            'INDI:*:_ASSO:SOUR:DATA:DATE' => new DateValue(I18N::translate('Date of entry in original source')),
949
            'INDI:*:_ASSO:SOUR:DATA:TEXT' => new TextFromSource(I18N::translate('Text')),
950
            'INDI:*:_ASSO:SOUR:EVEN'      => new EventTypeCitedFrom(I18N::translate('Event')),
951
            'INDI:*:_ASSO:SOUR:EVEN:ROLE' => new RoleInEvent(I18N::translate('Role')),
952
            'INDI:*:_ASSO:SOUR:NOTE'      => new NoteStructure(I18N::translate('Note on source citation')),
953
            'INDI:*:_ASSO:SOUR:OBJE'      => new XrefMedia(I18N::translate('Media object')),
954
            'INDI:*:_ASSO:SOUR:PAGE'      => new WhereWithinSource(I18N::translate('Citation details')),
955
            'INDI:*:_ASSO:SOUR:QUAY'      => new CertaintyAssessment(I18N::translate('Quality of data')),
956
            'NOTE:CHAN:_WT_USER'          => new WebtreesUser(I18N::translate('Author of last change')),
957
            'NOTE:RESN'                   => new RestrictionNotice(I18N::translate('Restriction')),
958
            'OBJE:CHAN:_WT_USER'          => new WebtreesUser(I18N::translate('Author of last change')),
959
            'OBJE:RESN'                   => new RestrictionNotice(I18N::translate('Restriction')),
960
            'REPO:CHAN:_WT_USER'          => new WebtreesUser(I18N::translate('Author of last change')),
961
            'REPO:RESN'                   => new RestrictionNotice(I18N::translate('Restriction')),
962
            'SOUR:CHAN:_WT_USER'          => new WebtreesUser(I18N::translate('Author of last change')),
963
            'SOUR:RESN'                   => new RestrictionNotice(I18N::translate('Restriction')),
964
            'SUBM:CHAN:_WT_USER'          => new WebtreesUser(I18N::translate('Author of last change')),
965
            'SUBM:RESN'                   => new RestrictionNotice(I18N::translate('Restriction')),
966
            '_LOC:CHAN:_WT_USER'          => new WebtreesUser(I18N::translate('Author of last change')),
967
            '_LOC:RESN'                   => new RestrictionNotice(I18N::translate('Restriction')),
968
        ];
969
    }
970
971
    /**
972
     * @return array<string,array<int,array<int,string>>>
973
     */
974
    private function webtreesSubTags(): array
975
    {
976
        return [
977
            'FAM'              => [['_UID', '0:M']],
978
            'FAM:*:SOUR:DATA'  => [['TEXT', '0:1']],
979
            'FAM:ANUL'         => [['_ASSO', '0:M', 'NOTE']],
980
            'FAM:CENS'         => [['_ASSO', '0:M', 'NOTE']],
981
            'FAM:CHAN'         => [['_WT_USER', '0:1']],
982
            'FAM:DIV'          => [['_ASSO', '0:M', 'NOTE']],
983
            'FAM:DIVF'         => [['_ASSO', '0:M', 'NOTE']],
984
            'FAM:ENGA'         => [['_ASSO', '0:M', 'NOTE']],
985
            'FAM:EVEN'         => [['_ASSO', '0:M', 'NOTE']],
986
            'FAM:MARB'         => [['_ASSO', '0:M', 'NOTE']],
987
            'FAM:MARC'         => [['_ASSO', '0:M', 'NOTE']],
988
            'FAM:MARL'         => [['_ASSO', '0:M', 'NOTE']],
989
            'FAM:MARR'         => [['_ASSO', '2:M', 'NOTE']],
990
            'FAM:MARS'         => [['_ASSO', '0:M', 'NOTE']],
991
            'FAM:SLGS'         => [['_ASSO', '0:M', 'NOTE']],
992
            'FAM:SOUR:DATA'    => [['TEXT', '0:1']],
993
            'INDI'             => [['_UID', '0:M']],
994
            'INDI:*:SOUR:DATA' => [['TEXT', '0:1']],
995
            'INDI:ADOP'        => [['_ASSO', '0:M', 'NOTE']],
996
            'INDI:BAPL'        => [['_ASSO', '0:M', 'NOTE']],
997
            'INDI:BAPM'        => [['_ASSO', '2:M', 'NOTE']],
998
            'INDI:BARM'        => [['_ASSO', '0:M', 'NOTE']],
999
            'INDI:BASM'        => [['_ASSO', '0:M', 'NOTE']],
1000
            'INDI:BIRT'        => [['_ASSO', '0:M', 'NOTE'], ['FAMC', '0:0']],
1001
            'INDI:BURI'        => [['_ASSO', '0:M', 'NOTE']],
1002
            'INDI:CENS'        => [['_ASSO', '0:M', 'NOTE']],
1003
            'INDI:CHAN'        => [['_WT_USER', '0:1']],
1004
            'INDI:CHR'         => [['_ASSO', '2:M', 'NOTE']],
1005
            'INDI:CHRA'        => [['_ASSO', '0:M', 'NOTE']],
1006
            'INDI:CONF'        => [['_ASSO', '0:M', 'NOTE']],
1007
            'INDI:CONL'        => [['_ASSO', '0:M', 'NOTE']],
1008
            'INDI:CREM'        => [['_ASSO', '0:M', 'NOTE']],
1009
            'INDI:DEAT'        => [['_ASSO', '0:M', 'NOTE']],
1010
            'INDI:EDUC'        => [['_ASSO', '0:M', 'NOTE']],
1011
            'INDI:EMIG'        => [['_ASSO', '0:M', 'NOTE']],
1012
            'INDI:ENDL'        => [['_ASSO', '0:M', 'NOTE']],
1013
            'INDI:EVEN'        => [['_ASSO', '0:M', 'NOTE']],
1014
            'INDI:GRAD'        => [['_ASSO', '0:M', 'NOTE']],
1015
            'INDI:IMMI'        => [['_ASSO', '0:M', 'NOTE']],
1016
            'INDI:NATU'        => [['_ASSO', '0:M', 'NOTE']],
1017
            'INDI:OCCU'        => [['_ASSO', '0:M', 'NOTE']],
1018
            'INDI:ORDN'        => [['_ASSO', '0:M', 'NOTE']],
1019
            'INDI:PROB'        => [['_ASSO', '0:M', 'NOTE']],
1020
            'INDI:PROP'        => [['_ASSO', '0:M', 'NOTE']],
1021
            'INDI:RESI'        => [['_ASSO', '0:M', 'NOTE']],
1022
            'INDI:RETI'        => [['_ASSO', '0:M', 'NOTE']],
1023
            'INDI:SLGC'        => [['_ASSO', '0:M', 'NOTE']],
1024
            'INDI:SOUR:DATA'   => [['TEXT', '0:1']],
1025
            'INDI:TITL'        => [['_ASSO', '0:M', 'NOTE']],
1026
            'INDI:WILL'        => [['_ASSO', '0:M', 'NOTE']],
1027
            'NOTE'             => [['RESN', '0:1', 'CHAN']],
1028
            'NOTE:CHAN'        => [['_WT_USER', '0:1']],
1029
            'NOTE:SOUR:DATA'   => [['TEXT', '0:1']],
1030
            'OBJE'             => [['RESN', '0:1', 'CHAN'], ['_UID', '0:M']],
1031
            'OBJE:CHAN'        => [['_WT_USER', '0:1']],
1032
            'OBJE:SOUR:DATA'   => [['TEXT', '0:1']],
1033
            'REPO'             => [['RESN', '0:1', 'CHAN'], ['_UID', '0:M']],
1034
            'REPO:CHAN'        => [['_WT_USER', '0:1']],
1035
            'SOUR'             => [['RESN', '0:1', 'CHAN'], ['_UID', '0:M']],
1036
            'SOUR:CHAN'        => [['_WT_USER', '0:1']],
1037
            'SUBM'             => [['RESN', '0:1', 'CHAN']],
1038
            'SUBM:CHAN'        => [['_WT_USER', '0:1']],
1039
        ];
1040
    }
1041
1042
    /**
1043
     * @return array<string,array<int,array<int,string>>>
1044
     */
1045
    private function customSubTags(): array
1046
    {
1047
        $custom_family_tags     = array_filter(explode(',', Site::getPreference('CUSTOM_FAMILY_TAGS')));
1048
        $custom_individual_tags = array_filter(explode(',', Site::getPreference('CUSTOM_INDIVIDUAL_TAGS')));
1049
1050
        $subtags = [
1051
            'FAM'  => array_map(static fn (string $tag): array => [$tag, '0:M'], $custom_family_tags),
1052
            'INDI' => array_map(static fn (string $tag): array => [$tag, '0:M'], $custom_individual_tags),
1053
        ];
1054
1055
        if (Site::getPreference('CUSTOM_TIME_TAGS') === '1') {
1056
            $subtags['INDI:BIRT:DATE'][] = ['TIME', '0:1'];
1057
            $subtags['INDI:DEAT:DATE'][] = ['TIME', '0:1'];
1058
        }
1059
1060
        if (Site::getPreference('CUSTOM_GEDCOM_L_TAGS') === '1') {
1061
            $subtags['FAM'][]               = ['_ASSO', '0:M'];
1062
            $subtags['FAM'][]               = ['_STAT', '0:1'];
1063
            $subtags['FAM'][]               = ['_UID', '0:M'];
1064
            $subtags['FAM:*:ADDR']          = [['_NAME', '0:1:?', 'ADR1']];
1065
            $subtags['FAM:*:PLAC']          = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1066
            $subtags['FAM:ENGA:PLAC']       = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1067
            $subtags['FAM:MARB:PLAC']       = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1068
            $subtags['FAM:MARR']            = [['_WITN', '0:1']];
1069
            $subtags['FAM:MARR:PLAC']       = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1070
            $subtags['FAM:SLGS:PLAC']       = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1071
            $subtags['INDI'][]              = ['_UID', '0:M'];
1072
            $subtags['INDI:*:ADDR']         = [['_NAME', '0:1:?', 'ADR1']];
1073
            $subtags['INDI:*:PLAC']         = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1074
            $subtags['INDI:ADOP:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1075
            $subtags['INDI:BAPL:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1076
            $subtags['INDI:BAPM']           = [['_GODP', '0:1'], ['_WITN', '0:1']];
1077
            $subtags['INDI:BAPM:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1078
            $subtags['INDI:BARM:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1079
            $subtags['INDI:BASM:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1080
            $subtags['INDI:BIRT:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1081
            $subtags['INDI:BLES:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1082
            $subtags['INDI:BURI:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1083
            $subtags['INDI:CENS:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1084
            $subtags['INDI:CHR']            = [['_GODP', '0:1'], ['_WITN', '0:1']];
1085
            $subtags['INDI:CHR:PLAC']       = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1086
            $subtags['INDI:CHRA:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1087
            $subtags['INDI:CONF:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1088
            $subtags['INDI:CONL:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1089
            $subtags['INDI:CREM:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1090
            $subtags['INDI:DEAT:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1091
            $subtags['INDI:EMIG:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1092
            $subtags['INDI:ENDL:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1093
            $subtags['INDI:EVEN:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1094
            $subtags['INDI:FCOM:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1095
            $subtags['INDI:IMMI:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1096
            $subtags['INDI:NAME']           = [['_RUFNAME', '0:1', 'SPFX']];
1097
            $subtags['INDI:NATU:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1098
            $subtags['INDI:ORDN:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1099
            $subtags['INDI:RESI:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1100
            $subtags['INDI:SLGC:PLAC']      = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1101
            $subtags['NOTE']                = [['_UID', '0:M']];
1102
            $subtags['OBJE']                = [['_PRIM', '0:1:?'], ['_UID', '0:M']];
1103
            $subtags['REPO']                = [['_UID', '0:M']];
1104
            $subtags['REPO:ADDR']           = [['_NAME', '0:1', 'ADR1']];
1105
            $subtags['SOUR']                = [['_UID', '0:M']];
1106
            $subtags['SOUR:DATA:EVEN:PLAC'] = [['_POST', '0:1'], ['_MAIDENHEAD', '0:1:?'], ['_LOC', '0:1']];
1107
            $subtags['SUBM']                = [['_UID', '0:M']];
1108
            $subtags['SUBM:ADDR']           = [['_NAME', '0:1', 'ADR1']];
1109
        }
1110
1111
        return $subtags;
1112
    }
1113
1114
    /**
1115
     * @param ElementFactoryInterface $element_factory
1116
     * @param bool                    $include_custom_tags
1117
     *
1118
     * @return void
1119
     */
1120
    public function registerTags(ElementFactoryInterface $element_factory, bool $include_custom_tags): void
1121
    {
1122
        // Standard GEDCOM.
1123
        $element_factory->registerTags($this->gedcom551Tags());
1124
1125
        // webtrees extensions.
1126
        $element_factory->registerTags($this->webtreesTags());
1127
1128
        if ($include_custom_tags) {
1129
            // webtrees extensions.
1130
            $element_factory->registerSubTags($this->webtreesSubTags());
1131
1132
            $custom_tags = [
1133
                new Aldfaer(),
1134
                new Ancestry(),
1135
                new BrothersKeeper(),
1136
                new FamilySearch(),
1137
                new FamilyTreeBuilder(),
1138
                new FamilyTreeMaker(),
1139
                new Gedcom7(),
1140
                new GedcomL(),
1141
                new Geneatique(),
1142
                new GenPlusWin(),
1143
                new Heredis(),
1144
                new Legacy(),
1145
                new MyHeritage(),
1146
                new PersonalAncestralFile(),
1147
                new PhpGedView(),
1148
                new ProGen(),
1149
                new Reunion(),
1150
                new RootsMagic(),
1151
            ];
1152
1153
            foreach ($custom_tags as $custom_tag) {
1154
                $element_factory->registerTags($custom_tag->tags());
1155
            }
1156
1157
            // Creating tags from all the above are grouped into one place
1158
            $element_factory->registerSubTags($this->customSubTags());
1159
        }
1160
    }
1161
}
1162