Project

General

Profile

Download (44.8 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
 * Copyright (C) 2009 EDIT
3
 * European Distributed Institute of Taxonomy
4
 * http://www.e-taxonomy.eu
5
 *
6
 * The contents of this file are subject to the Mozilla Public License Version 1.1
7
 * See LICENSE.TXT at the top of this package for the full license terms.
8
 */
9

    
10
package eu.etaxonomy.cdm.io.markup;
11

    
12
import java.util.ArrayList;
13
import java.util.List;
14
import java.util.Map;
15
import java.util.Set;
16
import java.util.UUID;
17
import java.util.regex.Matcher;
18
import java.util.regex.Pattern;
19

    
20
import javax.xml.stream.XMLEventReader;
21
import javax.xml.stream.XMLStreamException;
22
import javax.xml.stream.events.Attribute;
23
import javax.xml.stream.events.XMLEvent;
24

    
25
import org.apache.commons.lang.StringUtils;
26
import org.apache.log4j.Logger;
27

    
28
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade;
29
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeCacheStrategy;
30
import eu.etaxonomy.cdm.common.CdmUtils;
31
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
32
import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
33
import eu.etaxonomy.cdm.model.common.Annotation;
34
import eu.etaxonomy.cdm.model.common.AnnotationType;
35
import eu.etaxonomy.cdm.model.common.CdmBase;
36
import eu.etaxonomy.cdm.model.common.DefinedTerm;
37
import eu.etaxonomy.cdm.model.common.Language;
38
import eu.etaxonomy.cdm.model.common.Marker;
39
import eu.etaxonomy.cdm.model.common.MarkerType;
40
import eu.etaxonomy.cdm.model.common.TimePeriod;
41
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
42
import eu.etaxonomy.cdm.model.description.Feature;
43
import eu.etaxonomy.cdm.model.description.IndividualsAssociation;
44
import eu.etaxonomy.cdm.model.description.TaxonDescription;
45
import eu.etaxonomy.cdm.model.location.Country;
46
import eu.etaxonomy.cdm.model.location.NamedArea;
47
import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
48
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
49
import eu.etaxonomy.cdm.model.name.INonViralName;
50
import eu.etaxonomy.cdm.model.name.Rank;
51
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
52
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignationStatus;
53
import eu.etaxonomy.cdm.model.name.TaxonName;
54
import eu.etaxonomy.cdm.model.occurrence.Collection;
55
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
56
import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
57
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
58
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
59
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
60
import eu.etaxonomy.cdm.model.reference.Reference;
61
import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
62
import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
63
import eu.etaxonomy.cdm.strategy.parser.SpecimenTypeParser;
64
import eu.etaxonomy.cdm.strategy.parser.SpecimenTypeParser.TypeInfo;
65
import eu.etaxonomy.cdm.strategy.parser.TimePeriodParser;
66

    
67
/**
68
 * @author a.mueller
69
 * @since 30.05.2012
70
 *
71
 */
72
public class MarkupSpecimenImport extends MarkupImportBase  {
73
	@SuppressWarnings("unused")
74
	private static final Logger logger = Logger.getLogger(MarkupSpecimenImport.class);
75

    
76
	private static final String ALTERNATIVE_COLLECTION_TYPE_STATUS = "alternativeCollectionTypeStatus";
77
	private static final String ALTERNATIVE_COLLECTOR = "alternativeCollector";
78
	private static final String ALTERNATIVE_FIELD_NUM = "alternativeFieldNum";
79
	private static final String COLLECTOR = "collector";
80
	private static final String COLLECTION = "collection";
81
	private static final String COLLECTION_AND_TYPE = "collectionAndType";
82
	private static final String COLLECTION_TYPE_STATUS = "collectionTypeStatus";
83
	private static final String DAY = "day";
84
	private static final String DESTROYED = "destroyed";
85
	private static final String FIELD_NUM = "fieldNum";
86
	private static final String FULL_TYPE = "fullType";
87
	private static final String FULL_DATE = "fullDate";
88
	private static final String GATHERING_NOTES = "gatheringNotes";
89
	private static final String LOST = "lost";
90
	private static final String MONTH = "month";
91
	private static final String SUB_GATHERING = "subGathering";
92
	private static final String NOT_FOUND = "notFound";
93
	private static final String NOT_SEEN = "notSeen";
94
	private static final String ORIGINAL_DETERMINATION = "originalDetermination";
95

    
96
	private static final String UNKNOWN = "unknown";
97
	private static final String YEAR = "year";
98

    
99

    
100

    
101
	public MarkupSpecimenImport(MarkupDocumentImport docImport) {
102
		super(docImport);
103
	}
104

    
105

    
106
	public void handleSpecimenType(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent,
107
				HomotypicalGroup homotypicalGroup) throws XMLStreamException {
108

    
109
		// attributes
110
		Map<String, Attribute> attributes = getAttributes(parentEvent);
111
		String typeStatus = getAndRemoveAttributeValue(attributes, TYPE_STATUS);
112
		String notSeen = getAndRemoveAttributeValue(attributes, NOT_SEEN);
113
		String unknown = getAndRemoveAttributeValue(attributes, UNKNOWN);
114
		String notFound = getAndRemoveAttributeValue(attributes, NOT_FOUND);
115
		String destroyed = getAndRemoveAttributeValue(attributes, DESTROYED);
116
		String lost = getAndRemoveAttributeValue(attributes, LOST);
117
		checkNoAttributes(attributes, parentEvent);
118
		if (isNotBlank(typeStatus)) {
119
			// TODO
120
			// currently not needed
121
			fireWarningEvent("Type status not yet used", parentEvent, 4);
122
		}
123
		if (isNotBlank(notSeen)) {
124
			handleNotYetImplementedAttribute(attributes, NOT_SEEN, parentEvent);
125
		}
126
		if (isNotBlank(unknown)) {
127
			handleNotYetImplementedAttribute(attributes, UNKNOWN, parentEvent);
128
		}
129
		if (isNotBlank(notFound)) {
130
			handleNotYetImplementedAttribute(attributes, NOT_FOUND, parentEvent);
131
		}
132
		if (isNotBlank(destroyed)) {
133
			handleNotYetImplementedAttribute(attributes, DESTROYED, parentEvent);
134
		}
135
		if (isNotBlank(lost)) {
136
			handleNotYetImplementedAttribute(attributes, LOST, parentEvent);
137
		}
138

    
139
		INonViralName firstName = null;
140
		Set<TaxonName> names = homotypicalGroup.getTypifiedNames();
141
		if (names.isEmpty()) {
142
			String message = "There is no name in a homotypical group. Can't create the specimen type";
143
			fireWarningEvent(message, parentEvent, 8);
144
		} else {
145
			firstName = CdmBase.deproxy(names.iterator().next());
146
		}
147

    
148
		DerivedUnitFacade facade = DerivedUnitFacade.NewInstance(SpecimenOrObservationType.PreservedSpecimen);
149
		state.setFirstSpecimenInFacade(true);
150
		String text = "";
151
		state.resetCollectionAndType();
152
		state.setSpecimenType(true);
153
		boolean isFullType = false;
154
		// elements
155
		while (reader.hasNext()) {
156
			XMLEvent next = readNoWhitespace(reader);
157
			if (isMyEndingElement(next, parentEvent)) {
158
				if (! isFullType){
159
					makeSpecimenType(state, facade, text, state.getCollectionAndType(), firstName, parentEvent);
160
				}
161
				state.setSpecimenType(false);
162
				state.resetCollectionAndType();
163
				state.setFirstSpecimenInFacade(false);
164
				return;
165
			} else if (isStartingElement(next, FULL_TYPE)) {
166
				handleAmbigousManually(state, reader, next.asStartElement());
167
				isFullType = true;
168
			} else if (isStartingElement(next, TYPE_STATUS)) {
169
				handleNotYetImplementedElement(next);
170
			} else if (isStartingElement(next, GATHERING)) {
171
				handleGathering(state, reader, next, facade);
172
			} else if (isStartingElement(next, ORIGINAL_DETERMINATION)) {
173
				handleNotYetImplementedElement(next);
174
			} else if (isStartingElement(next, SPECIMEN_TYPE)) {
175
				handleNotYetImplementedElement(next);
176
			} else if (isStartingElement(next, COLLECTION_AND_TYPE)) {
177
				String colAndType = getCData(state, reader, next, true);
178
				state.addCollectionAndType(colAndType);
179
			} else if (isStartingElement(next, CITATION)) {
180
				handleNotYetImplementedElement(next);
181
			} else if (isStartingElement(next, NOTES)) {
182
				handleNotYetImplementedElement(next);
183
			} else if (isStartingElement(next, ANNOTATION)) {
184
				handleNotYetImplementedElement(next);
185
			} else if (next.isCharacters()) {
186
				text += next.asCharacters().getData();
187
			} else {
188
				handleUnexpectedElement(next);
189
			}
190
		}
191
		throw new IllegalStateException("Specimen type has no closing tag");
192
	}
193

    
194

    
195

    
196
	private void makeSpecimenType(MarkupImportState state, DerivedUnitFacade facade, String text, String collectionAndType,
197
			INonViralName name, XMLEvent parentEvent) {
198
		text = text.trim();
199
		if (isBlank(text) || isPunctuation(text)){
200
			//do nothing
201
		}else{
202
			String message = "Text '%s' not handled for <SpecimenType>";
203
			this.fireWarningEvent(String.format(message, text), parentEvent, 4);
204
		}
205

    
206
		if (makeFotgSpecimenType(state, collectionAndType, facade, name, parentEvent) || state.getConfig().isUseFotGSpecimenTypeCollectionAndTypeOnly()){
207
			return;
208
		}else{
209
			// remove brackets
210
			if (collectionAndType.matches("^\\(.*\\)\\.?$")) {
211
				collectionAndType = collectionAndType.replaceAll("\\.$", "");
212
				collectionAndType = collectionAndType.substring(1, collectionAndType.length() - 1);
213
			}
214

    
215
			String[] splitsSemi = collectionAndType.split("[;]");
216
            for (String splitSemi : splitsSemi) {
217
                String[] splitKomma = splitSemi.split("[,]");
218
                TypeInfo lastTypeInfo = null;
219
                for (String str : splitKomma) {
220
                    str = str.trim();
221
        			boolean addToAllNamesInGroup = true;
222
        			TypeInfo typeInfo = makeSpecimenTypeTypeInfo(state, str, lastTypeInfo, parentEvent);
223
        			SpecimenTypeDesignationStatus typeStatus = typeInfo.status;
224
        			Collection collection = this.getCollection(state, typeInfo.collectionString);
225

    
226
        			// TODO improve cache strategy handling
227
        			DerivedUnit typeSpecimen;
228
        			if (state.isFirstSpecimenInFacade()){
229
        			    state.setFirstSpecimenInFacade(false);
230
        			    typeSpecimen = facade.innerDerivedUnit();
231
        			    typeSpecimen.setCollection(collection);
232
        			}else{
233
        			    typeSpecimen = facade.addDuplicate(collection, null, null, null, null);
234
        			}
235
        			typeSpecimen.setCacheStrategy(new DerivedUnitFacadeCacheStrategy());
236
        			name.addSpecimenTypeDesignation(typeSpecimen, typeStatus,
237
        			        null, null, null, typeInfo.notDesignated, addToAllNamesInGroup);
238
        			handleNotSeen(state, typeSpecimen, typeInfo);
239
        			lastTypeInfo = typeInfo;
240
                }
241
			}
242
		}
243
	}
244

    
245

    
246
	/**
247
     * @param state
248
     * @param typeSpecimen
249
     * @param typeInfo
250
     */
251
    private void handleNotSeen(MarkupImportState state, DerivedUnit typeSpecimen, TypeInfo typeInfo) {
252
        if (typeInfo.notSeen){
253
            String text = "n.v. for " + state.getConfig().getSourceReference().getAbbrevTitleCache();
254
            typeSpecimen.addAnnotation(Annotation.NewInstance(text, AnnotationType.EDITORIAL(), getDefaultLanguage(state)));
255
            if(state.getConfig().getSpecimenNotSeenMarkerTypeUuid() != null){
256
                UUID uuidNotSeenMarker = state.getConfig().getSpecimenNotSeenMarkerTypeUuid();
257
                String markerTypeNotSeenLabel = state.getConfig().getSpecimenNotSeenMarkerTypeLabel();
258
                markerTypeNotSeenLabel = markerTypeNotSeenLabel == null ? "Not seen" : markerTypeNotSeenLabel;
259
                MarkerType notSeenMarkerType = getMarkerType(state, uuidNotSeenMarker, markerTypeNotSeenLabel, markerTypeNotSeenLabel, null, null);
260
                Marker marker = Marker.NewInstance(notSeenMarkerType, true);
261
                typeSpecimen.addMarker(marker);
262
            }
263
        }
264
    }
265

    
266

    
267
    private Pattern fotgTypePattern = null;
268
	/**
269
	 * Implemented for Flora of the Guyanas this may include duplicated code from similar places
270
	 * @param state
271
	 * @param collectionAndTypeOrig
272
	 * @param facade
273
	 * @param name
274
	 * @param parentEvent
275
	 * @return
276
	 */
277
	private boolean makeFotgSpecimenType(MarkupImportState state, final String collectionAndTypeOrig, DerivedUnitFacade facade, INonViralName name, XMLEvent parentEvent) {
278
		String collectionAndType = collectionAndTypeOrig;
279

    
280
		String notDesignatedRE = "not\\s+designated";
281
		String designatedByRE = "\\s*\\(((designated\\s+by\\s+|according\\s+to\\s+)[^\\)]+|here\\s+designated)\\)";
282
		String typesRE = "(holotype|isotypes?|neotype|isoneotype|syntype|lectotype|isolectotypes?|typ\\.\\scons\\.,?)";
283
		String collectionRE = "[A-Z\\-]{1,5}!?";
284
		String collectionsRE = String.format("%s(,\\s+%s)*",collectionRE, collectionRE);
285
		String addInfoRE = "(not\\s+seen|(presumed\\s+)?destroyed)";
286
		String singleTypeTypeRE = String.format("(%s\\s)?%s(,\\s+%s)*", typesRE, collectionsRE, addInfoRE);
287
		String allTypesRE = String.format("(\\(not\\s+seen\\)|\\(%s([,;]\\s%s)?\\))", singleTypeTypeRE, singleTypeTypeRE);
288
		String designatedRE = String.format("%s(%s)?", allTypesRE, designatedByRE);
289
		if (fotgTypePattern == null){
290

    
291
			String pattern = String.format("(%s|%s)", notDesignatedRE, designatedRE );
292
			fotgTypePattern = Pattern.compile(pattern);
293
		}
294
		Matcher matcher = fotgTypePattern.matcher(collectionAndType);
295

    
296
		if (matcher.matches()){
297
		    fireWarningEvent("Try to synchronize type handling (at least creation) with standard type handling. E.g. use TypeInfo and according algorithms", parentEvent, 2);
298
			if (collectionAndType.matches(notDesignatedRE)){
299
				SpecimenTypeDesignation desig = SpecimenTypeDesignation.NewInstance();
300
				desig.setNotDesignated(true);
301
//				name.addSpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalNameString, isNotDesignated, addToAllHomotypicNames)
302
				name.addTypeDesignation(desig, true);
303
			}else if(collectionAndType.matches(designatedRE)){
304
				String designatedBy = null;
305
				Matcher desigMatcher = Pattern.compile(designatedByRE).matcher(collectionAndType);
306
				boolean hasDesignatedBy = desigMatcher.find();
307
				if (hasDesignatedBy){
308
					designatedBy = desigMatcher.group(0);
309
					collectionAndType = collectionAndType.replace(designatedBy, "");
310
				}
311

    
312
				//remove brackets
313
				collectionAndType = collectionAndType.substring(1, collectionAndType.length() -1);
314
				List<String> singleTypes = new ArrayList<String>();
315
				Pattern singleTypePattern = Pattern.compile("^" + singleTypeTypeRE);
316
				matcher = singleTypePattern.matcher(collectionAndType);
317
				while (matcher.find()){
318
					String match = matcher.group(0);
319
					singleTypes.add(match);
320
					collectionAndType = collectionAndType.substring(match.length());
321
					if (!collectionAndType.isEmpty()){
322
						collectionAndType = collectionAndType.substring(1).trim();
323
					}else{
324
						break;
325
					}
326
					matcher = singleTypePattern.matcher(collectionAndType);
327
				}
328

    
329
				List<SpecimenTypeDesignation> designations = new ArrayList<SpecimenTypeDesignation>();
330

    
331
				//single types
332
				for (String singleTypeOrig : singleTypes){
333
					String singleType = singleTypeOrig;
334
					//type
335
					Pattern typePattern = Pattern.compile("^" + typesRE);
336
					matcher = typePattern.matcher(singleType);
337
					SpecimenTypeDesignationStatus typeStatus = null;
338
					if (matcher.find()){
339
						String typeStr = matcher.group(0);
340
						singleType = singleType.substring(typeStr.length()).trim();
341
						try {
342
							typeStatus = SpecimenTypeParser.parseSpecimenTypeStatus(typeStr);
343
						} catch (UnknownCdmTypeException e) {
344
							fireWarningEvent("specimen type not recognized. Use generic type instead", parentEvent, 4);
345
							typeStatus = SpecimenTypeDesignationStatus.TYPE();
346
							//TODO use also type info from state
347
						}
348
					}else{
349
						typeStatus = SpecimenTypeDesignationStatus.TYPE();
350
						//TODO use also type info from state
351
					}
352

    
353

    
354
					//collection
355
					Pattern collectionPattern = Pattern.compile("^" + collectionsRE);
356
					matcher = collectionPattern.matcher(singleType);
357
					String[] collectionStrings = new String[0];
358
					if (matcher.find()){
359
						String collectionStr = matcher.group(0);
360
						singleType = singleType.substring(collectionStr.length());
361
						collectionStr = collectionStr.replace("(", "").replace(")", "").replaceAll("\\s", "");
362
						collectionStrings = collectionStr.split(",");
363
					}
364

    
365
					//addInfo
366
					if (!singleType.isEmpty() && singleType.startsWith(", ")){
367
						singleType = singleType.substring(2);
368
					}
369

    
370
					boolean notSeen = false;
371
					if (singleType.equals("not seen")){
372
						singleType = singleType.replace("not seen", "");
373
						notSeen = true;
374
					}
375
					if (singleType.startsWith("not seen, ")){
376
						singleType = singleType.replace("not seen, ", "");
377
						notSeen = true;
378
					}
379
					boolean destroyed = false;
380
					if (singleType.equals("destroyed")){
381
						destroyed = true;
382
						singleType = singleType.replace("destroyed", "");
383
					}
384
					boolean presumedDestroyed = false;
385
					if (singleType.equals("presumed destroyed")){
386
						presumedDestroyed = true;
387
						singleType = singleType.replace("presumed destroyed", "");
388
					}
389
					boolean hasAddInfo = notSeen || destroyed || presumedDestroyed;
390

    
391

    
392
					if (!singleType.isEmpty()){
393
						String message = "SingleType was not fully read. Remaining: " + singleType + ". Original singleType was: " + singleTypeOrig;
394
						fireWarningEvent(message, parentEvent, 6);
395
						System.out.println(message);
396
					}
397

    
398
					if (collectionStrings.length > 0){
399
						boolean isFirst = true;
400
						for (String collStr : collectionStrings){
401
							Collection collection = getCollection(state, collStr);
402
							DerivedUnit unit = isFirst ? facade.innerDerivedUnit()
403
									: facade.addDuplicate(collection, null, null, null, null);
404
							SpecimenTypeDesignation desig = SpecimenTypeDesignation.NewInstance();
405
							designations.add(desig);
406
							desig.setTypeSpecimen(unit);
407
							desig.setTypeStatus(typeStatus);
408
							handleSpecimenTypeAddInfo(state, notSeen, destroyed,
409
									presumedDestroyed, desig);
410
							name.addTypeDesignation(desig, true);
411
							isFirst = false;
412
						}
413
					}else if (hasAddInfo){  //handle addInfo if no collection data available
414
						SpecimenTypeDesignation desig = SpecimenTypeDesignation.NewInstance();
415
						designations.add(desig);
416
						desig.setTypeStatus(typeStatus);
417
						handleSpecimenTypeAddInfo(state, notSeen, destroyed,
418
								presumedDestroyed, desig);
419
						name.addTypeDesignation(desig, true);
420
					}else{
421
						fireWarningEvent("No type designation could be created as collection info was not recognized", parentEvent, 4);
422
					}
423
				}
424

    
425
				if (designatedBy != null){
426
					if (designations.size() != 1){
427
						fireWarningEvent("Size of type designations is not exactly 1, which is expected for 'designated by'", parentEvent, 2);
428
					}
429
					designatedBy = designatedBy.trim();
430
					if (designatedBy.startsWith("(") && designatedBy.endsWith(")") ){
431
						designatedBy = designatedBy.substring(1, designatedBy.length() - 1);
432
					}
433

    
434
					for (SpecimenTypeDesignation desig : designations){
435
						if (designatedBy.startsWith("designated by")){
436
							String titleCache = designatedBy.replace("designated by", "").trim();
437
							Reference reference = ReferenceFactory.newGeneric();
438
							reference.setTitleCache(titleCache, true);
439
							desig.setCitation(reference);
440
							//in future we could also try to parse it automatically
441
							fireWarningEvent("MANUALLY: Designated by should be parsed manually: " + titleCache, parentEvent, 1);
442
						}else if (designatedBy.equals("designated here")){
443
							Reference ref = state.getConfig().getSourceReference();
444
							desig.setCitation(ref);
445
							fireWarningEvent("MANUALLY: Microcitation should be added to 'designated here", parentEvent, 1);
446
						}else if (designatedBy.startsWith("according to")){
447
							String annotationStr = designatedBy.replace("according to", "").trim();
448
							Annotation annotation = Annotation.NewInstance(annotationStr, AnnotationType.EDITORIAL(), Language.ENGLISH());
449
							desig.addAnnotation(annotation);
450
						}else{
451
							fireWarningEvent("Designated by does not match known pattern: " + designatedBy, parentEvent, 6);
452
						}
453
					}
454
				}
455
			}else{
456
				fireWarningEvent("CollectionAndType unexpectedly not matching: " + collectionAndTypeOrig, parentEvent, 6);
457
			}
458
			return true;
459
		}else{
460
			if (state.getConfig().isUseFotGSpecimenTypeCollectionAndTypeOnly()){
461
				fireWarningEvent("NO MATCH: " + collectionAndTypeOrig, parentEvent, 4);
462
			}
463
			return false;
464
		}
465

    
466
//		// remove brackets
467
//		if (collectionAndType.matches("^\\(.*\\)\\.?$")) {
468
//			collectionAndType = collectionAndType.replaceAll("\\.$", "");
469
//			collectionAndType = collectionAndType.substring(1, collectionAndType.length() - 1);
470
//		}
471
//
472
//		String[] split = collectionAndType.split("[;,]");
473
//		for (String str : split) {
474
//			str = str.trim();
475
//			boolean addToAllNamesInGroup = true;
476
//			TypeInfo typeInfo = makeSpecimenTypeTypeInfo(str, parentEvent);
477
//			SpecimenTypeDesignationStatus typeStatus = typeInfo.status;
478
//			Collection collection = this.getCollection(state, typeInfo.collectionString);
479
//
480
//			// TODO improve cache strategy handling
481
//			DerivedUnit typeSpecimen = facade.addDuplicate(collection, null, null, null, null);
482
//			typeSpecimen.setCacheStrategy(new DerivedUnitFacadeCacheStrategy());
483
//			name.addSpecimenTypeDesignation(typeSpecimen, typeStatus, null, null, null, false, addToAllNamesInGroup);
484
//		}
485
	}
486

    
487

    
488
	/**
489
	 * @param notSeen
490
	 * @param destroyed
491
	 * @param presumedDestroyed
492
	 * @param desig
493
	 */
494
	private void handleSpecimenTypeAddInfo(MarkupImportState state, boolean notSeen, boolean destroyed,
495
			boolean presumedDestroyed, SpecimenTypeDesignation desig) {
496
		DerivedUnit specimen = desig.getTypeSpecimen();
497
		AnnotatableEntity annotEntity = specimen != null ? specimen : desig;
498

    
499
	    if (notSeen){
500
			UUID uuidNotSeenMarker = MarkupTransformer.uuidMarkerNotSeen;
501
			MarkerType notSeenMarkerType = getMarkerType(state, uuidNotSeenMarker, "Not seen", "Not seen", null, null);
502
			Marker marker = Marker.NewInstance(notSeenMarkerType, true);
503
			annotEntity.addMarker(marker);
504
			fireWarningEvent("not seen not yet implemented", "handleSpecimenTypeAddInfo", 4);
505
		}
506
		if (destroyed){
507
			UUID uuidDestroyedMarker = MarkupTransformer.uuidMarkerDestroyed;
508
			MarkerType destroyedMarkerType = getMarkerType(state, uuidDestroyedMarker, "Destroyed", "Destroyed", null, null);
509
			Marker marker = Marker.NewInstance(destroyedMarkerType, true);
510
			annotEntity.addMarker(marker);
511
			fireWarningEvent("'destroyed' not yet fully implemented", "handleSpecimenTypeAddInfo", 4);
512
		}
513
		if (presumedDestroyed){
514
			Annotation annotation = Annotation.NewInstance("presumably destroyed", Language.ENGLISH());
515
			annotation.setAnnotationType(AnnotationType.EDITORIAL());
516
			annotEntity.addAnnotation(annotation);
517
		}
518
	}
519

    
520

    
521
	private TypeInfo makeSpecimenTypeTypeInfo(MarkupImportState state, String originalString, TypeInfo lastTypeInfo, XMLEvent event) {
522
		TypeInfo result = new TypeInfo();
523
		if ("not designated".equals(originalString)){
524
			result.notDesignated = true;
525
			return result;
526
		}
527
		List<String> knownCollections = state.getConfig().getKnownCollections();
528
		for (String knownCollection:knownCollections){
529
		    if (originalString.contains(knownCollection)){
530
		        result.collectionString = knownCollection;
531
		        originalString = originalString.replace(knownCollection, "").trim();
532
		        break;
533
		    }
534
		}
535

    
536
		String[] split = originalString.split("(?<!not)\\s+");
537

    
538
		String unrecognizedTypeParts = null;
539
		for (String str : split) {
540
		    //holo/lecto/iso ...
541
			if (str.matches(SpecimenTypeParser.typeTypePattern)) {
542
				SpecimenTypeDesignationStatus status;
543
				try {
544
					status = SpecimenTypeParser.parseSpecimenTypeStatus(str);
545
				} catch (UnknownCdmTypeException e) {
546
					String message = "Specimen type status '%s' not recognized by parser";
547
					fireWarningEvent(String.format(message, str), event, 4);
548
					status = null;
549
				}
550
                if (result.status != null){
551
                    String message = "More than 1 status string found: " + originalString;
552
                    fireWarningEvent(message, event, 4);
553
                }
554
				result.status = status;
555
			} else if (str.matches(SpecimenTypeParser.collectionPattern)) {
556
				if (result.collectionString != null){
557
				    String message = "More than 1 collection string found: " + originalString;
558
                    fireWarningEvent(message, event, 4);
559
				}
560
			    result.collectionString = str;
561
			} else if (str.matches(SpecimenTypeParser.notSeen)) {
562
                if (result.notSeen){
563
                    String message = "More than 1 'not seen' string found: " + originalString;
564
                    fireWarningEvent(message, event, 4);
565
                }
566
                result.notSeen = true;
567
            } else {
568
                unrecognizedTypeParts = CdmUtils.concat(" ", unrecognizedTypeParts, str);
569
			}
570
			if (result.status == null && lastTypeInfo != null && lastTypeInfo.status != null){
571
			    result.status = lastTypeInfo.status;
572
			}
573
		}
574
		if(isNotBlank(unrecognizedTypeParts)){
575
		    String message = "Type parts '%s' could not be recognized";
576
            fireWarningEvent(String.format(message, unrecognizedTypeParts), event, 2);
577
		}
578
		return result;
579
	}
580

    
581

    
582
	private void handleGathering(MarkupImportState state, XMLEventReader readerOrig, XMLEvent parentEvent , DerivedUnitFacade facade) throws XMLStreamException {
583
		checkNoAttributes(parentEvent);
584
		boolean hasCollector = false;
585
		boolean hasFieldNum = false;
586

    
587
		LookAheadEventReader reader = new LookAheadEventReader(parentEvent.asStartElement(), readerOrig);
588

    
589
		// elements
590
		while (reader.hasNext()) {
591
			XMLEvent next = readNoWhitespace(reader);
592
			if (isMyEndingElement(next, parentEvent)) {
593
				if (! hasCollector){
594
					if (state.getCurrentCollector() == null){
595
						checkMandatoryElement(hasCollector,parentEvent.asStartElement(), COLLECTOR);
596
					}else{
597
						facade.setCollector(state.getCurrentCollector());
598
					}
599
				}
600
				checkMandatoryElement(hasFieldNum,parentEvent.asStartElement(), FIELD_NUM);
601
				return;
602
			}else if (isStartingElement(next, COLLECTOR)) {
603
				hasCollector = true;
604
				String collectorStr = getCData(state, reader, next);
605
				TeamOrPersonBase<?> collector = createCollector(state, collectorStr);
606
				facade.setCollector(collector);
607
				state.setCurrentCollector(collector);
608
			} else if (isStartingElement(next, ALTERNATIVE_COLLECTOR)) {
609
				handleNotYetImplementedElement(next);
610
			} else if (isStartingElement(next, FIELD_NUM)) {
611
				hasFieldNum = true;
612
				String fieldNumStr = getCData(state, reader, next);
613
				facade.setFieldNumber(fieldNumStr);
614
			} else if (isStartingElement(next, ALTERNATIVE_FIELD_NUM)) {
615
				handleAlternativeFieldNumber(state, reader, next, facade.innerFieldUnit());
616
			} else if (isStartingElement(next, COLLECTION_TYPE_STATUS)) {
617
				handleNotYetImplementedElement(next);
618
			} else if (isStartingElement(next, COLLECTION_AND_TYPE)) {
619
				handleGatheringCollectionAndType(state, reader, next, facade);
620
			} else if (isStartingElement(next, ALTERNATIVE_COLLECTION_TYPE_STATUS)) {
621
				handleNotYetImplementedElement(next);
622
			} else if (isStartingElement(next, SUB_GATHERING)) {
623
				handleNotYetImplementedElement(next);
624
			} else if (isStartingElement(next, COLLECTION)) {
625
				handleNotYetImplementedElement(next);
626
			} else if (isStartingElement(next, LOCALITY)) {
627
				handleLocality(state, reader, next, facade);
628
			} else if (isStartingElement(next, FULL_NAME)) {
629
				Rank defaultRank = Rank.SPECIES(); // can be any
630
				INonViralName nvn = createNameByCode(state, defaultRank);
631
				handleFullName(state, reader, nvn, next);
632
				TaxonName name = TaxonName.castAndDeproxy(nvn);
633
				DeterminationEvent.NewInstance(name, facade.innerDerivedUnit() != null ? facade.innerDerivedUnit() : facade.innerFieldUnit());
634
			} else if (isStartingElement(next, DATES)) {
635
				TimePeriod timePeriod = handleDates(state, reader, next);
636
				facade.setGatheringPeriod(timePeriod);
637
			} else if (isStartingElement(next, GATHERING_NOTES)) {
638
				handleAmbigousManually(state, reader, next.asStartElement());
639
			} else if (isStartingElement(next, NOTES)) {
640
				handleNotYetImplementedElement(next);
641
			}else if (next.isCharacters()) {
642
				String text = next.asCharacters().getData().trim();
643
				if (isPunctuation(text)){
644
					//do nothing
645
				}else if (state.isSpecimenType() && charIsSimpleType(text) ){
646
						//do nothing
647
				}else if ( (text.equals("=") || text.equals("(") ) && reader.nextIsStart(ALTERNATIVE_FIELD_NUM)){
648
					//do nothing
649
				}else if ( (text.equals(").") || text.equals(")")) && reader.previousWasEnd(ALTERNATIVE_FIELD_NUM)){
650
					//do nothing
651
				}else if ( charIsOpeningOrClosingBracket(text) ){
652
					//for now we don't do anything, however in future brackets may have semantics
653
				}else{
654
					//TODO
655
					String message = "Unrecognized text: %s";
656
					fireWarningEvent(String.format(message, text), next, 6);
657
				}
658
			} else {
659
				handleUnexpectedElement(next);
660
			}
661
		}
662
		throw new IllegalStateException("Collection has no closing tag.");
663

    
664
	}
665

    
666

    
667
	private final String fotgPattern = "^\\(([A-Z]{1,3})(?:,\\s?([A-Z]{1,3}))*\\)"; // eg. (US, B, CAN)
668
	private void handleGatheringCollectionAndType(MarkupImportState state, XMLEventReader reader, XMLEvent parent, DerivedUnitFacade facade) throws XMLStreamException {
669
		checkNoAttributes(parent);
670

    
671
		XMLEvent next = readNoWhitespace(reader);
672

    
673
		if (next.isCharacters()){
674
			String txt = next.asCharacters().getData().trim();
675
			if (state.isSpecimenType()){
676
				state.addCollectionAndType(txt);
677
			}else{
678

    
679
				Matcher fotgMatcher = Pattern.compile(fotgPattern).matcher(txt);
680

    
681
				if (fotgMatcher.matches()){
682
					txt = txt.substring(1, txt.length() - 1);  //remove bracket
683
					String[] splits = txt.split(",");
684
					for (String split : splits ){
685
						Collection collection = getCollection(state, split.trim());
686
						if (facade.innerDerivedUnit() == null){
687
						    String message = "Adding a duplicate to a non derived unit based facade is not possible. Please check why no derived unit exists yet in facade!";
688
						    this.fireWarningEvent(message, next, -6);
689
						}else{
690
						    facade.addDuplicate(collection, null, null, null, null);
691
						}
692
					}
693
					//FIXME 9
694
					//create derived units and and add collections
695

    
696
				}else{
697
					fireWarningEvent("Collection and type pattern for gathering not recognized: " + txt, next, 4);
698
				}
699
			}
700

    
701
		}else{
702
			fireUnexpectedEvent(next, 0);
703
		}
704

    
705
		if (isMyEndingElement(next, parent)){
706
			return;  //in case we have a completely empty element
707
		}
708
		next = readNoWhitespace(reader);
709
		if (isMyEndingElement(next, parent)){
710
			return;
711
		}else{
712
			fireUnexpectedEvent(next, 0);
713
			return;
714
		}
715
	}
716

    
717

    
718
	private Collection getCollection(MarkupImportState state, String code) {
719
		Collection collection = state.getCollectionByCode(code);
720
		if (collection == null){
721
			List<Collection> list = this.docImport.getCollectionService().searchByCode(code);
722
			if (list.size() == 1){
723
				collection = list.get(0);
724
			}else if (list.size() > 1){
725
				fireWarningEvent("More then one occurrence for collection " + code +  " in database. Collection not reused" , "", 1);
726
			}
727

    
728
			if (collection == null){
729
				collection = Collection.NewInstance();
730
				collection.setCode(code);
731
				this.docImport.getCollectionService().saveOrUpdate(collection);
732
			}
733
			state.putCollectionByCode(code, collection);
734
		}
735
		return collection;
736
	}
737

    
738

    
739
	private void handleAlternativeFieldNumber(MarkupImportState state, XMLEventReader reader, XMLEvent parent, FieldUnit fieldUnit) throws XMLStreamException {
740
		Map<String, Attribute> attrs = getAttributes(parent);
741
		Boolean doubtful = this.getAndRemoveBooleanAttributeValue(parent, attrs, "doubful", false);
742

    
743
		//for now we do not handle annotation and typeNotes
744
		String altFieldNum = getCData(state, reader, parent, false).trim();
745
		DefinedTerm type = this.getIdentifierType(state, MarkupTransformer.uuidIdentTypeAlternativeFieldNumber, "Alternative field number", "Alternative field number", "alt. field no.", null);
746
		fieldUnit.addIdentifier(altFieldNum, type);
747
		if (doubtful){
748
			fireWarningEvent("Marking alternative field numbers as doubtful not yet possible, see #4673", parent,4);
749
//			Marker.NewInstance(identifier, "true", MarkerType.IS_DOUBTFUL());
750
		}
751

    
752
	}
753

    
754

    
755
	private boolean charIsOpeningOrClosingBracket(String text) {
756
		return text.equals("(") || text.equals(")");
757
	}
758

    
759

    
760
	private TimePeriod handleDates(MarkupImportState state, XMLEventReader reader, XMLEvent parent) throws XMLStreamException {
761
		checkNoAttributes(parent);
762
		TimePeriod result = TimePeriod.NewInstance();
763
		String parseMessage = "%s can not be parsed: %s";
764
		boolean hasFullDate = false;
765
		boolean hasAtomised = false;
766
		boolean hasUnparsedAtomised = false;
767
		while (reader.hasNext()) {
768
			XMLEvent next = readNoWhitespace(reader);
769
			if (isMyEndingElement(next, parent)) {
770
				if (! isAlternative(hasFullDate, hasAtomised, hasUnparsedAtomised)){
771
					String message = "Some problems exist when defining the date";
772
					fireWarningEvent(message, parent, 4);
773
				}
774
				return result;
775
			} else if (isStartingElement(next, FULL_DATE)) {
776
				String fullDate = getCData(state, reader, next, true);
777
				if (fullDate.endsWith(".")){
778
				    fullDate = fullDate.substring(0, fullDate.length()-1);
779
				}
780
				result = TimePeriodParser.parseString(fullDate);
781
				if (result.getFreeText() != null){
782
					fireWarningEvent(String.format(parseMessage, FULL_DATE, fullDate), parent, 1);
783
				}
784
				hasFullDate = true;
785
			} else if (isStartingElement(next, DAY)) {
786
				String day = getCData(state, reader, next, true).trim();
787
				day = normalizeDate(day);
788
				if (CdmUtils.isNumeric(day)){
789
					result.setStartDay(Integer.valueOf(day));
790
					hasAtomised = true;
791
				}else{
792
					fireWarningEvent(String.format(parseMessage,"Day", day), parent, 2);
793
					hasUnparsedAtomised = true;
794
				}
795
			} else if (isStartingElement(next, MONTH)) {
796
				String month = getCData(state, reader, next, true).trim();
797
				month = normalizeDate(month);
798
				if (CdmUtils.isNumeric(month)){
799
					result.setStartMonth(Integer.valueOf(month));
800
					hasAtomised = true;
801
				}else{
802
					fireWarningEvent(String.format(parseMessage,"Month", month), parent, 2);
803
					hasUnparsedAtomised = true;
804
				}
805
			} else if (isStartingElement(next, YEAR)) {
806
				String year = getCData(state, reader, next, true).trim();
807
				year = normalizeDate(year);
808
				if (CdmUtils.isNumeric(year)){
809
					result.setStartYear(Integer.valueOf(year));
810
					hasAtomised = true;
811
				}else{
812
					fireWarningEvent(String.format(parseMessage,"Year", year), parent, 2);
813
					hasUnparsedAtomised = true;
814
				}
815
			} else {
816
				handleUnexpectedElement(next);
817
			}
818
		}
819
		throw new IllegalStateException("Dates has no closing tag.");
820
	}
821

    
822

    
823
	private String normalizeDate(String partOfDate) {
824
		if (isBlank(partOfDate)){
825
			return null;
826
		}
827
		partOfDate = partOfDate.trim();
828
		while (partOfDate.startsWith("-")){
829
			partOfDate = partOfDate.substring(1);
830
		}
831
		return partOfDate;
832
	}
833

    
834

    
835
	private boolean isAlternative(boolean first, boolean second, boolean third) {
836
		return ( (first ^ second) && !third)  ||
837
				(! first && ! second && third) ;
838
	}
839

    
840

    
841
	private void handleLocality(MarkupImportState state, XMLEventReader reader,XMLEvent parentEvent, DerivedUnitFacade facade)throws XMLStreamException {
842
		String classValue = getClassOnlyAttribute(parentEvent);
843
		boolean isLocality = false;
844
		NamedAreaLevel areaLevel = null;
845
		if ("locality".equalsIgnoreCase(classValue)||state.getConfig().isIgnoreLocalityClass()) {
846
			isLocality = true;
847
		} else {
848
			areaLevel = makeNamedAreaLevel(state, classValue, parentEvent);
849
		}
850

    
851
		String text = "";
852
		// elements
853
		while (reader.hasNext()) {
854
			XMLEvent next = readNoWhitespace(reader);
855
			if (isMyEndingElement(next, parentEvent)) {
856
				if (StringUtils.isNotBlank(text)) {
857
					text = normalize(text);text = removeTrailingPunctuation(text);
858
					if (isLocality) {
859
						facade.setLocality(text, getDefaultLanguage(state));
860
					} else {
861
						text = CdmUtils.removeTrailingDot(text);
862
						NamedArea area = makeArea(state, text, areaLevel);
863
						facade.addCollectingArea(area);
864
					}
865
				}
866
				// TODO
867
				return;
868
			}else if (isStartingElement(next, ALTITUDE)) {
869
				handleNotYetImplementedElement(next);
870
				// homotypicalGroup = handleNom(state, reader, next, taxon,
871
				// homotypicalGroup);
872
			} else if (isStartingElement(next, COORDINATES)) {
873
				handleNotYetImplementedElement(next);
874
			} else if (isStartingElement(next, ANNOTATION)) {
875
				handleNotYetImplementedElement(next);
876
			} else if (next.isCharacters()) {
877
				text += next.asCharacters().getData();
878
			} else {
879
				handleUnexpectedElement(next);
880
			}
881
		}
882
		throw new IllegalStateException("<SpecimenType> has no closing tag");
883
	}
884

    
885

    
886

    
887
	/**
888
     * @param text
889
     * @return
890
     */
891
    private String removeTrailingPunctuation(String text) {
892
        while (isPunctuation(text.substring(text.length()-1))){
893
            text = text.substring(0, text.length()-1).trim();
894
        }
895
        return text;
896
    }
897

    
898

    
899
    private TeamOrPersonBase<?> createCollector(MarkupImportState state, String collectorStr) {
900
		return createAuthor(state, collectorStr);
901
	}
902

    
903

    
904
	public List<DescriptionElementBase> handleMaterialsExamined(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, Feature feature, TaxonDescription defaultDescription) throws XMLStreamException {
905
		List<DescriptionElementBase> result = new ArrayList<DescriptionElementBase>();
906
		//reset current areas
907
		state.removeCurrentAreas();
908
		while (reader.hasNext()) {
909
			XMLEvent next = readNoWhitespace(reader);
910
			if (isMyEndingElement(next, parentEvent)) {
911
				if (result.isEmpty()){
912
					fireWarningEvent("Materials examined created empty Individual Associations list", parentEvent, 4);
913
				}
914
				state.removeCurrentAreas();
915
				return result;
916
			} else if (isStartingElement(next, SUB_HEADING)) {
917
//				Map<String, Object> inlineMarkup = new HashMap<String, Object>();
918
				String text = getCData(state, reader, next, true);
919
				if (isFeatureHeading(state, next, text)){
920
					feature = makeHeadingFeature(state, next, text, feature);
921
				}else{
922
					String message = "Unhandled subheading: %s";
923
					fireWarningEvent(String.format(message,  text), next, 4);
924
				}
925
//				for (String key : inlineMarkup.keySet()){
926
//					handleInlineMarkup(state, key, inlineMarkup);
927
//				}
928

    
929
			} else if (isStartingElement(next, BR) || isEndingElement(next, BR)) {
930
				//do nothing
931
			} else if (isStartingElement(next, GATHERING)) {
932
				DerivedUnitFacade facade = DerivedUnitFacade.NewInstance(SpecimenOrObservationType.DerivedUnit);
933
				addCurrentAreas(state, next, facade);
934
				handleGathering(state, reader, next, facade);
935
				SpecimenOrObservationBase<?> specimen;
936
				if (facade.innerDerivedUnit() != null){
937
					specimen = facade.innerDerivedUnit();
938
				}else{
939
					specimen = facade.innerFieldUnit();
940
				}
941
				IndividualsAssociation individualsAssociation = IndividualsAssociation.NewInstance();
942
				individualsAssociation.addPrimaryTaxonomicSource(state.getConfig().getSourceReference());
943
				individualsAssociation.setAssociatedSpecimenOrObservation(specimen);
944
				result.add(individualsAssociation);
945
			} else if (isStartingElement(next, GATHERING_GROUP)) {
946
				List<DescriptionElementBase> list = getGatheringGroupDescription(state, reader, next);
947
				result.addAll(list);
948
			}else if (next.isCharacters()) {
949
				String text = next.asCharacters().getData().trim();
950
				if (isPunctuation(text)){
951
					//do nothing
952
				}else{
953
					String message = "Unrecognized text: %s";
954
					fireWarningEvent(String.format(message, text), next, 6);
955
				}
956
			} else {
957
				handleUnexpectedElement(next);
958
			}
959
		}
960
		throw new IllegalStateException("<String> has no closing tag");
961

    
962
	}
963

    
964

    
965

    
966
	private List<DescriptionElementBase> getGatheringGroupDescription(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
967
		Map<String, Attribute> attributes = getAttributes(parentEvent);
968
		String geoScope = getAndRemoveAttributeValue(attributes, "geoscope");
969
		Boolean doubtful = getAndRemoveBooleanAttributeValue(parentEvent, attributes, DOUBTFUL, null);
970
		checkNoAttributes(attributes, parentEvent);
971

    
972
		List<DescriptionElementBase> result = new ArrayList<DescriptionElementBase>();
973

    
974

    
975
		TaxonDescription td = null;
976

    
977
		if (isNotBlank(geoScope)){
978
			NamedArea area = Country.getCountryByLabel(geoScope);
979
			if (area == null){
980
				try {
981
					area = state.getTransformer().getNamedAreaByKey(geoScope);
982
				} catch (Exception e) {
983
					fireWarningEvent("getNamedArea not supported", parentEvent, 16);
984
				}
985
			}
986
			if (area == null){
987
				fireWarningEvent("Area for geoscope not found: " +  geoScope +"; add specimen group to ordinary description", parentEvent, 4);
988
			}else{
989
				state.addCurrentArea(area);
990
				Set<TaxonDescription> descs = state.getCurrentTaxon().getDescriptions();
991
				for (TaxonDescription desc : descs){
992
					Set<NamedArea> scopes = desc.getGeoScopes();
993
					if (scopes.size() == 1 && scopes.iterator().next().equals(area)){
994
						td = desc;
995
						break;
996
					}
997
				}
998
				if (td == null){
999
					TaxonDescription desc = TaxonDescription.NewInstance(state.getCurrentTaxon());
1000
					desc.addPrimaryTaxonomicSource(state.getConfig().getSourceReference(), null);
1001
					desc.addGeoScope(area);
1002
					if (doubtful != null){
1003
						desc.addMarker(Marker.NewInstance(MarkerType.IS_DOUBTFUL(), doubtful));
1004
					}
1005
					td = desc;
1006
				}
1007
			}
1008
		}
1009

    
1010
		while (reader.hasNext()) {
1011
			XMLEvent next = readNoWhitespace(reader);
1012
			if (isMyEndingElement(next, parentEvent)) {
1013
				if (result.isEmpty()){
1014
					fireWarningEvent("Gathering group created empty Individual Associations list", parentEvent, 4);
1015
				}
1016
				state.removeCurrentAreas();
1017
				return result;
1018
			} else if (isStartingElement(next, GATHERING)) {
1019
				DerivedUnitFacade facade = DerivedUnitFacade.NewInstance(SpecimenOrObservationType.DerivedUnit);
1020
				addCurrentAreas(state, next, facade);
1021
				handleGathering(state, reader, next, facade);
1022
				SpecimenOrObservationBase<?> specimen;
1023
				if (facade.innerDerivedUnit() != null){
1024
					specimen = facade.innerDerivedUnit();
1025
				}else{
1026
					specimen = facade.innerFieldUnit();
1027
				}
1028
				IndividualsAssociation individualsAssociation = IndividualsAssociation.NewInstance();
1029
				individualsAssociation.addPrimaryTaxonomicSource(state.getConfig().getSourceReference());
1030
				individualsAssociation.setAssociatedSpecimenOrObservation(specimen);
1031
				result.add(individualsAssociation);
1032

    
1033
			}else if (next.isCharacters()) {
1034
				String text = next.asCharacters().getData().trim();
1035
				if (isPunctuation(text)){
1036
					//do nothing
1037
				}else{
1038
					//TODO
1039
					String message = "Unrecognized text: %s";
1040
					fireWarningEvent(String.format(message, text), next, 6);
1041
				}
1042
			} else {
1043
				handleUnexpectedElement(next);
1044
			}
1045
		}
1046
		throw new IllegalStateException("<Gathering group> has no closing tag");
1047

    
1048
	}
1049

    
1050
	private void addCurrentAreas(MarkupImportState state, XMLEvent event, DerivedUnitFacade facade) {
1051
		for (NamedArea area : state.getCurrentAreas()){
1052
			if (area == null){
1053
				continue;
1054
			}else if (area.isInstanceOf(Country.class)){
1055
				facade.setCountry(area);
1056
			}else{
1057
				String message = "Current area %s is not country. This is not expected for currently known data.";
1058
				fireWarningEvent(String.format(message, area.getTitleCache()), event, 2);
1059
				facade.addCollectingArea(area);
1060
			}
1061
		}
1062

    
1063
	}
1064

    
1065

    
1066
//	private void handleInlineMarkup(MarkupImportState state, String key, Map<String, Object> inlineMarkup) {
1067
//		Object obj = inlineMarkup.get(key);
1068
//		if (key.equals(LOCALITY)){
1069
//			if (obj instanceof NamedArea){
1070
//				NamedArea area = (NamedArea)obj;
1071
//				state.addCurrentArea(area);
1072
//			}
1073
//		}
1074
//
1075
//	}
1076

    
1077

    
1078
	/**
1079
	 * Changes the feature if the (sub)-heading implies this. Also recognizes hidden country information
1080
	 * @param state
1081
	 * @param parent
1082
	 * @param text
1083
	 * @param feature
1084
	 * @return
1085
	 */
1086
	private Feature makeHeadingFeature(MarkupImportState state, XMLEvent parent, String originalText, Feature feature) {
1087
		//expand, provide by config or service
1088
		String materialRegEx = "Mat[\u00E9\u00C9]riel";
1089
		String examinedRegEx = "[\u00E9\u00C9]tudi[\u00E9\u00C9]";
1090
		String countryRegEx = "(gabonais)";
1091
		String postfixCountryRegEx = "\\s+(pour le Gabon)";
1092

    
1093
		String materialExaminedRegEx = "(?i)" + materialRegEx + "\\s+(" + countryRegEx +"\\s+)?" + examinedRegEx + "(" +postfixCountryRegEx + ")?:?";
1094

    
1095
		String text = originalText;
1096

    
1097
		if (isBlank(text)){
1098
			return feature;
1099
		}else{
1100
			if (text.matches(materialExaminedRegEx)){
1101
				//gabon specific
1102
				if (text.contains("gabonais ")){
1103
					text = text.replace("gabonais ", "");
1104
					state.addCurrentArea(Country.GABONGABONESEREPUBLIC());
1105
				}
1106
				if (text.contains(" pour le Gabon")){
1107
					text = text.replace(" pour le Gabon", "");
1108
					state.addCurrentArea(Country.GABONGABONESEREPUBLIC());
1109
				}
1110

    
1111
				//update feature
1112
				feature = Feature.MATERIALS_EXAMINED();
1113
				state.putFeatureToGeneralSorterList(feature);
1114
				return feature;
1115
			}else{
1116
				String message = "Heading/Subheading not recognized: %s";
1117
				fireWarningEvent(String.format(message, originalText), parent, 4);
1118
				return feature;
1119
			}
1120
		}
1121
	}
1122

    
1123

    
1124
	/**
1125
	 * True if heading or subheading represents feature information
1126
	 * @param state
1127
	 * @param parent
1128
	 * @param text
1129
	 * @return
1130
	 */
1131
	private boolean isFeatureHeading(MarkupImportState state, XMLEvent parent, String text) {
1132
		return makeHeadingFeature(state, parent, text, null) != null;
1133
	}
1134

    
1135

    
1136
	public String handleInLineGathering(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
1137
		DerivedUnitFacade facade = DerivedUnitFacade.NewInstance(SpecimenOrObservationType.FieldUnit);
1138
		handleGathering(state, reader, parentEvent, facade);
1139
		SpecimenOrObservationBase<?> specimen  = facade.innerFieldUnit();
1140
		if (specimen == null){
1141
			specimen = facade.innerDerivedUnit();
1142
			String message = "Inline gaterhing has no field unit";
1143
			fireWarningEvent(message, parentEvent, 2);
1144
		}
1145

    
1146
		String result = "<cdm:specimen uuid='%s'>%s</specimen>";
1147
		if (specimen != null){
1148
			result = String.format(result, specimen.getUuid(), specimen.getTitleCache());
1149
		}else{
1150
			String message = "Inline gathering has no specimen";
1151
			fireWarningEvent(message, parentEvent, 4);
1152
		}
1153
		save(specimen, state);
1154
		return result;
1155
	}
1156

    
1157

    
1158

    
1159

    
1160

    
1161
}
(16-16/19)