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.logging.log4j.LogManager;import org.apache.logging.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.Language;
37
import eu.etaxonomy.cdm.model.common.Marker;
38
import eu.etaxonomy.cdm.model.common.MarkerType;
39
import eu.etaxonomy.cdm.model.common.TimePeriod;
40
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
41
import eu.etaxonomy.cdm.model.description.Feature;
42
import eu.etaxonomy.cdm.model.description.IndividualsAssociation;
43
import eu.etaxonomy.cdm.model.description.TaxonDescription;
44
import eu.etaxonomy.cdm.model.location.Country;
45
import eu.etaxonomy.cdm.model.location.NamedArea;
46
import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
47
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
48
import eu.etaxonomy.cdm.model.name.INonViralName;
49
import eu.etaxonomy.cdm.model.name.Rank;
50
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
51
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignationStatus;
52
import eu.etaxonomy.cdm.model.name.TaxonName;
53
import eu.etaxonomy.cdm.model.occurrence.Collection;
54
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
55
import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
56
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
57
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
58
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
59
import eu.etaxonomy.cdm.model.reference.Reference;
60
import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
61
import eu.etaxonomy.cdm.model.term.DefinedTerm;
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 = LogManager.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 givenName = 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
		    givenName = 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(), givenName, 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(DerivedUnitFacadeCacheStrategy.NewInstance());
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
    private void handleNotSeen(MarkupImportState state, DerivedUnit typeSpecimen, TypeInfo typeInfo) {
246
        if (typeInfo.notSeen){
247
            String text = "n.v. for " + state.getConfig().getSourceReference().getAbbrevTitleCache();
248
            typeSpecimen.addAnnotation(Annotation.NewInstance(text, AnnotationType.EDITORIAL(), getDefaultLanguage(state)));
249
            if(state.getConfig().getSpecimenNotSeenMarkerTypeUuid() != null){
250
                UUID uuidNotSeenMarker = state.getConfig().getSpecimenNotSeenMarkerTypeUuid();
251
                String markerTypeNotSeenLabel = state.getConfig().getSpecimenNotSeenMarkerTypeLabel();
252
                markerTypeNotSeenLabel = markerTypeNotSeenLabel == null ? "Not seen" : markerTypeNotSeenLabel;
253
                MarkerType notSeenMarkerType = getMarkerType(state, uuidNotSeenMarker, markerTypeNotSeenLabel, markerTypeNotSeenLabel, null, null);
254
                Marker marker = Marker.NewInstance(notSeenMarkerType, true);
255
                typeSpecimen.addMarker(marker);
256
            }
257
        }
258
    }
259

    
260

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

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

    
285
			String pattern = String.format("(%s|%s)", notDesignatedRE, designatedRE );
286
			fotgTypePattern = Pattern.compile(pattern);
287
		}
288
		Matcher matcher = fotgTypePattern.matcher(collectionAndType);
289

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

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

    
323
				List<SpecimenTypeDesignation> designations = new ArrayList<SpecimenTypeDesignation>();
324

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

    
347

    
348
					//collection
349
					Pattern collectionPattern = Pattern.compile("^" + collectionsRE);
350
					matcher = collectionPattern.matcher(singleType);
351
					String[] collectionStrings = new String[0];
352
					if (matcher.find()){
353
						String collectionStr = matcher.group(0);
354
						singleType = singleType.substring(collectionStr.length());
355
						collectionStr = collectionStr.replace("(", "").replace(")", "").replaceAll("\\s", "");
356
						collectionStrings = collectionStr.split(",");
357
					}
358

    
359
					//addInfo
360
					if (!singleType.isEmpty() && singleType.startsWith(", ")){
361
						singleType = singleType.substring(2);
362
					}
363

    
364
					boolean notSeen = false;
365
					if (singleType.equals("not seen")){
366
						singleType = singleType.replace("not seen", "");
367
						notSeen = true;
368
					}
369
					if (singleType.startsWith("not seen, ")){
370
						singleType = singleType.replace("not seen, ", "");
371
						notSeen = true;
372
					}
373
					boolean destroyed = false;
374
					if (singleType.equals("destroyed")){
375
						destroyed = true;
376
						singleType = singleType.replace("destroyed", "");
377
					}
378
					boolean presumedDestroyed = false;
379
					if (singleType.equals("presumed destroyed")){
380
						presumedDestroyed = true;
381
						singleType = singleType.replace("presumed destroyed", "");
382
					}
383
					boolean hasAddInfo = notSeen || destroyed || presumedDestroyed;
384

    
385

    
386
					if (!singleType.isEmpty()){
387
						String message = "SingleType was not fully read. Remaining: " + singleType + ". Original singleType was: " + singleTypeOrig;
388
						fireWarningEvent(message, parentEvent, 6);
389
						System.out.println(message);
390
					}
391

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

    
419
				if (designatedBy != null){
420
					if (designations.size() != 1){
421
						fireWarningEvent("Size of type designations is not exactly 1, which is expected for 'designated by'", parentEvent, 2);
422
					}
423
					designatedBy = designatedBy.trim();
424
					if (designatedBy.startsWith("(") && designatedBy.endsWith(")") ){
425
						designatedBy = designatedBy.substring(1, designatedBy.length() - 1);
426
					}
427

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

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

    
481

    
482
	/**
483
	 * @param notSeen
484
	 * @param destroyed
485
	 * @param presumedDestroyed
486
	 * @param desig
487
	 */
488
	private void handleSpecimenTypeAddInfo(MarkupImportState state, boolean notSeen, boolean destroyed,
489
			boolean presumedDestroyed, SpecimenTypeDesignation desig) {
490
		DerivedUnit specimen = desig.getTypeSpecimen();
491
		AnnotatableEntity annotEntity = specimen != null ? specimen : desig;
492

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

    
514

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

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

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

    
575

    
576
	private void handleGathering(MarkupImportState state, XMLEventReader readerOrig, XMLEvent parentEvent , DerivedUnitFacade facade) throws XMLStreamException {
577
		checkNoAttributes(parentEvent);
578
		boolean hasCollector = false;
579
		boolean hasFieldNum = false;
580

    
581
		LookAheadEventReader reader = new LookAheadEventReader(parentEvent.asStartElement(), readerOrig);
582

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

    
658
	}
659

    
660

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

    
665
		XMLEvent next = readNoWhitespace(reader);
666

    
667
		if (next.isCharacters()){
668
			String txt = next.asCharacters().getData().trim();
669
			if (state.isSpecimenType()){
670
				state.addCollectionAndType(txt);
671
			}else{
672

    
673
				Matcher fotgMatcher = Pattern.compile(fotgPattern).matcher(txt);
674

    
675
				if (fotgMatcher.matches()){
676
					txt = txt.substring(1, txt.length() - 1);  //remove bracket
677
					String[] splits = txt.split(",");
678
					for (String split : splits ){
679
						Collection collection = getCollection(state, split.trim());
680
						if (facade.innerDerivedUnit() == null){
681
						    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!";
682
						    this.fireWarningEvent(message, next, -6);
683
						}else{
684
						    facade.addDuplicate(collection, null, null, null, null);
685
						}
686
					}
687
					//FIXME 9
688
					//create derived units and and add collections
689

    
690
				}else{
691
					fireWarningEvent("Collection and type pattern for gathering not recognized: " + txt, next, 4);
692
				}
693
			}
694

    
695
		}else{
696
			fireUnexpectedEvent(next, 0);
697
		}
698

    
699
		if (isMyEndingElement(next, parent)){
700
			return;  //in case we have a completely empty element
701
		}
702
		next = readNoWhitespace(reader);
703
		if (isMyEndingElement(next, parent)){
704
			return;
705
		}else{
706
			fireUnexpectedEvent(next, 0);
707
			return;
708
		}
709
	}
710

    
711

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

    
722
			if (collection == null){
723
				collection = Collection.NewInstance();
724
				collection.setCode(code);
725
				this.docImport.getCollectionService().saveOrUpdate(collection);
726
			}
727
			state.putCollectionByCode(code, collection);
728
		}
729
		return collection;
730
	}
731

    
732

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

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

    
746
	}
747

    
748

    
749
	private boolean charIsOpeningOrClosingBracket(String text) {
750
		return text.equals("(") || text.equals(")");
751
	}
752

    
753

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

    
816

    
817
	private String normalizeDate(String partOfDate) {
818
		if (isBlank(partOfDate)){
819
			return null;
820
		}
821
		partOfDate = partOfDate.trim();
822
		while (partOfDate.startsWith("-")){
823
			partOfDate = partOfDate.substring(1);
824
		}
825
		return partOfDate;
826
	}
827

    
828

    
829
	private boolean isAlternative(boolean first, boolean second, boolean third) {
830
		return ( (first ^ second) && !third)  ||
831
				(! first && ! second && third) ;
832
	}
833

    
834

    
835
	private void handleLocality(MarkupImportState state, XMLEventReader reader,XMLEvent parentEvent, DerivedUnitFacade facade)throws XMLStreamException {
836
		String classValue = getClassOnlyAttribute(parentEvent);
837
		boolean isLocality = false;
838
		NamedAreaLevel areaLevel = null;
839
		if ("locality".equalsIgnoreCase(classValue)||state.getConfig().isIgnoreLocalityClass()) {
840
			isLocality = true;
841
		} else {
842
			areaLevel = makeNamedAreaLevel(state, classValue, parentEvent);
843
		}
844

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

    
879

    
880

    
881
	/**
882
     * @param text
883
     * @return
884
     */
885
    private String removeTrailingPunctuation(String text) {
886
        while (isPunctuation(text.substring(text.length()-1))){
887
            text = text.substring(0, text.length()-1).trim();
888
        }
889
        return text;
890
    }
891

    
892

    
893
    private TeamOrPersonBase<?> createCollector(MarkupImportState state, String collectorStr) {
894
		return createAuthor(state, collectorStr);
895
	}
896

    
897

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

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

    
956
	}
957

    
958

    
959

    
960
	private List<DescriptionElementBase> getGatheringGroupDescription(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
961
		Map<String, Attribute> attributes = getAttributes(parentEvent);
962
		String geoScope = getAndRemoveAttributeValue(attributes, "geoscope");
963
		Boolean doubtful = getAndRemoveBooleanAttributeValue(parentEvent, attributes, DOUBTFUL, null);
964
		checkNoAttributes(attributes, parentEvent);
965

    
966
		List<DescriptionElementBase> result = new ArrayList<DescriptionElementBase>();
967

    
968

    
969
		TaxonDescription td = null;
970

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

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

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

    
1042
	}
1043

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

    
1057
	}
1058

    
1059

    
1060
//	private void handleInlineMarkup(MarkupImportState state, String key, Map<String, Object> inlineMarkup) {
1061
//		Object obj = inlineMarkup.get(key);
1062
//		if (key.equals(LOCALITY)){
1063
//			if (obj instanceof NamedArea){
1064
//				NamedArea area = (NamedArea)obj;
1065
//				state.addCurrentArea(area);
1066
//			}
1067
//		}
1068
//
1069
//	}
1070

    
1071

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

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

    
1089
		String text = originalText;
1090

    
1091
		if (isBlank(text)){
1092
			return feature;
1093
		}else{
1094
			if (text.matches(materialExaminedRegEx)){
1095
				//gabon specific
1096
				if (text.contains("gabonais ")){
1097
					text = text.replace("gabonais ", "");
1098
					state.addCurrentArea(Country.GABONGABONESEREPUBLIC());
1099
				}
1100
				if (text.contains(" pour le Gabon")){
1101
					text = text.replace(" pour le Gabon", "");
1102
					state.addCurrentArea(Country.GABONGABONESEREPUBLIC());
1103
				}
1104

    
1105
				//update feature
1106
				feature = Feature.MATERIALS_EXAMINED();
1107
				state.putFeatureToGeneralSorterList(feature);
1108
				return feature;
1109
			}else{
1110
				String message = "Heading/Subheading not recognized: %s";
1111
				fireWarningEvent(String.format(message, originalText), parent, 4);
1112
				return feature;
1113
			}
1114
		}
1115
	}
1116

    
1117

    
1118
	/**
1119
	 * True if heading or subheading represents feature information
1120
	 * @param state
1121
	 * @param parent
1122
	 * @param text
1123
	 * @return
1124
	 */
1125
	private boolean isFeatureHeading(MarkupImportState state, XMLEvent parent, String text) {
1126
		return makeHeadingFeature(state, parent, text, null) != null;
1127
	}
1128

    
1129

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

    
1140
		String result = "<cdm:specimen uuid='%s'>%s</specimen>";
1141
		if (specimen != null){
1142
			result = String.format(result, specimen.getUuid(), specimen.getTitleCache());
1143
		}else{
1144
			String message = "Inline gathering has no specimen";
1145
			fireWarningEvent(message, parentEvent, 4);
1146
		}
1147
		save(specimen, state);
1148
		return result;
1149
	}
1150

    
1151

    
1152

    
1153

    
1154

    
1155
}
(16-16/19)