Project

General

Profile

Download (90.9 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2007 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.net.MalformedURLException;
13
import java.util.ArrayList;
14
import java.util.Arrays;
15
import java.util.Collection;
16
import java.util.HashMap;
17
import java.util.HashSet;
18
import java.util.Iterator;
19
import java.util.List;
20
import java.util.Map;
21
import java.util.Set;
22
import java.util.Stack;
23
import java.util.UUID;
24
import java.util.regex.Matcher;
25
import java.util.regex.Pattern;
26

    
27
import javax.xml.namespace.QName;
28
import javax.xml.stream.Location;
29
import javax.xml.stream.XMLEventReader;
30
import javax.xml.stream.XMLStreamConstants;
31
import javax.xml.stream.XMLStreamException;
32
import javax.xml.stream.events.Attribute;
33
import javax.xml.stream.events.Characters;
34
import javax.xml.stream.events.EndElement;
35
import javax.xml.stream.events.StartElement;
36
import javax.xml.stream.events.XMLEvent;
37

    
38
import org.apache.commons.lang.StringUtils;
39
import org.apache.commons.lang.WordUtils;
40
import org.apache.logging.log4j.LogManager;
41
import org.apache.logging.log4j.Logger;
42

    
43
import eu.etaxonomy.cdm.api.service.IClassificationService;
44
import eu.etaxonomy.cdm.api.service.ITermService;
45
import eu.etaxonomy.cdm.common.CdmUtils;
46
import eu.etaxonomy.cdm.ext.geo.GeoServiceArea;
47
import eu.etaxonomy.cdm.ext.geo.IEditGeoService;
48
import eu.etaxonomy.cdm.io.common.CdmImportBase;
49
import eu.etaxonomy.cdm.io.common.CdmImportBase.TermMatchMode;
50
import eu.etaxonomy.cdm.io.common.events.IIoEvent;
51
import eu.etaxonomy.cdm.io.common.events.IoProblemEvent;
52
import eu.etaxonomy.cdm.io.common.mapping.UndefinedTransformerMethodException;
53
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
54
import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
55
import eu.etaxonomy.cdm.model.common.Annotation;
56
import eu.etaxonomy.cdm.model.common.AnnotationType;
57
import eu.etaxonomy.cdm.model.common.CdmBase;
58
import eu.etaxonomy.cdm.model.common.Extension;
59
import eu.etaxonomy.cdm.model.common.ExtensionType;
60
import eu.etaxonomy.cdm.model.common.IntextReference;
61
import eu.etaxonomy.cdm.model.common.Language;
62
import eu.etaxonomy.cdm.model.common.MarkerType;
63
import eu.etaxonomy.cdm.model.common.VerbatimTimePeriod;
64
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
65
import eu.etaxonomy.cdm.model.description.Distribution;
66
import eu.etaxonomy.cdm.model.description.Feature;
67
import eu.etaxonomy.cdm.model.description.PolytomousKey;
68
import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
69
import eu.etaxonomy.cdm.model.description.TaxonDescription;
70
import eu.etaxonomy.cdm.model.description.TextData;
71
import eu.etaxonomy.cdm.model.location.NamedArea;
72
import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
73
import eu.etaxonomy.cdm.model.location.NamedAreaType;
74
import eu.etaxonomy.cdm.model.media.IdentifiableMediaEntity;
75
import eu.etaxonomy.cdm.model.media.Media;
76
import eu.etaxonomy.cdm.model.name.INonViralName;
77
import eu.etaxonomy.cdm.model.name.NomenclaturalCode;
78
import eu.etaxonomy.cdm.model.name.Rank;
79
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
80
import eu.etaxonomy.cdm.model.reference.IArticle;
81
import eu.etaxonomy.cdm.model.reference.IBook;
82
import eu.etaxonomy.cdm.model.reference.IBookSection;
83
import eu.etaxonomy.cdm.model.reference.IJournal;
84
import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
85
import eu.etaxonomy.cdm.model.reference.Reference;
86
import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
87
import eu.etaxonomy.cdm.model.reference.ReferenceType;
88
import eu.etaxonomy.cdm.model.taxon.Classification;
89
import eu.etaxonomy.cdm.model.taxon.Taxon;
90
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
91
import eu.etaxonomy.cdm.model.term.DefinedTerm;
92
import eu.etaxonomy.cdm.model.term.DefinedTermBase;
93
import eu.etaxonomy.cdm.model.term.OrderedTermVocabulary;
94
import eu.etaxonomy.cdm.model.term.TermVocabulary;
95
import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
96
import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl;
97
import eu.etaxonomy.cdm.strategy.parser.TimePeriodParser;
98

    
99
/**
100
 * @author a.mueller
101
 * @since 04.08.2008
102
 */
103
public abstract class MarkupImportBase  {
104
	private static final Logger logger = LogManager.getLogger(MarkupImportBase.class);
105

    
106
	//Base
107
	protected static final String ALTITUDE = "altitude";
108
	protected static final String ANNOTATION = "annotation";
109
	protected static final String BOLD = "bold";
110
	protected static final String BR = "br";
111
	protected static final String DOUBTFUL = "doubtful";
112
	protected static final String CITATION = "citation";
113
	protected static final String CLASS = "class";
114
	protected static final String COORDINATES = "coordinates";
115
	protected static final String DATES = "dates";
116
	protected static final String GATHERING = "gathering";
117
	protected static final String GATHERING_GROUP = "gatheringGroup";
118
	protected static final String GENUS_ABBREVIATION = "genus abbreviation";
119
	protected static final String FOOTNOTE = "footnote";
120
	protected static final String FOOTNOTE_REF = "footnoteRef";
121
	protected static final String FULL_NAME = "fullName";
122
	protected static final String ITALICS = "italics";
123
	protected static final String NUM = "num";
124
	protected static final String NOTES = "notes";
125
	protected static final String PUBLICATION = "publication";
126
	protected static final String SPECIMEN_TYPE = "specimenType";
127
	protected static final String STATUS = "status";
128
	protected static final String SUB_HEADING = "subHeading";
129
	protected static final String TYPE = "type";
130
	protected static final String TYPE_STATUS = "typeStatus";
131
	protected static final String UNKNOWN = "unknown";
132

    
133

    
134
	protected static final boolean CREATE_NEW = true;
135
	protected static final boolean NO_IMAGE_GALLERY = false;
136
	protected static final boolean IMAGE_GALLERY = true;
137

    
138
	protected static final String ADDENDA = "addenda";
139
	protected static final String BIBLIOGRAPHY = "bibliography";
140
	protected static final String BIOGRAPHIES = "biographies";
141
	protected static final String CHAR = "char";
142
	protected static final String DEDICATION = "dedication";
143
	protected static final String DEFAULT_MEDIA_URL = "defaultMediaUrl";
144
	protected static final String DISTRIBUTION_LIST = "distributionList";
145
	protected static final String DISTRIBUTION_LOCALITY = "distributionLocality";
146
	protected static final String FEATURE = "feature";
147
	protected static final String FIGURE = "figure";
148
	protected static final String FIGURE_LEGEND = "figureLegend";
149
	protected static final String FIGURE_PART = "figurePart";
150
	protected static final String FIGURE_REF = "figureRef";
151
	protected static final String FIGURE_TITLE = "figureTitle";
152
	protected static final String FOOTNOTE_STRING = "footnoteString";
153
	protected static final String FREQUENCY = "frequency";
154
	protected static final String HEADING = "heading";
155
	protected static final String HABITAT = "habitat";
156
	protected static final String HABITAT_LIST = "habitatList";
157
	protected static final String IS_FREETEXT = "isFreetext";
158
	protected static final String ID = "id";
159
	protected static final String KEY = "key";
160
	protected static final String LIFE_CYCLE_PERIODS = "lifeCyclePeriods";
161
	protected static final String META_DATA = "metaData";
162
	protected static final String MODS = "mods";
163

    
164
	protected static final String NOMENCLATURE = "nomenclature";
165
	protected static final String QUOTE = "quote";
166
	protected static final String RANK = "rank";
167
	protected static final String REF = "ref";
168
	protected static final String REF_NUM = "refNum";
169
	protected static final String REFERENCE = "reference";
170
	protected static final String REFERENCES = "references";
171
	protected static final String SUB_CHAR = "subChar";
172
	protected static final String TAXON = "taxon";
173
	protected static final String TAXONTITLE = "taxontitle";
174
	protected static final String TAXONTYPE = "taxontype";
175
	protected static final String TEXT_SECTION = "textSection";
176
	protected static final String TREATMENT = "treatment";
177
	protected static final String SERIALS_ABBREVIATIONS = "serialsAbbreviations";
178
	protected static final String STRING = "string";
179
	protected static final String URL = "url";
180
	protected static final String WRITER = "writer";
181

    
182
	protected static final String LOCALITY = "locality";
183

    
184

    
185

    
186
	//Nomenclature
187
	protected static final String ACCEPTED = "accepted";
188
	protected static final String ACCEPTED_NAME = "acceptedName";
189
	protected static final String ALTERNATEPUBTITLE = "alternatepubtitle";
190
	protected static final String APPENDIX = "appendix";
191
	protected static final String AUTHOR = "author";
192
	protected static final String DETAILS = "details";
193
	protected static final String EDITION = "edition";
194
	protected static final String EDITORS = "editors";
195
	protected static final String HOMONYM = "homonym";
196
	protected static final String HOMOTYPES = "homotypes";
197
	protected static final String NOMENCLATURAL_NOTES = "nomenclaturalNotes";
198
	protected static final String INFRANK = "infrank";
199
	protected static final String INFRAUT = "infraut";
200
	protected static final String INFRPARAUT = "infrparaut";
201
	protected static final String ISSUE = "issue";
202
	protected static final String NAME_STATUS = "namestatus";
203
	protected static final String NAME = "name";
204
	protected static final String NAME_TYPE = "nameType";
205
	protected static final String NOM = "nom";
206
	protected static final String PAGES = "pages";
207
	protected static final String PARAUT = "paraut";
208
	protected static final String PUBFULLNAME = "pubfullname";
209
	protected static final String PUBLOCATION = "publocation";
210
	protected static final String PUBLISHER = "publisher";
211
	protected static final String PUBNAME = "pubname";
212
	protected static final String PUBTITLE = "pubtitle";
213
	protected static final String PUBTYPE = "pubtype";
214
	protected static final String REF_PART = "refPart";
215
	protected static final String SYNONYM = "synonym";
216
	protected static final String USAGE = "usage";
217
	protected static final String VOLUME = "volume";
218
	protected static final String YEAR = "year";
219

    
220

    
221
	//keys
222
	protected static final String COUPLET = "couplet";
223
	protected static final String IS_SPOTCHARACTERS = "isSpotcharacters";
224
	protected static final String ONLY_NUMBERED_TAXA_EXIST = "onlyNumberedTaxaExist";
225
	protected static final String EXISTS = "exists";
226
	protected static final String KEYNOTES = "keynotes";
227
	protected static final String KEY_TITLE = "keyTitle";
228
	protected static final String QUESTION = "question";
229
	protected static final String TEXT = "text";
230
	protected static final String TO_COUPLET = "toCouplet";
231
	protected static final String TO_KEY = "toKey";
232
	protected static final String TO_TAXON = "toTaxon";
233

    
234

    
235
	//Feature
236
	protected static final String VERNACULAR_NAMES = "vernacularNames";
237
	protected static final String VERNACULAR_NAME = "vernacularName";
238
	protected static final String TRANSLATION = "translation";
239
	protected static final String LOCAL_LANGUAGE = "localLanguage";
240

    
241

    
242

    
243
	protected MarkupDocumentImport docImport;
244

    
245
	private final IEditGeoService editGeoService;
246
	protected MarkupFeatureImport featureImport;
247

    
248
	public MarkupImportBase(MarkupDocumentImport docImport) {
249
		super();
250
		this.docImport = docImport;
251
		this.editGeoService = docImport.getEditGeoService();
252
	}
253

    
254
	private final Stack<QName> unhandledElements = new Stack<QName>();
255
	private final Stack<QName> handledElements = new Stack<QName>();
256

    
257

    
258
	protected <T extends CdmBase> void  save(Collection<T> collection, MarkupImportState state) {
259
		if (state.isCheck() || collection.isEmpty()){
260
			return;
261
		}
262
		T example = collection.iterator().next();
263
		if (example.isInstanceOf(TaxonBase.class)){
264
			Collection<TaxonBase> typedCollection = (Collection<TaxonBase>)collection;
265
			docImport.getTaxonService().saveOrUpdate(typedCollection);
266
		}else if (example.isInstanceOf(Classification.class)){
267
			Collection<Classification> typedCollection = (Collection<Classification>)collection;
268
			docImport.getClassificationService().saveOrUpdate(typedCollection);
269
		}else if (example.isInstanceOf(PolytomousKey.class)){
270
			Collection<PolytomousKey> typedCollection = (Collection<PolytomousKey>)collection;
271
			docImport.getPolytomousKeyService().saveOrUpdate(typedCollection);
272
		}else if (example.isInstanceOf(DefinedTermBase.class)){
273
			Collection<DefinedTermBase> typedCollection = (Collection<DefinedTermBase>)collection;
274
			getTermService().saveOrUpdate(typedCollection);
275
		}
276

    
277
	}
278

    
279

    
280
	//TODO move to service layer for all IdentifiableEntities
281
	protected void save(CdmBase cdmBase, MarkupImportState state) {
282
		if (state.isCheck()){
283
			return;
284
		}
285
		cdmBase = CdmBase.deproxy(cdmBase, CdmBase.class);
286
		if (cdmBase == null){
287
			String message = "Tried to save a null object.";
288
			fireWarningEvent(message, "--location ?? --", 6,1);
289
		} else if (cdmBase.isInstanceOf(TaxonBase.class)){
290
			docImport.getTaxonService().saveOrUpdate((TaxonBase<?>)cdmBase);
291
		}else if (cdmBase.isInstanceOf(Classification.class)){
292
			docImport.getClassificationService().saveOrUpdate((Classification)cdmBase);
293
		}else if (cdmBase.isInstanceOf(PolytomousKey.class)){
294
			docImport.getPolytomousKeyService().saveOrUpdate((PolytomousKey)cdmBase);
295
		}else if (cdmBase.isInstanceOf(DefinedTermBase.class)){
296
			docImport.getTermService().saveOrUpdate((DefinedTermBase<?>)cdmBase);
297
		}else if (cdmBase.isInstanceOf(Media.class)){
298
			docImport.getMediaService().saveOrUpdate((Media)cdmBase);
299
		}else if (cdmBase.isInstanceOf(SpecimenOrObservationBase.class)){
300
			docImport.getOccurrenceService().saveOrUpdate((SpecimenOrObservationBase<?>)cdmBase);
301
		}else if (cdmBase.isInstanceOf(DescriptionElementBase.class)){
302
			docImport.getDescriptionElementService().save((DescriptionElementBase)cdmBase);
303
		}else if (cdmBase.isInstanceOf(Reference.class)){
304
			docImport.getReferenceService().saveOrUpdate((Reference)cdmBase);
305
		}else{
306
			String message = "Unknown cdmBase type to save: " + cdmBase.getClass();
307
			fireWarningEvent(message, "Unknown location", 8);
308
		}
309
		//logger.warn("Saved " +  cdmBase);
310
	}
311

    
312

    
313
	protected ITermService getTermService() {
314
		return docImport.getTermService();
315
	}
316

    
317
	protected IClassificationService getClassificationService() {
318
		return docImport.getClassificationService();
319
	}
320

    
321
//*********************** Attribute methods *************************************/
322

    
323
	/**
324
	 * Returns a map for all attributes of an start element
325
	 * @param event
326
	 * @return
327
	 */
328
	protected Map<String, Attribute> getAttributes(XMLEvent event) {
329
		Map<String, Attribute> result = new HashMap<>();
330
		if (!event.isStartElement()){
331
			fireWarningEvent("Event is not an startElement. Can't check attributes", makeLocationStr(event.getLocation()), 1, 1);
332
			return result;
333
		}
334
		StartElement element = event.asStartElement();
335
		@SuppressWarnings("unchecked")
336
        Iterator<Attribute> attributes = element.getAttributes();
337
		while (attributes.hasNext()){
338
			Attribute attribute = attributes.next();
339
			//TODO namespaces
340
			result.put(attribute.getName().getLocalPart(), attribute);
341
		}
342
		return result;
343
	}
344

    
345
	/**
346
	 * Throws an unexpected attributes event if the event has any attributes.
347
	 * @param event
348
	 */
349
	protected void checkNoAttributes(Map<String, Attribute> attributes, XMLEvent event) {
350
		String[] exceptions = new String[]{};
351
		handleUnexpectedAttributes(event.getLocation(), attributes, 1, exceptions);
352
	}
353

    
354

    
355

    
356
	/**
357
	 * Throws an unexpected attributes event if the event has any attributes.
358
	 * @param event
359
	 */
360
	protected void checkNoAttributes(XMLEvent event) {
361
		String[] exceptions = new String[]{};
362
		checkNoAttributes(event, 1, exceptions);
363
	}
364

    
365
	/**
366
	 * Throws an unexpected attributes event if the event has any attributes except those mentioned in "exceptions".
367
	 * @param event
368
	 * @param exceptions
369
	 */
370
	protected void checkNoAttributes(XMLEvent event, int stackDepth, String... exceptions) {
371
		if (! event.isStartElement()){
372
			fireWarningEvent("Event is not an startElement. Can't check attributes", makeLocationStr(event.getLocation()), 1, 1);
373
			return;
374
		}
375
		StartElement startElement = event.asStartElement();
376
		Map<String, Attribute> attributes = getAttributes(startElement);
377
		handleUnexpectedAttributes(startElement.getLocation(), attributes, stackDepth+1, exceptions);
378
	}
379

    
380

    
381
	/**
382
	 * Checks if the given attribute exists and has the given value.
383
	 * If yes, true is returned and the attribute is removed from the attributes map.
384
	 * Otherwise false is returned.
385
	 * @param attributes
386
	 * @param attrName
387
	 * @param value
388
	 * @return <code>true</code> if attribute has given value, <code>false</code> otherwise
389
	 */
390
	protected boolean checkAndRemoveAttributeValue( Map<String, Attribute> attributes, String attrName, String value) {
391
		Attribute attr = attributes.get(attrName);
392
		if (attr == null ||value == null ){
393
			return false;
394
		}else{
395
			if (value.equals(attr.getValue())){
396
				attributes.remove(attrName);
397
				return true;
398
			}else{
399
				return false;
400
			}
401
		}
402
	}
403

    
404

    
405
	/**
406
	 * Returns the value of a given attribute name and removes the attribute from the attributes map.
407
	 * Returns <code>null</code> if attribute does not exist.
408
	 * @param attributes the list of all attributes
409
	 * @param attrName the requested attribute name
410
	 * @return the value for the attribute
411
	 */
412
	protected String getAndRemoveAttributeValue(Map<String, Attribute> attributes, String attrName) {
413
		return getAndRemoveAttributeValue(null, attributes, attrName, false, 1);
414
	}
415

    
416
	/**
417
	 * Returns the value of a boolean attribute with the given name and removes the attribute from the attributes map.
418
	 * Returns <code>defaultValue</code> if the attribute does not exist. ALso returns <code>defaultValue</code> and throws a warning if the
419
	 * attribute has no boolean value (true, false).
420
	 * @param
421
	 * @param attributes the
422
	 * @param attrName the name of the attribute
423
	 * @param defaultValue the default value to return if attribute does not exist or can not be defined
424
	 * @return
425
	 */
426
	protected Boolean getAndRemoveBooleanAttributeValue(XMLEvent event, Map<String, Attribute> attributes, String attrName, Boolean defaultValue) {
427
		String value = getAndRemoveAttributeValue(null, attributes, attrName, false, 1);
428
		Boolean result = defaultValue;
429
		if (value != null){
430
			if (value.equalsIgnoreCase("true")){
431
				result = true;
432
			}else if (value.equalsIgnoreCase("false")){
433
				result = false;
434
			}else{
435
				String message = "Boolean attribute has no boolean value ('true', 'false') but '%s'";
436
				fireWarningEvent(String.format(message, value), makeLocationStr(event.getLocation()), 6, 1);
437
			}
438
		}
439
		return result;
440
	}
441

    
442

    
443
	/**
444
	 * Returns the value of a given attribute name and returns the attribute from the attributes map.
445
	 * Fires a mandatory field is missing event if the attribute does not exist.
446
	 * @param xmlEvent
447
	 * @param attributes
448
	 * @param attrName
449
	 * @return
450
	 */
451
	protected String getAndRemoveRequiredAttributeValue(XMLEvent xmlEvent, Map<String, Attribute> attributes, String attrName) {
452
		return getAndRemoveAttributeValue(xmlEvent, attributes, attrName, true, 1);
453
	}
454

    
455
	/**
456
	 * Returns the value of a given attribute name and returns the attribute from the attributes map.
457
	 * If required is <code>true</code> and the attribute does not exist a mandatory field is missing event is fired.
458
	 * @param xmlEvent
459
	 * @param attributes
460
	 * @param attrName
461
	 * @param isRequired
462
	 * @return
463
	 */
464
	private String getAndRemoveAttributeValue(XMLEvent xmlEvent, Map<String, Attribute> attributes, String attrName, boolean isRequired, int stackDepth) {
465
		Attribute attr = attributes.get(attrName);
466
		if (attr == null ){
467
			if (isRequired){
468
				fireMandatoryElementIsMissing(xmlEvent, attrName, 8, stackDepth+1);
469
			}
470
			return null;
471
		}else{
472
			attributes.remove(attrName);
473
			return attr.getValue();
474
		}
475
	}
476

    
477
	/**
478
	 * Fires an not yet implemented event if the given attribute exists in attributes.
479
	 * @param attributes
480
	 * @param attrName
481
	 * @param event
482
	 */
483
	protected void handleNotYetImplementedAttribute(Map<String, Attribute>  attributes,
484
	        String attrName, XMLEvent event) {
485
		Attribute attr = attributes.get(attrName);
486
		if (attr != null){
487
			attributes.remove(attrName);
488
			QName qName = attr.getName();
489
			fireNotYetImplementedAttribute(event.getLocation(), qName, attr.getValue(), 1);
490
		}
491
	}
492

    
493
	/**
494
	 * Fires an unhandled attributes event, if attributes exist in attributes map not covered by the exceptions.
495
	 * No event is fired if the unhandled elements stack is not empty.
496
	 * @param location
497
	 * @param attributes
498
	 * @param exceptions
499
	 */
500
	protected void handleUnexpectedAttributes(Location location,Map<String, Attribute> attributes, String... exceptions) {
501
		handleUnexpectedAttributes(location, attributes, 1, exceptions);
502
	}
503

    
504
	/**
505
	 * see {@link #handleUnexpectedAttributes(Location, Map, String...)}
506
     *
507
	 * @param location
508
	 * @param attributes
509
	 * @param stackDepth the stack trace depth
510
	 * @param exceptions
511
	 */
512
	private void handleUnexpectedAttributes(Location location,Map<String, Attribute> attributes, int stackDepth, String... exceptions) {
513
		if (attributes.size() > 0){
514
			if (this.unhandledElements.size() == 0 ){
515
				boolean hasUnhandledAttributes = false;
516
				for (String key : attributes.keySet()){
517
					boolean isException = false;
518
					for (String exception : exceptions){
519
						if(key.equals(exception)){
520
							isException = true;
521
						}
522
					}
523
					if (!isException){
524
						hasUnhandledAttributes = true;
525
					}
526
				}
527
				if (hasUnhandledAttributes){
528
					fireUnexpectedAttributes(location, attributes, stackDepth+1);
529
				}
530
			}
531
		}
532
	}
533

    
534

    
535
	private void fireUnexpectedAttributes(Location location, Map<String, Attribute> attributes, int stackDepth) {
536
		String attributesString = "";
537
		for (String key : attributes.keySet()){
538
			Attribute attribute = attributes.get(key);
539
			attributesString = CdmUtils.concat(",", attributesString, attribute.getName().getLocalPart() + ":" + attribute.getValue());
540
		}
541
		String message = "Unexpected attributes: %s";
542
		IoProblemEvent event = makeProblemEvent(location, String.format(message, attributesString), 1 , stackDepth +1 );
543
		fire(event);
544
	}
545

    
546

    
547
	protected void fireUnexpectedAttributeValue(XMLEvent parentEvent, String attrName, String attrValue) {
548
		String message = "Unexpected attribute value %s='%s'";
549
		message = String.format(message, attrName, attrValue);
550
		IoProblemEvent event = makeProblemEvent(parentEvent.getLocation(), message, 1 , 1 );
551
		fire(event);
552
	}
553

    
554
	protected void handleNotYetImplementedAttributeValue(XMLEvent xmlEvent, String attrName, String attrValue) {
555
		String message = "Attribute %s not yet implemented for value '%s'";
556
		message = String.format(message, attrName, attrValue);
557
		IIoEvent event = makeProblemEvent(xmlEvent.getLocation(), message, 1, 1 );
558
		fire(event);
559
	}
560

    
561
	protected void fireNotYetImplementedAttribute(Location location, QName qName,
562
	        String value, int stackDepth) {
563
		String message = "Attribute not yet implemented: %s (%s)";
564
		IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart(), value), 1, stackDepth+1 );
565
		fire(event);
566
	}
567

    
568

    
569
	protected void fireUnexpectedEvent(XMLEvent xmlEvent, int stackDepth) {
570
		Location location = xmlEvent.getLocation();
571
		String message = "Unexpected event: %s";
572
		IIoEvent event = makeProblemEvent(location, String.format(message, xmlEvent.toString()), 2, stackDepth +1);
573
		fire(event);
574
	}
575

    
576
	protected void fireUnexpectedStartElement(Location location, StartElement startElement, int stackDepth) {
577
		QName qName = startElement.getName();
578
		String message = "Unexpected start element: %s";
579
		IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart()), 2, stackDepth +1);
580
		fire(event);
581
	}
582

    
583

    
584
	protected void fireUnexpectedEndElement(Location location, EndElement endElement, int stackDepth) {
585
		QName qName = endElement.getName();
586
		String message = "Unexpected end element: %s";
587
		IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart()), 16, stackDepth+1);
588
		fire(event);
589
	}
590

    
591
	protected void fireNotYetImplementedElement(Location location, QName qName, int stackDepth) {
592
		String message = "Element not yet implemented: %s";
593
		IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart()), 1, stackDepth+1 );
594
		fire(event);
595
	}
596

    
597
	protected void fireNotYetImplementedCharacters(Location location, Characters chars, int stackDepth) {
598
		String message = "Characters not yet handled: %s";
599
		IIoEvent event = makeProblemEvent(location, String.format(message, chars.getData()), 1, stackDepth+1 );
600
		fire(event);
601
	}
602

    
603
	/**
604
	 * Creates a problem event.
605
	 * Be aware of the right depths of the stack trace !
606
	 * @param location
607
	 * @param message
608
	 * @param severity
609
	 * @return
610
	 */
611
	private IoProblemEvent makeProblemEvent(Location location, String message, int severity, int stackDepth) {
612
		stackDepth++;
613
		StackTraceElement[] stackTrace = new Exception().getStackTrace();
614
		int lineNumber = stackTrace[stackDepth].getLineNumber();
615
		String methodName = stackTrace[stackDepth].getMethodName();
616
		String locationStr = makeLocationStr(location);
617
		String className = stackTrace[stackDepth].getClassName();
618
		Class<?> declaringClass;
619
		try {
620
			declaringClass = Class.forName(className);
621
		} catch (ClassNotFoundException e) {
622
			declaringClass = this.getClass();
623
		}
624
		IoProblemEvent event = IoProblemEvent.NewInstance(declaringClass, message,
625
				locationStr, lineNumber, severity, methodName);
626
		return event;
627
	}
628

    
629
	/**
630
	 * Creates a string from a location
631
	 * @param location
632
	 * @return
633
	 */
634
	protected String makeLocationStr(Location location) {
635
		String locationStr = location == null ? " - no location - " : "l." + location.getLineNumber() + "/c."+ location.getColumnNumber();
636
		return locationStr;
637
	}
638

    
639

    
640
	/**
641
	 * Fires an unexpected element event if the unhandled elements stack is empty.
642
	 * Otherwise adds the element to the stack.
643
	 * @param event
644
	 */
645
	protected void handleUnexpectedStartElement(XMLEvent event) {
646
		handleUnexpectedStartElement(event, 1);
647
	}
648

    
649
	/**
650
	 * Fires an unexpected element event if the unhandled elements stack is empty.
651
	 * Otherwise adds the element to the stack.
652
	 * @param event
653
	 */
654
	protected void handleUnexpectedStartElement(XMLEvent event, int stackDepth) {
655
		QName qName = event.asStartElement().getName();
656
		if (! unhandledElements.empty()){
657
			unhandledElements.push(qName);
658
		}else{
659
			fireUnexpectedStartElement(event.getLocation(), event.asStartElement(), stackDepth + 1);
660
		}
661
	}
662

    
663

    
664
	protected void handleUnexpectedEndElement(EndElement event) {
665
		handleUnexpectedEndElement(event, 1);
666
	}
667

    
668
	/**
669
	 * Fires an unexpected element event if the event is not the last on the stack.
670
	 * Otherwise removes last stack element.
671
	 * @param event
672
	 */
673
	protected void handleUnexpectedEndElement(EndElement event, int stackDepth) {
674
		QName qName = event.asEndElement().getName();
675
		if (!unhandledElements.isEmpty() && unhandledElements.peek().equals(qName)){
676
			unhandledElements.pop();
677
		}else{
678
			fireUnexpectedEndElement(event.getLocation(), event.asEndElement(), stackDepth + 1);
679
		}
680
	}
681

    
682
	/**
683
	 *
684
	 * @param endElement
685
	 */
686
	protected void popUnimplemented(EndElement endElement) {
687
		QName qName = endElement.asEndElement().getName();
688
		if (unhandledElements.peek().equals(qName)){
689
			unhandledElements.pop();
690
		}else{
691
			String message = "End element is not last on stack: %s";
692
			message = String.format(message, qName.getLocalPart());
693
			IIoEvent event = makeProblemEvent(endElement.getLocation(), message, 16, 1);
694
			fire(event);
695
		}
696

    
697
	}
698

    
699

    
700
	/**
701
	 * Fires an unexpected element event if the unhandled element stack is empty.
702
	 * @param event
703
	 */
704
	protected void handleUnexpectedElement(XMLEvent event) {
705
		if (event.isStartElement()){
706
			handleUnexpectedStartElement(event, 2);
707
		}else if (event.isEndElement()){
708
			handleUnexpectedEndElement(event.asEndElement(), 2);
709
		}else if (event.getEventType() == XMLStreamConstants.COMMENT){
710
			//do nothing
711
		}else if (! unhandledElements.empty()){
712
			//do nothing
713
		}else{
714
			fireUnexpectedEvent(event, 1);
715
		}
716
	}
717

    
718
	/**
719
	 * Fires an not yet implemented event and adds the element name to the unhandled elements stack.
720
	 * @param event
721
	 */
722
	protected void handleNotYetImplementedCharacters(XMLEvent event) {
723
		Characters chars = event.asCharacters();
724
		fireNotYetImplementedCharacters(event.getLocation(), chars, 1);
725
	}
726

    
727
	/**
728
	 * Fires an not yet implemented event and adds the element name to the unhandled elements stack.
729
	 * @param event
730
	 */
731
	protected void handleNotYetImplementedElement(XMLEvent event) {
732
		QName qName = event.asStartElement().getName();
733
		boolean isTopLevel = unhandledElements.isEmpty();
734
		unhandledElements.push(qName);
735
		if (isTopLevel){
736
			fireNotYetImplementedElement(event.getLocation(), qName, 1);
737
		}
738
	}
739

    
740
	/**
741
	 * Fires an not yet implemented event and adds the element name to the unhandled elements stack.
742
	 * @param event
743
	 */
744
	protected void handleIgnoreElement(XMLEvent event) {
745
		QName qName = event.asStartElement().getName();
746
		unhandledElements.push(qName);
747
	}
748

    
749
	protected void handleAmbigousManually(MarkupImportState state,
750
			XMLEventReader reader, StartElement startElement) {
751
		QName qName = startElement.getName();
752
		unhandledElements.push(qName);
753
		fireWarningEvent(
754
				"Handle manually: " + qName.getLocalPart() + " is ambigous and should therefore be handled manually",
755
				makeLocationStr(startElement.getLocation()), 2, 2);
756
	}
757

    
758
	/**
759
	 * Checks if a mandatory text is not empty or null.
760
	 * Returns true if text is given.
761
	 * Fires an mandatory element is missing event otherwise and returns <code>null</code>.
762
	 * @param text
763
	 * @param parentEvent
764
	 * @return
765
	 */
766
	protected boolean checkMandatoryText(String text, XMLEvent parentEvent) {
767
		if (! StringUtils.isNotBlank(text)){
768
			fireMandatoryElementIsMissing(parentEvent, "CData", 4, 1);
769
			return false;
770
		}
771
		return true;
772
	}
773

    
774
	/**
775
	 * Fires an mandatory element is missing event if exists is <code>false</code>.
776
	 * @param hasMandatory
777
	 * @param parentEvent
778
	 * @param string
779
	 */
780
	protected void checkMandatoryElement(boolean exists, StartElement parentEvent, String attrName) {
781
		if (! exists){
782
			fireMandatoryElementIsMissing(parentEvent, attrName, 5, 1);
783
		}
784
	}
785

    
786

    
787
	/**
788
	 * Fires an element is missing event.
789
	 * @param xmlEvent
790
	 * @param string
791
	 * @param severity
792
	 * @param stackDepth
793
	 * @throws IllegalStateException if xmlEvent is not a StartElement and not an Attribute
794
	 */
795
	private void fireMandatoryElementIsMissing(XMLEvent xmlEvent, String missingEventName, int severity, int stackDepth) throws IllegalStateException{
796
		Location location = xmlEvent.getLocation();
797
		String typeName;
798
		QName qName;
799
		if (xmlEvent.isAttribute()){
800
			Attribute attribute = ((Attribute)xmlEvent);
801
			typeName = "attribute";
802
			qName = attribute.getName();
803
		}else if (xmlEvent.isStartElement()){
804
			typeName = "element";
805
			qName = xmlEvent.asStartElement().getName();
806
		}else{
807
			throw new IllegalStateException("mandatory element only allowed for attributes and start tags in " + makeLocationStr(location));
808
		}
809
		String message = "Mandatory %s '%s' is missing in %s";
810
		message = String.format(message, typeName , missingEventName, qName.getLocalPart());
811
		IIoEvent event = makeProblemEvent(location, message, severity, stackDepth +1);
812
		fire(event);
813
	}
814

    
815

    
816

    
817

    
818
	/**
819
	 * Returns <code>true</code> if the "next" event is the ending tag for the "parent" event.
820
	 * @param next end element to test, must not be null
821
	 * @param parentEvent start element to test
822
	 * @return true if the "next" event is the ending tag for the "parent" event.
823
	 * @throws XMLStreamException
824
	 */
825
	protected boolean isMyEndingElement(XMLEvent next, XMLEvent parentEvent) throws XMLStreamException {
826
		if (! parentEvent.isStartElement()){
827
			String message = "Parent event should be start tag";
828
			fireWarningEvent(message, makeLocationStr(next.getLocation()), 6);
829
			return false;
830
		}
831
		return isEndingElement(next, parentEvent.asStartElement().getName().getLocalPart());
832
	}
833

    
834
	/**
835
	 * Trims the text and removes turns all whitespaces into single empty space.
836
	 * @param text
837
	 * @return
838
	 */
839
	protected String normalize(String text) {
840
		text = StringUtils.trimToEmpty(text);
841
		text = text.replaceAll("\\s+", " ");
842
		return text;
843
	}
844

    
845

    
846

    
847
	/**
848
	 * Removes whitespaces at beginning and end and makes the first letter
849
	 * a capital letter and all other letters small letters.
850
	 * @param value
851
	 * @return
852
	 */
853
	protected String toFirstCapital(String value) {
854
		if (StringUtils.isBlank(value)){
855
			return value;
856
		}else{
857
			String result = "";
858
			value = value.trim();
859
			result += value.trim().substring(0,1).toUpperCase();
860
			if (value.length()>1){
861
				result += value.substring(1).toLowerCase();
862
			}
863
			return result;
864
		}
865
	}
866

    
867
	/**
868
	 * Currently not used.
869
	 * @param str
870
	 * @param allowedNumberOfCharacters
871
	 * @param onlyFirstCapital
872
	 * @return
873
	 */
874
	protected boolean isAbbreviation(String str, int allowedNumberOfCharacters, boolean onlyFirstCapital){
875
		if (isBlank(str)){
876
			return false;
877
		}
878
		str = str.trim();
879
		if (! str.endsWith(".")){
880
			return false;
881
		}
882
		str = str.substring(0, str.length() -1);
883
		if (str.length() > allowedNumberOfCharacters){
884
			return false;
885
		}
886
		final String re = "^\\p{javaUpperCase}\\p{javaLowerCase}*$";
887
		if (str.matches(re)){
888
			return true;
889
		}else{
890
			return false;
891
		}
892
	}
893

    
894
	/**
895
	 * Checks if <code>abbrev</code> is the short form for the genus name (strGenusName).
896
	 * Usually this is the case if <code>abbrev</code> is the first letter (optional with ".")
897
	 * of strGenusName. But in older floras it may also be the first 2 or 3 letters (optional with dot).
898
	 * However, we allow only a maximum of 2 letters to be anambigous. In cases with 3 letters better
899
	 * change the original markup data.
900
	 * @param single
901
	 * @param strGenusName
902
	 * @return
903
	 */
904
	protected boolean isGenusAbbrev(String abbrev, String strGenusName) {
905
		if (! abbrev.matches("[A-Z][a-z]?\\.?")) {
906
			return false;
907
		}else if (abbrev.length() == 0 || strGenusName == null || strGenusName.length() == 0){
908
			return false;
909
		}else{
910
			abbrev = abbrev.replace(".", "");
911
			return strGenusName.startsWith(abbrev);
912
//			boolean result = true;
913
//			for (int i = 0 ; i < abbrev.length(); i++){
914
//				result &= ( abbrev.charAt(i) == strGenusName.charAt(i));
915
//			}
916
//			return result;
917
		}
918
	}
919

    
920

    
921
	/**
922
	 * Checks if all words in the given string start with a capital letter but do not have any further capital letter.
923
	 * @param word the string to be checekd. Usually should be a single word.
924
	 * @return true if the above is the case, false otherwise
925
	 */
926
	protected boolean isFirstCapitalWord(String word) {
927
		if (WordUtils.capitalizeFully(word).equals(word)){
928
			return true;
929
		}else if (WordUtils.capitalizeFully(word,new char[]{'-'}).equals(word)){
930
			//for words like Le-Testui (which is a species epithet)
931
			return true;
932
		}else{
933
			return false;
934
		}
935
	}
936

    
937

    
938
	/**
939
	 * Read next event. Ignore whitespace events.
940
	 * @param reader
941
	 * @return
942
	 * @throws XMLStreamException
943
	 */
944
	protected XMLEvent readNoWhitespace(XMLEventReader reader) throws XMLStreamException {
945
		XMLEvent event = reader.nextEvent();
946
		while (!unhandledElements.isEmpty()){
947
			if (event.isStartElement()){
948
				handleNotYetImplementedElement(event);
949
			}else if (event.isEndElement()){
950
				popUnimplemented(event.asEndElement());
951
			}
952
			event = reader.nextEvent();
953
		}
954
		while (event.isCharacters() && event.asCharacters().isWhiteSpace()){
955
			event = reader.nextEvent();
956
		}
957
		return event;
958
	}
959

    
960
	/**
961
	 * Returns the REQUIRED "class" attribute for a given event and checks that it is the only attribute.
962
	 * @param parentEvent
963
	 * @return
964
	 */
965
	protected String getClassOnlyAttribute(XMLEvent parentEvent) {
966
		return getClassOnlyAttribute(parentEvent, true);
967
	}
968

    
969

    
970
	/**
971
	 * Returns the "class" attribute for a given event and checks that it is the only attribute.
972
	 * @param parentEvent
973
	 * @return
974
	 */
975
	protected String getClassOnlyAttribute(XMLEvent parentEvent, boolean required) {
976
		return getOnlyAttribute(parentEvent, CLASS, required);
977
	}
978

    
979
	/**
980
	 * Returns the value for the only attribute for a given event and checks that it is the only attribute.
981
	 * @param parentEvent
982
	 * @return
983
	 */
984
	protected String getOnlyAttribute(XMLEvent parentEvent, String attrName, boolean required) {
985
		Map<String, Attribute> attributes = getAttributes(parentEvent);
986
		String classValue =getAndRemoveAttributeValue(parentEvent, attributes, attrName, required, 1);
987
		checkNoAttributes(attributes, parentEvent);
988
		return classValue;
989
	}
990

    
991

    
992
	protected void fireWarningEvent(String message, String locationStr, Integer severity, Integer depth) {
993
		docImport.fireWarningEvent(message, locationStr, severity, depth);
994
	}
995

    
996
	protected void fireWarningEvent(String message, XMLEvent event, Integer severity) {
997
		docImport.fireWarningEvent(message, makeLocationStr(event.getLocation()), severity, 1);
998
	}
999

    
1000
	protected void fireSchemaConflictEventExpectedStartTag(String elName, XMLEventReader reader) throws XMLStreamException {
1001
		docImport.fireSchemaConflictEventExpectedStartTag(elName, reader);
1002
	}
1003

    
1004

    
1005
	protected void fireWarningEvent(String message, String locationStr, int severity) {
1006
		docImport.fireWarningEvent(message, locationStr, severity, 1);
1007
	}
1008

    
1009
	protected void fire(IIoEvent event) {
1010
		docImport.fire(event);
1011
	}
1012

    
1013
	protected boolean isNotBlank(String str){
1014
		return StringUtils.isNotBlank(str);
1015
	}
1016

    
1017
	protected boolean isBlank(String str){
1018
		return StringUtils.isBlank(str);
1019
	}
1020

    
1021
	protected TaxonDescription getTaxonDescription(Taxon taxon, Reference ref, boolean isImageGallery, boolean createNewIfNotExists) {
1022
		return docImport.getTaxonDescription(taxon, ref, isImageGallery, createNewIfNotExists);
1023
	}
1024

    
1025
    protected TaxonDescription getDefaultTaxonDescription(Taxon taxon, boolean isImageGallery, boolean createNewIfNotExists, Reference source) {
1026
        return docImport.getDefaultTaxonDescription(taxon, isImageGallery, createNewIfNotExists, source);
1027
    }
1028

    
1029
    /**
1030
     * Returns the taxon description with marked as <code>true</code> with the given marker type.
1031
     * If createNewIfNotExists a new description is created if it does not yet exist.
1032
     * For the new description the source and the title are set if not <code>null</code>.
1033
     * @param taxon
1034
     * @param markerType
1035
     * @param isImageGallery
1036
     * @param createNewIfNotExists
1037
     * @param source
1038
     * @param title
1039
     * @return the existing or new taxon description
1040
     */
1041
    protected TaxonDescription getMarkedTaxonDescription(Taxon taxon, MarkerType markerType, boolean isImageGallery, boolean createNewIfNotExists, Reference source, String title) {
1042
        return docImport.getMarkedTaxonDescription(taxon, markerType, isImageGallery, createNewIfNotExists, source, title);
1043
    }
1044

    
1045

    
1046
	/**
1047
	 * Returns the default language defined in the state. If no default language is defined in the state,
1048
	 * the CDM default language is returned.
1049
	 * @param state
1050
	 * @return
1051
	 */
1052
	protected Language getDefaultLanguage(MarkupImportState state) {
1053
		Language result = state.getDefaultLanguage();
1054
		if (result == null){
1055
			result = Language.DEFAULT();
1056
		}
1057
		return result;
1058
	}
1059

    
1060

    
1061
//*********************** FROM XML IMPORT BASE ****************************************
1062
	protected boolean isEndingElement(XMLEvent event, String elName) throws XMLStreamException {
1063
		return docImport.isEndingElement(event, elName);
1064
	}
1065

    
1066
	protected boolean isStartingElement(XMLEvent event, String elName) throws XMLStreamException {
1067
		return docImport.isStartingElement(event, elName);
1068
	}
1069

    
1070

    
1071
	protected void fillMissingEpithetsForTaxa(Taxon parentTaxon, Taxon childTaxon) {
1072
		docImport.fillMissingEpithetsForTaxa(parentTaxon, childTaxon);
1073
	}
1074

    
1075
	protected Feature getFeature(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<Feature> voc){
1076
		return docImport.getFeature(state, uuid, label, text, labelAbbrev, voc);
1077
	}
1078

    
1079
    protected PresenceAbsenceTerm getPresenceAbsenceTerm(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, boolean isAbsenceTerm, TermVocabulary<PresenceAbsenceTerm> voc){
1080
        return docImport.getPresenceTerm(state, uuid, label, text, labelAbbrev, isAbsenceTerm, voc);
1081
    }
1082

    
1083
	protected ExtensionType getExtensionType(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev){
1084
		return docImport.getExtensionType(state, uuid, label, text, labelAbbrev);
1085
	}
1086

    
1087
	protected DefinedTerm getIdentifierType(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<DefinedTerm> voc){
1088
		return docImport.getIdentifierType(state, uuid, label, text, labelAbbrev, voc);
1089
	}
1090

    
1091
	protected AnnotationType getAnnotationType(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<AnnotationType> voc){
1092
		return docImport.getAnnotationType(state, uuid, label, text, labelAbbrev, voc);
1093
	}
1094

    
1095
	protected MarkerType getMarkerType(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<MarkerType> voc){
1096
		return docImport.getMarkerType(state, uuid, label, text, labelAbbrev, voc);
1097
	}
1098

    
1099
	protected NamedAreaLevel getNamedAreaLevel(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, OrderedTermVocabulary<NamedAreaLevel> voc){
1100
		return docImport.getNamedAreaLevel(state, uuid, label, text, labelAbbrev, voc);
1101
	}
1102

    
1103
	protected NamedArea getNamedArea(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, NamedAreaType areaType, NamedAreaLevel level, TermVocabulary voc, TermMatchMode matchMode){
1104
		return docImport.getNamedArea(state, uuid, label, text, labelAbbrev, areaType, level, voc, matchMode);
1105
	}
1106

    
1107
	protected Language getLanguage(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<?> voc){
1108
		return docImport.getLanguage(state, uuid, label, text, labelAbbrev, voc);
1109
	}
1110

    
1111
// *************************************** Concrete methods **********************************************/
1112

    
1113

    
1114
	/**
1115
	 * @param state
1116
	 * @param classValue
1117
	 * @param byAbbrev
1118
	 * @return
1119
	 */
1120
	protected Rank makeRank(MarkupImportState state, String value, boolean byAbbrev) {
1121
		Rank rank = null;
1122
		if (StringUtils.isBlank(value)) {
1123
			return null;
1124
		}
1125
		try {
1126
			boolean useUnknown = true;
1127
			NomenclaturalCode nc = makeNomenclaturalCode(state);
1128
			if (value.equals(GENUS_ABBREVIATION)){
1129
				rank = Rank.GENUS();
1130
			}else if (byAbbrev) {
1131
				rank = Rank.getRankByIdInVoc(value.toLowerCase(), nc, useUnknown);
1132
				if (value.equalsIgnoreCase("forma")){
1133
				    return Rank.FORM();
1134
				}else if (value.toLowerCase().matches("(sub)?(section|genus|series|tribe)")){
1135
				    return Rank.getRankByEnglishName(value, nc, useUnknown);
1136
				}else if (value.equals("§")){
1137
                    return Rank.SECTION_BOTANY();  //Special case in Flora Malesiana
1138
				}
1139
			} else {
1140
				rank = Rank.getRankByEnglishName(value, nc, useUnknown);
1141
			}
1142
			if (rank.equals(Rank.UNKNOWN_RANK())) {
1143
				rank = null;
1144
			}
1145
			if (rank == null && "sous-genre".equalsIgnoreCase(value)){
1146
				rank = Rank.SUBGENUS();
1147
			}
1148
		} catch (UnknownCdmTypeException e) {
1149
			// doNothing
1150
		}
1151
		return rank;
1152
	}
1153

    
1154
	NonViralNameParserImpl parser = NonViralNameParserImpl.NewInstance();
1155
    protected TeamOrPersonBase<?> createAuthor(MarkupImportState state, String authorTitle) {
1156
		TeamOrPersonBase<?> result = parser.author(authorTitle);
1157
		return state.getDeduplicationHelper().getExistingAuthor(result, true);
1158
	}
1159

    
1160
	protected String getAndRemoveMapKey(Map<String, String> map, String key) {
1161
		String result = map.get(key);
1162
		map.remove(key);
1163
		if (result != null) {
1164
			result = normalize(result);
1165
		}
1166
		return StringUtils.stripToNull(result);
1167
	}
1168

    
1169

    
1170
	/**
1171
	 * Creates a {@link INonViralName} object depending on the defined {@link NomenclaturalCode}
1172
	 * and the given parameters.
1173
	 * @param state
1174
	 * @param rank
1175
	 * @return
1176
	 */
1177
	protected INonViralName createNameByCode(MarkupImportState state, Rank rank) {
1178
		NomenclaturalCode nc = makeNomenclaturalCode(state);
1179
		INonViralName name = nc.getNewTaxonNameInstance(rank);
1180
		return name;
1181
	}
1182

    
1183
	protected void handleFullName(MarkupImportState state, XMLEventReader reader,
1184
			INonViralName name, XMLEvent event) throws XMLStreamException {
1185
		String fullNameStr;
1186
		Map<String, Attribute> attrs = getAttributes(event);
1187
		String rankStr = getAndRemoveRequiredAttributeValue(event, attrs, "rank");
1188
		String hybridClass = getAndRemoveAttributeValue(attrs, "hybridClass");
1189

    
1190
		Rank rank = makeRank(state, rankStr, false);
1191
		name.setRank(rank);
1192
		if (rank == null) {
1193
			String message = "Rank was computed as null. This must not be.";
1194
			fireWarningEvent(message, event, 6);
1195
			name.setRank(Rank.UNKNOWN_RANK());
1196
		}
1197
		if (!attrs.isEmpty()) {
1198
			handleUnexpectedAttributes(event.getLocation(), attrs);
1199
		}
1200
		fullNameStr = getCData(state, reader, event, false);
1201
		NonViralNameParserImpl.NewInstance().parseFullName(name, fullNameStr, rank, false);
1202
		if (hybridClass != null ){
1203
		    if ("hybrid formula".equals(hybridClass)){
1204
		        if (!name.isHybridFormula()){
1205
		            fireWarningEvent("Hybrid formula is not set though requested: " + fullNameStr, event, 4);
1206
		        }
1207
		    }else if ("hybrid".equals(hybridClass)){
1208
                if (!name.isHybridName()){
1209
                    fireWarningEvent("Hybrid name is recognized: " + fullNameStr, event, 4);
1210
                }
1211
            }else{
1212
                handleNotYetImplementedAttributeValue(event, "hybridClass", hybridClass);
1213
            }
1214
		}
1215
	}
1216

    
1217

    
1218
	/**
1219
	 * Returns the {@link NomenclaturalCode} for this import. Default is {@link NomenclaturalCode#ICBN} if
1220
	 * no code is defined.
1221
	 * @param state
1222
	 * @return
1223
	 */
1224
	protected NomenclaturalCode makeNomenclaturalCode(MarkupImportState state) {
1225
		NomenclaturalCode nc = state.getConfig().getNomenclaturalCode();
1226
		if (nc == null) {
1227
			nc = NomenclaturalCode.ICNAFP; // default;
1228
		}
1229
		return nc;
1230
	}
1231

    
1232

    
1233
	/**
1234
	 * @param state
1235
	 * @param levelString
1236
	 * @param next
1237
	 * @return
1238
	 */
1239
	protected NamedAreaLevel makeNamedAreaLevel(MarkupImportState state, String levelString, XMLEvent next) {
1240
		NamedAreaLevel level;
1241
		try {
1242
			level = state.getTransformer().getNamedAreaLevelByKey(levelString);
1243
			if (level == null) {
1244
				UUID levelUuid = state.getTransformer().getNamedAreaLevelUuid(levelString);
1245
				if (levelUuid == null) {
1246
					String message = "Unknown distribution locality class (named area level): %s. Create new level instead.";
1247
					message = String.format(message, levelString);
1248
					fireWarningEvent(message, next, 6);
1249
				}
1250
				level = getNamedAreaLevel(state, levelUuid, levelString, levelString, levelString, null);
1251
			}
1252
		} catch (UndefinedTransformerMethodException e) {
1253
			throw new RuntimeException(e);
1254
		}
1255
		return level;
1256
	}
1257

    
1258

    
1259
	/**
1260
	 * @param state
1261
	 * @param areaName
1262
	 * @param level
1263
	 * @return
1264
	 */
1265
	protected NamedArea makeArea(MarkupImportState state, String areaName, NamedAreaLevel level) {
1266

    
1267
		//TODO FM vocabulary
1268
		TermVocabulary<NamedArea> voc = null;
1269
		NamedAreaType areaType = null;
1270

    
1271
		NamedArea area = null;
1272
		try {
1273
			area = state.getTransformer().getNamedAreaByKey(areaName);
1274
		} catch (UndefinedTransformerMethodException e) {
1275
			throw new RuntimeException(e);
1276
		}
1277
		if (area == null){
1278
			boolean isNewInState = false;
1279
			UUID uuid = state.getAreaUuid(areaName);
1280
			if (uuid == null){
1281
				isNewInState = true;
1282
				try {
1283
					uuid = state.getTransformer().getNamedAreaUuid(areaName);
1284
					if (uuid == null){
1285
					    uuid = UUID.randomUUID();
1286
					    state.putAreaUuid(areaName, uuid);
1287
					}
1288
				} catch (UndefinedTransformerMethodException e) {
1289
					throw new RuntimeException(e);
1290
				}
1291
			}
1292

    
1293
			CdmImportBase.TermMatchMode matchMode = CdmImportBase.TermMatchMode.UUID_LABEL;
1294
			area = getNamedArea(state, uuid, areaName, areaName, areaName, areaType, level, voc, matchMode);
1295
			if (isNewInState){
1296
				state.putAreaUuid(areaName, area.getUuid());
1297

    
1298
				//TODO just for testing -> make generic and move to better place
1299
				String geoServiceLayer="vmap0_as_bnd_political_boundary_a";
1300
				String layerFieldName ="nam";
1301

    
1302
				if ("Bangka".equals(areaName)){
1303
					String areaValue = "PULAU BANGKA#SUMATERA SELATAN";
1304
					GeoServiceArea geoServiceArea = new GeoServiceArea();
1305
					geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);
1306
					this.editGeoService.setMapping(area, geoServiceArea);
1307
//					save(area, state);
1308
				}
1309
				if ("Luzon".equals(areaName)){
1310
					GeoServiceArea geoServiceArea = new GeoServiceArea();
1311

    
1312
					List<String> list = Arrays.asList("HERMANA MAYOR ISLAND#CENTRAL LUZON",
1313
							"HERMANA MENOR ISLAND#CENTRAL LUZON",
1314
							"CENTRAL LUZON");
1315
					for (String areaValue : list){
1316
						geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);
1317
					}
1318

    
1319
					this.editGeoService.setMapping(area, geoServiceArea);
1320
//					save(area, state);
1321
				}
1322
				if ("Mindanao".equals(areaName)){
1323
					GeoServiceArea geoServiceArea = new GeoServiceArea();
1324

    
1325
					List<String> list = Arrays.asList("NORTHERN MINDANAO",
1326
							"SOUTHERN MINDANAO",
1327
							"WESTERN MINDANAO");
1328
					//TODO to be continued
1329
					for (String areaValue : list){
1330
						geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);
1331
					}
1332

    
1333
					this.editGeoService.setMapping(area, geoServiceArea);
1334
//					save(area, state);
1335
				}
1336
				if ("Palawan".equals(areaName)){
1337
					GeoServiceArea geoServiceArea = new GeoServiceArea();
1338

    
1339
					List<String> list = Arrays.asList("PALAWAN#SOUTHERN TAGALOG");
1340
					for (String areaValue : list){
1341
						geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);
1342
					}
1343

    
1344
					this.editGeoService.setMapping(area, geoServiceArea);
1345
//					save(area, state);
1346
				}
1347

    
1348
			}
1349
		}
1350
		return area;
1351
	}
1352

    
1353

    
1354

    
1355
	/**
1356
	 * Reads character data. Any element other than character data or the ending
1357
	 * tag will fire an unexpected element event.
1358
     *
1359
	 * @see #getCData(MarkupImportState, XMLEventReader, XMLEvent, boolean)
1360
	 * @param state
1361
	 * @param reader
1362
	 * @param next
1363
	 * @return
1364
	 * @throws XMLStreamException
1365
	 */
1366
	protected String getCData(MarkupImportState state, XMLEventReader reader, XMLEvent next) throws XMLStreamException {
1367
		return getCData(state, reader, next, true);
1368
	}
1369

    
1370
	/**
1371
	 * Reads character data. Any element other than character data or the ending
1372
	 * tag will fire an unexpected element event.
1373
	 *
1374
	 * @param state
1375
	 * @param reader
1376
	 * @param next
1377
	 * @param inlineMarkup map for inline markup, this is used for e.g. the locality markup within a subheading
1378
	 * The map will be filled by the markup element name as key. The value may be a String, a CdmBase or any other object.
1379
	 * If null any markup text will be neglected but a warning will be fired if they exist.
1380
	 * @param removeInlineMarkupText if true the markedup text will be removed from the returned String
1381
	 * @param checkAttributes
1382
	 * @return
1383
	 * @throws XMLStreamException
1384
	 */
1385
	protected String getCData(MarkupImportState state, XMLEventReader reader, XMLEvent parent, /*Map<String, Object> inlineMarkup, *boolean removeInlineMarkupText,*/ boolean checkAttributes) throws XMLStreamException {
1386
		if (checkAttributes){
1387
			checkNoAttributes(parent);
1388
		}
1389

    
1390
		String text = "";
1391
		while (reader.hasNext()) {
1392
			XMLEvent next = readNoWhitespace(reader);
1393
			if (isMyEndingElement(next, parent)) {
1394
				return text;
1395
			} else if (next.isCharacters()) {
1396
				text += next.asCharacters().getData();
1397
			} else if (isStartingElement(next, FOOTNOTE_REF)){
1398
				handleNotYetImplementedElement(next);
1399
//			} else if (isStartingElement(next, LOCALITY)){
1400
//				handleCDataLocality(state, reader, parent);
1401
			} else {
1402
				handleUnexpectedElement(next);
1403
			}
1404
		}
1405
		throw new IllegalStateException("Event has no closing tag");
1406

    
1407
	}
1408

    
1409
//	private void handleCDataLocality(MarkupImportState state, XMLEventReader reader, XMLEvent parent) {
1410
//		checkAndRemoveAttributeValue(attributes, attrName, value)
1411
//
1412
//	}
1413

    
1414

    
1415

    
1416
	/**
1417
	 * For it returns a pure CData annotation string. This behaviour may change in future. More complex annotations
1418
	 * should be handled differently.
1419
	 */
1420
	protected String handleSimpleAnnotation(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
1421
		String annotation = getCData(state, reader, parentEvent);
1422
		return annotation;
1423
	}
1424

    
1425
	/**
1426
	 * True if text is single "." oder "," or ";" or ":"
1427
	 */
1428
	protected boolean isPunctuation(String text) {
1429
		return text == null ? false : text.trim().matches("^[\\.,;:]$");
1430
	}
1431

    
1432
	/**
1433
	 * Text indicating that type information is following but no information about the type of the type
1434
	 * @param text
1435
	 * @return
1436
	 */
1437
	protected boolean charIsSimpleType(String text) {
1438
		return text.matches("(?i)Type:");
1439
	}
1440

    
1441
	protected String getXmlTag(XMLEvent event) {
1442
		String result;
1443
		if (event.isStartElement()) {
1444
			result = "<" + event.asStartElement().getName().getLocalPart()
1445
					+ ">";
1446
		} else if (event.isEndElement()) {
1447
			result = "</" + event.asEndElement().getName().getLocalPart() + ">";
1448
		} else {
1449
			String message = "Only start or end elements are allowed as Html tags";
1450
			throw new IllegalStateException(message);
1451
		}
1452
		return result;
1453
	}
1454

    
1455
	protected WriterDataHolder handleWriter(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
1456
		String text = "";
1457
		checkNoAttributes(parentEvent);
1458
		WriterDataHolder dataHolder = new WriterDataHolder();
1459
		List<FootnoteDataHolder> footnotes = new ArrayList<>();
1460

    
1461
		// TODO handle attributes
1462
		while (reader.hasNext()) {
1463
			XMLEvent next = readNoWhitespace(reader);
1464
			if (isMyEndingElement(next, parentEvent)) {
1465
				text = CdmUtils.removeBrackets(text);
1466
				if (checkMandatoryText(text, parentEvent)) {
1467
					text = normalize(text);
1468
					dataHolder.writer = text;
1469
					dataHolder.footnotes = footnotes;
1470

    
1471
					// Extension
1472
					UUID uuidWriterExtension = MarkupTransformer.uuidWriterExtension;
1473
					ExtensionType writerExtensionType =
1474
							this.getExtensionType(state, uuidWriterExtension,"Writer", "writer", "writer");
1475
					Extension extension = Extension.NewInstance();
1476
					extension.setType(writerExtensionType);
1477
					extension.setValue(text);
1478
					dataHolder.extension = extension;
1479

    
1480
					// Annotation
1481
					UUID uuidWriterAnnotation = MarkupTransformer.uuidWriterAnnotation;
1482
					AnnotationType writerAnnotationType = this.getAnnotationType(state, uuidWriterAnnotation, "Writer", "writer", "writer", null);
1483
					Annotation annotation = Annotation.NewInstance(text, writerAnnotationType, getDefaultLanguage(state));
1484
					dataHolder.annotation = annotation;
1485

    
1486
					return dataHolder;
1487
				} else {
1488
					return null;
1489
				}
1490
			} else if (isStartingElement(next, FOOTNOTE_REF)) {
1491
				FootnoteDataHolder footNote = handleFootnoteRef(state, reader, next);
1492
				if (footNote.isRef()) {
1493
					footnotes.add(footNote);
1494
				} else {
1495
					logger.warn("Non ref footnotes not yet impelemnted");
1496
				}
1497
			} else if (next.isCharacters()) {
1498
				text += next.asCharacters().getData();
1499

    
1500
			} else {
1501
				handleUnexpectedElement(next);
1502
				state.setUnsuccessfull();
1503
			}
1504
		}
1505
		throw new IllegalStateException("<writer> has no end tag");
1506
	}
1507

    
1508

    
1509
	protected void registerFootnotes(MarkupImportState state, AnnotatableEntity entity, List<FootnoteDataHolder> footnotes) {
1510
		for (FootnoteDataHolder footNote : footnotes) {
1511
			registerFootnoteDemand(state, entity, footNote);
1512
		}
1513
	}
1514

    
1515

    
1516
	private void registerFootnoteDemand(MarkupImportState state, AnnotatableEntity entity, FootnoteDataHolder footnote) {
1517
		FootnoteDataHolder existingFootnote = state.getFootnote(footnote.ref);
1518
		if (existingFootnote != null) {
1519
			attachFootnote(state, entity, existingFootnote);
1520
		} else {
1521
			Set<AnnotatableEntity> demands = state.getFootnoteDemands(footnote.ref);
1522
			if (demands == null) {
1523
				demands = new HashSet<>();
1524
				state.putFootnoteDemands(footnote.ref, demands);
1525
			}
1526
			demands.add(entity);
1527
		}
1528
	}
1529

    
1530

    
1531
	protected void attachFootnote(MarkupImportState state, AnnotatableEntity entity, FootnoteDataHolder footnote) {
1532
		AnnotationType annotationType = this.getAnnotationType(state, MarkupTransformer.uuidFootnote, "Footnote", "An e-flora footnote", "fn", null);
1533
		Annotation annotation = Annotation.NewInstance(footnote.string, annotationType, getDefaultLanguage(state));
1534
		// TODO transient objects
1535
		entity.addAnnotation(annotation);
1536
		save(entity, state);
1537
	}
1538

    
1539

    
1540
	protected void attachFigure(MarkupImportState state, XMLEvent next, AnnotatableEntity entity, Media figure) {
1541
		// IdentifiableEntity<?> toSave;
1542
		if (entity.isInstanceOf(TextData.class)) {
1543
			TextData deb = CdmBase.deproxy(entity, TextData.class);
1544
			deb.addMedia(figure);
1545
			// toSave = ((TaxonDescription)deb.getInDescription()).getTaxon();
1546
		} else if (entity.isInstanceOf(SpecimenOrObservationBase.class)) {
1547
			String message = "figures for specimen should be handled as Textdata";
1548
			fireWarningEvent(message, next, 4);
1549
			// toSave = ime;
1550
		} else if (entity.isInstanceOf(IdentifiableMediaEntity.class)) {
1551
			IdentifiableMediaEntity<?> ime = CdmBase.deproxy(entity, IdentifiableMediaEntity.class);
1552
			ime.addMedia(figure);
1553
			// toSave = ime;
1554
		} else {
1555
			String message = "Unsupported entity to attach media: %s";
1556
			message = String.format(message, entity.getClass().getName());
1557
			// toSave = null;
1558
		}
1559
		save(entity, state);
1560
	}
1561

    
1562

    
1563
	protected void registerGivenFootnote(MarkupImportState state, FootnoteDataHolder footnote) {
1564
		state.registerFootnote(footnote);
1565
		Set<AnnotatableEntity> demands = state.getFootnoteDemands(footnote.id);
1566
		if (demands != null) {
1567
			for (AnnotatableEntity entity : demands) {
1568
				attachFootnote(state, entity, footnote);
1569
			}
1570
		}
1571
	}
1572

    
1573

    
1574
	protected FootnoteDataHolder handleFootnote(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent,
1575
			MarkupSpecimenImport specimenImport, MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {
1576
		FootnoteDataHolder result = new FootnoteDataHolder();
1577
		Map<String, Attribute> attributes = getAttributes(parentEvent);
1578
		result.id = getAndRemoveAttributeValue(attributes, ID);
1579
		// result.ref = getAndRemoveAttributeValue(attributes, REF);
1580
		checkNoAttributes(attributes, parentEvent);
1581

    
1582
		while (reader.hasNext()) {
1583
			XMLEvent next = readNoWhitespace(reader);
1584
			if (isStartingElement(next, FOOTNOTE_STRING)) {
1585
				String string = handleFootnoteString(state, reader, next, specimenImport, nomenclatureImport);
1586
				result.string = string;
1587
			} else if (isMyEndingElement(next, parentEvent)) {
1588
				return result;
1589
			} else {
1590
				fireUnexpectedEvent(next, 0);
1591
			}
1592
		}
1593
		return result;
1594
	}
1595

    
1596

    
1597
	protected Media handleFigure(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent,
1598
			MarkupSpecimenImport specimenImport, MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {
1599
		// FigureDataHolder result = new FigureDataHolder();
1600

    
1601
		Map<String, Attribute> attributes = getAttributes(parentEvent);
1602
		String id = getAndRemoveAttributeValue(attributes, ID);
1603
		String type = getAndRemoveAttributeValue(attributes, TYPE);
1604
		String urlAttr = getAndRemoveAttributeValue(attributes, URL);
1605
		checkNoAttributes(attributes, parentEvent);
1606

    
1607
		String urlString = null;
1608
		String legendString = null;
1609
		String titleString = null;
1610
		String numString = null;
1611
		String text = null;
1612
		if (isNotBlank(urlAttr)){
1613
			urlString = CdmUtils.Nz(state.getBaseMediaUrl()) + urlAttr;
1614
		}
1615
		while (reader.hasNext()) {
1616
			XMLEvent next = readNoWhitespace(reader);
1617
			if (isMyEndingElement(next, parentEvent)) {
1618
				if (isNotBlank(text)){
1619
				    if (isNeglectableFigureText(text)){
1620
				        fireWarningEvent("Text not yet handled for figures: " + text, next, 4);
1621
				    }
1622
				}
1623
				Media media = makeFigure(state, id, type, urlString, legendString, titleString, numString, next);
1624
				return media;
1625
			} else if (isStartingElement(next, FIGURE_LEGEND)) {
1626
				// TODO same as figure string ?
1627
				legendString = handleFootnoteString(state, reader, next, specimenImport, nomenclatureImport);
1628
			} else if (isStartingElement(next, FIGURE_TITLE)) {
1629
				titleString = getCData(state, reader, next);
1630
			} else if (isStartingElement(next, URL)) {
1631
				String localUrl = getCData(state, reader, next);
1632
				String url = CdmUtils.Nz(state.getBaseMediaUrl()) + localUrl;
1633
				if (isBlank(urlString)){
1634
					urlString = url;
1635
				}
1636
				if (! url.equals(urlString)){
1637
					String message = "URL attribute and URL element differ. Attribute: %s, Element: %s";
1638
					fireWarningEvent(String.format(message, urlString, url), next, 2);
1639
				}
1640
			} else if (isStartingElement(next, NUM)) {
1641
				numString = getCData(state, reader, next);
1642
			} else if (next.isCharacters()) {
1643
				text = CdmUtils.concat("", text, next.asCharacters().getData());
1644
			} else {
1645
				fireUnexpectedEvent(next, 0);
1646
			}
1647
		}
1648
		throw new IllegalStateException("<figure> has no end tag");
1649
	}
1650

    
1651

    
1652
	/**
1653
     * @param text2
1654
     * @return
1655
     */
1656
    private boolean isNeglectableFigureText(String text) {
1657
        if (text.matches("Fig\\.*")){
1658
            return true;
1659
        }else{
1660
            return false;
1661
        }
1662
    }
1663

    
1664

    
1665
    /**
1666
	 * @param state
1667
	 * @param id
1668
	 * @param type
1669
	 * @param urlString
1670
	 * @param legendString
1671
	 * @param titleString
1672
	 * @param numString
1673
	 * @param next
1674
	 */
1675
	private Media makeFigure(MarkupImportState state, String id, String type, String urlString,
1676
			String legendString, String titleString, String numString, XMLEvent next) {
1677
		Media media = null;
1678
//		boolean isFigure = false;  //no difference between figure and media since v3.3
1679
		try {
1680
			//TODO maybe everything is a figure as it is all taken from a book
1681
			if ("lineart".equals(type)) {
1682
//				isFigure = true;
1683
//				media = Figure.NewInstance(url.toURI(), null, null,	null);
1684
			} else if (type == null || "photo".equals(type)
1685
					|| "signature".equals(type)
1686
					|| "others".equals(type)) {
1687
				//TODO
1688
			} else {
1689
				String message = "Unknown figure type '%s'";
1690
				message = String.format(message, type);
1691
				fireWarningEvent(message, next, 2);
1692
			}
1693
			media = docImport.getImageMedia(urlString, docImport.getReadMediaData());
1694

    
1695
			if (media != null){
1696
				// title
1697
				if (StringUtils.isNotBlank(titleString)) {
1698
					media.putTitle(getDefaultLanguage(state), titleString);
1699
				}
1700
				// legend
1701
				if (StringUtils.isNotBlank(legendString)) {
1702
					media.putDescription(getDefaultLanguage(state), legendString);
1703
				}
1704
				if (StringUtils.isNotBlank(numString)) {
1705
					// TODO use concrete source (e.g. DAPHNIPHYLLACEAE in FM
1706
					// vol.13)
1707
					Reference citation = state.getConfig().getSourceReference();
1708
					media.addSource(OriginalSourceType.Import, numString, "num", citation, null);
1709
					// TODO name used in source if available
1710
				}
1711
				// TODO which citation
1712
				if (StringUtils.isNotBlank(id)) {
1713
					media.addSource(OriginalSourceType.Import, id, null, state.getConfig().getSourceReference(), null);
1714
				} else {
1715
					String message = "Figure id should never be empty or null";
1716
					fireWarningEvent(message, next, 6);
1717
				}
1718

    
1719
				// text
1720
				// do nothing
1721
				registerGivenFigure(state, next, id, media);
1722

    
1723
			}else{
1724
				String message = "No media found: ";
1725
				fireWarningEvent(message, next, 4);
1726
			}
1727
		} catch (MalformedURLException e) {
1728
			String message = "Media uri has incorrect syntax: %s";
1729
			message = String.format(message, urlString);
1730
			fireWarningEvent(message, next, 4);
1731
//		} catch (URISyntaxException e) {
1732
//			String message = "Media uri has incorrect syntax: %s";
1733
//			message = String.format(message, urlString);
1734
//			fireWarningEvent(message, next, 4);
1735
		}
1736

    
1737
		return media;
1738
	}
1739

    
1740

    
1741
	private void registerGivenFigure(MarkupImportState state, XMLEvent next, String id, Media figure) {
1742
		state.registerFigure(id, figure);
1743
		Set<AnnotatableEntity> demands = state.getFigureDemands(id);
1744
		if (demands != null) {
1745
			for (AnnotatableEntity entity : demands) {
1746
				attachFigure(state, next, entity, figure);
1747
			}
1748
		}
1749
		save(figure, state);
1750
	}
1751

    
1752

    
1753
	private FootnoteDataHolder handleFootnoteRef(MarkupImportState state,
1754
			XMLEventReader reader, XMLEvent parentEvent)
1755
			throws XMLStreamException {
1756
		FootnoteDataHolder result = new FootnoteDataHolder();
1757
		Map<String, Attribute> attributes = getAttributes(parentEvent);
1758
		result.ref = getAndRemoveAttributeValue(attributes, REF);
1759
		checkNoAttributes(attributes, parentEvent);
1760

    
1761
		// text is not handled, needed only for debugging purposes
1762
		String text = "";
1763
		while (reader.hasNext()) {
1764
			XMLEvent next = readNoWhitespace(reader);
1765
			// if (isStartingElement(next, FOOTNOTE_STRING)){
1766
			// String string = handleFootnoteString(state, reader, next);
1767
			// result.string = string;
1768
			// }else
1769
			if (isMyEndingElement(next, parentEvent)) {
1770
				if (StringUtils.isNotBlank(text)){
1771
					fireWarningEvent("text is not empty but not handled during import", parentEvent, 4);
1772
				}
1773
				return result;
1774
			} else if (next.isCharacters() && unhandledElements.isEmpty()) {
1775
				text += next.asCharacters().getData();
1776
			} else if (isStartingElement(next, NUM)) {
1777
				//ignore numbering of footnotes as they are numbered differently in the CDM
1778
				handleIgnoreElement(next);
1779
			} else {
1780
				handleUnexpectedElement(next);
1781
			}
1782
		}
1783
		return result;
1784
	}
1785

    
1786

    
1787

    
1788
	private String handleFootnoteString(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, MarkupSpecimenImport specimenImport, MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {
1789
		boolean isTextMode = true;
1790
		String text = "";
1791
		while (reader.hasNext()) {
1792
			XMLEvent next = readNoWhitespace(reader);
1793
			if (isMyEndingElement(next, parentEvent)) {
1794
				return text;
1795
			} else if (next.isEndElement()) {
1796
				if (isEndingElement(next, FULL_NAME)) {
1797
					popUnimplemented(next.asEndElement());
1798
				} else if (isEndingElement(next, BR)) {
1799
					isTextMode = true;
1800
				} else if (isHtml(next)) {
1801
					text += getXmlTag(next);
1802
				} else {
1803
					handleUnexpectedEndElement(next.asEndElement());
1804
				}
1805
			} else if (next.isStartElement()) {
1806
				if (isStartingElement(next, FULL_NAME)) {
1807
					handleNotYetImplementedElement(next);
1808
				} else if (isStartingElement(next, GATHERING)) {
1809
					text += specimenImport.handleInLineGathering(state, reader, next);
1810
				} else if (isStartingElement(next, REFERENCES)) {
1811
					text += " " + handleInLineReferences(state, reader, next, nomenclatureImport) + " ";
1812
				} else if (isStartingElement(next, BR)) {
1813
					text += "<br/>";
1814
					isTextMode = false;
1815
				} else if (isStartingElement(next, NOMENCLATURE)) {
1816
					handleNotYetImplementedElement(next);
1817
				} else if (isHtml(next)) {
1818
					text += getXmlTag(next);
1819
				} else {
1820
					handleUnexpectedStartElement(next.asStartElement());
1821
				}
1822
			} else if (next.isCharacters()) {
1823
				if (!isTextMode) {
1824
					String message = "footnoteString is not in text mode";
1825
					fireWarningEvent(message, next, 6);
1826
				} else {
1827
					text += next.asCharacters().getData().trim();
1828
					// getCData(state, reader, next); does not work as we have inner tags like <references>
1829
				}
1830
			} else {
1831
				handleUnexpectedEndElement(next.asEndElement());
1832
			}
1833
		}
1834
		throw new IllegalStateException("<footnoteString> has no closing tag");
1835

    
1836
	}
1837

    
1838
	private static final List<String> htmlList = Arrays.asList("sub", "sup",
1839
			"ol", "ul", "li", "i", "b", "table", "br","tr","td","th");
1840

    
1841
	protected boolean isHtml(XMLEvent event) {
1842
		if (event.isStartElement()) {
1843
			String tag = event.asStartElement().getName().getLocalPart();
1844
			return htmlList.contains(tag);
1845
		} else if (event.isEndElement()) {
1846
			String tag = event.asEndElement().getName().getLocalPart();
1847
			return htmlList.contains(tag);
1848
		} else {
1849
			return false;
1850
		}
1851

    
1852
	}
1853

    
1854

    
1855
	private String handleInLineReferences(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent,
1856
	        MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {
1857
		checkNoAttributes(parentEvent);
1858

    
1859
		boolean hasReference = false;
1860
		String text = "";
1861
		while (reader.hasNext()) {
1862
			XMLEvent next = readNoWhitespace(reader);
1863
			if (isMyEndingElement(next, parentEvent)) {
1864
				checkMandatoryElement(hasReference, parentEvent.asStartElement(), REFERENCE);
1865
				return text;
1866
			} else if (isStartingElement(next, REFERENCE)) {
1867
				text += handleInLineReference(state, reader, next, nomenclatureImport);
1868
				hasReference = true;
1869
			} else {
1870
				handleUnexpectedElement(next);
1871
			}
1872
		}
1873
		throw new IllegalStateException("<References> has no closing tag");
1874
	}
1875

    
1876
	private String handleInLineReference(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent, MarkupNomenclatureImport nomenclatureImport)throws XMLStreamException {
1877
		Reference reference = nomenclatureImport.handleReference(state, reader, parentEvent);
1878
		fireWarningEvent("Check correct usage of inline reference", parentEvent, 3);
1879
		IntextReference intext = IntextReference.NewInstance(reference, null, 0, 0);
1880
		save(reference, state);
1881
		return intext.toInlineString(reference.getTitleCache());
1882
	}
1883

    
1884
	protected class SubheadingResult{
1885
	    String text;
1886
	    StringReferences references;
1887
        List<IntextReference> inlineReferences;
1888
	}
1889

    
1890
	/**
1891
	 * Handle < string > .
1892
	 * @param state
1893
	 * @param reader
1894
	 * @param parentEvent
1895
	 * @param feature only needed for distributionLocalities
1896
	 * @return
1897
	 * @throws XMLStreamException
1898
	 */
1899
	protected Map<String, SubheadingResult> handleString(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, Feature feature)throws XMLStreamException {
1900
		// attributes
1901
		String classValue = getClassOnlyAttribute(parentEvent, false);
1902
		if (StringUtils.isNotBlank(classValue)) {
1903
			String message = "class attribute for <string> not yet implemented";
1904
			fireWarningEvent(message, parentEvent, 2);
1905
		}
1906
		boolean isHabitat = false;
1907

    
1908
		// subheadings
1909
		Map<String, SubheadingResult> subHeadingMap = new HashMap<>();
1910
		String currentSubheading = null;
1911

    
1912
		boolean isTextMode = true;
1913
		String text = "";
1914
		StringReferences currentReferences = null;
1915
		List<IntextReference> inlineReferences = new ArrayList<>();
1916
		boolean lastWasReference = false;
1917
		while (reader.hasNext()) {
1918
			XMLEvent next = readNoWhitespace(reader);
1919
			if (isMyEndingElement(next, parentEvent)) {
1920
				putCurrentSubheading(subHeadingMap, currentSubheading, text, currentReferences, inlineReferences);
1921
				return subHeadingMap;
1922
			}
1923
			//check if last event was reference
1924
			if (lastWasReference && !isStartingElement(next, BR) && !isEndingElement(next, BR)
1925
			        && !isStartingElement(next, SUB_HEADING)){
1926
			    for (LabeledReference labeledRef : currentReferences.content){
1927
			        if (labeledRef.ref != null){
1928
			            IntextReference intext = IntextReference.NewInstance(labeledRef.ref, null, 0, 0);
1929
			            inlineReferences.add(intext);
1930
			            text += intext.toInlineString(labeledRef.label);
1931
			        }else{
1932
			            text += labeledRef.label;
1933
			        }
1934
			    }
1935
			    lastWasReference = false;
1936
			}
1937
			if (isStartingElement(next, BR)) {
1938
				text += "<br/>";
1939
				isTextMode = false;
1940
			} else if (isEndingElement(next, BR)) {
1941
				isTextMode = true;
1942
			} else if (isHtml(next)) {
1943
				text += getXmlTag(next);
1944
			} else if (isStartingElement(next, SUB_HEADING)) {
1945
				text = putCurrentSubheading(subHeadingMap, currentSubheading, text, currentReferences, inlineReferences);
1946
				currentReferences = null;
1947
				inlineReferences = new ArrayList<>();
1948
				lastWasReference = false;
1949
				// TODO footnotes
1950
				currentSubheading = getCData(state, reader, next).trim();
1951
			} else if (isStartingElement(next, DISTRIBUTION_LOCALITY)) {
1952
				if (feature != null && !feature.equals(Feature.DISTRIBUTION())) {
1953
					String message = "Distribution locality only allowed for feature of type 'distribution'";
1954
					fireWarningEvent(message, next, 4);
1955
				}
1956
				text += handleDistributionLocality(state, reader, next);
1957
			} else if (next.isCharacters()) {
1958
				if (! isTextMode) {
1959
					String message = "String is not in text mode";
1960
					fireWarningEvent(message, next, 6);
1961
				} else {
1962
					text += next.asCharacters().getData();
1963
				}
1964
			} else if (isStartingElement(next, HEADING)) {
1965
				//TODO
1966
				handleNotYetImplementedElement(next);
1967
			} else if (isStartingElement(next, VERNACULAR_NAMES)) {
1968
				//TODO
1969
				handleNotYetImplementedElement(next);
1970
			} else if (isStartingElement(next, QUOTE)) {
1971
				//TODO
1972
				handleNotYetImplementedElement(next);
1973
			} else if (isStartingElement(next, DEDICATION)) {
1974
				//TODO
1975
				handleNotYetImplementedElement(next);
1976
			} else if (isStartingElement(next, TAXONTYPE)) {
1977
				//TODO
1978
				handleNotYetImplementedElement(next);
1979
			} else if (isStartingElement(next, FULL_NAME)) {
1980
				//TODO
1981
				handleNotYetImplementedElement(next);
1982
			}else if (isStartingElement(next, REFERENCES)) {
1983
				if (currentReferences != null){
1984
				    fireWarningEvent("References do already exist", next, 2);
1985
				}
1986
			    currentReferences = handleStringReferences(state, reader, next);
1987
			    lastWasReference = true;
1988
			}else if (isStartingElement(next, REFERENCE)) {
1989
                //TODO
1990
                handleNotYetImplementedElement(next);
1991
            } else if (isStartingElement(next, GATHERING)) {
1992
				//TODO
1993
				handleNotYetImplementedElement(next);
1994
			} else if (isStartingElement(next, ANNOTATION)) {
1995
				//TODO  //TODO test handleSimpleAnnotation
1996
				handleNotYetImplementedElement(next);
1997
			} else if (isStartingElement(next, HABITAT)) {
1998
			    text += featureImport.handleHabitat(state, reader, next);
1999
			    isHabitat = true;
2000
			} else if (isStartingElement(next, FIGURE_REF)) {
2001
				//TODO
2002
				handleNotYetImplementedElement(next);
2003
			} else if (isStartingElement(next, FIGURE)) {
2004
				//TODO
2005
				handleNotYetImplementedElement(next);
2006
			} else if (isStartingElement(next, FOOTNOTE_REF)) {
2007
				//TODO
2008
				handleNotYetImplementedElement(next);
2009
			} else if (isStartingElement(next, FOOTNOTE)) {
2010
				//TODO
2011
				handleNotYetImplementedElement(next);
2012
			} else if (isStartingElement(next, WRITER)) {
2013
				//TODO
2014
				handleNotYetImplementedElement(next);
2015
			} else if (isStartingElement(next, DATES)) {
2016
				//TODO
2017
				handleNotYetImplementedElement(next);
2018
			} else if (isStartingElement(next, TO_KEY)) {
2019
			    handleNotYetImplementedElement(next);
2020
			} else {
2021
				handleUnexpectedElement(next);
2022
			}
2023
		}
2024
		throw new IllegalStateException("<String> has no closing tag");
2025
	}
2026

    
2027

    
2028
	/**
2029
	 * container class more or less representing a list of labeled references
2030
	 */
2031
	protected class StringReferences{
2032
	    String subheading;
2033
	    List<LabeledReference> content = new ArrayList<>() ; //either String or LabeledReference
2034
	    @Override
2035
        public String toString(){
2036
	        String result = null;
2037
	        for (LabeledReference labRef : content){
2038
	            result = CdmUtils.concat("", labRef.label);
2039
	        }
2040
	        return result;
2041
	    }
2042
        public List<LabeledReference> getReferences() {
2043
            List<LabeledReference> result = new ArrayList<>();
2044
            for (LabeledReference labRef : content){
2045
                if (labRef.ref != null){
2046
                    result.add(labRef);
2047
                }
2048
            }
2049
            return result;
2050
        }
2051
	}
2052

    
2053
	protected class LabeledReference{
2054
	    public LabeledReference(Reference ref, String detail, String label) {
2055
            this.ref = ref; this.detail = detail; this.label = label;
2056
        }
2057
	    protected Reference ref;  //if null, this LabeledReference represents only a string in between references
2058
	    protected String detail; //micro reference
2059
	    protected String label;
2060
	}
2061

    
2062
    private StringReferences handleStringReferences(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
2063
        checkNoAttributes(parentEvent);
2064
        StringReferences result = new StringReferences();
2065
        while (reader.hasNext()) {
2066
            XMLEvent next = readNoWhitespace(reader);
2067
            if (isMyEndingElement(next, parentEvent)) {
2068
                return result;
2069
            } else if (isStartingElement(next, SUB_HEADING)) {
2070
                String subheading = getCData(state, reader, next);
2071
                if (!subheading.matches("(References?|Literature):?")){
2072
                    fireWarningEvent("Subheading for references not recognized: " + subheading, next, 4);
2073
                }
2074
                result.subheading = subheading;
2075
            } else if (isStartingElement(next, REFERENCE)) {
2076
                handleInlineReference(state, reader, next, result);
2077
            } else {
2078
                handleUnexpectedElement(next);
2079
            }
2080
        }
2081
        throw new IllegalStateException("<References> has no closing tag");
2082
    }
2083

    
2084
    private void handleInlineReference(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent,
2085
            StringReferences result) throws XMLStreamException {
2086
        checkNoAttributes(parentEvent);
2087
        boolean hasRefPart = false;
2088
        Map<String, String> refMap = new HashMap<>();
2089
        String label = "";
2090
        while (reader.hasNext()) {
2091
            XMLEvent next = readNoWhitespace(reader);
2092
            if (isMyEndingElement(next, parentEvent)) {
2093
                checkMandatoryElement(hasRefPart, parentEvent.asStartElement(), REF_PART);
2094
                String details = refMap.get(DETAILS);
2095
//              String label = makeLabel(state, refMap, next);
2096
                Reference ref = createReference(state, refMap, next);
2097
                ref = state.getDeduplicationHelper().getExistingReference(ref, false);
2098

    
2099
                String label2 = ref.getTitleCache(); //TODO preliminary for debugging and testing
2100
                result.content.add(new LabeledReference(ref, details, label));
2101
                return;
2102
            } else if (isStartingElement(next, REF_PART)) {
2103
                String classValue = handleRefPart(state, reader, next, refMap);
2104
                String text = refMap.get(classValue);
2105
                if (classValue.equals(YEAR)){
2106
                    text = "("+text+")";
2107
                }
2108
                hasRefPart = true;
2109
                label = CdmUtils.concat(" ", label, text);
2110
            } else {
2111
                handleUnexpectedElement(next);
2112
            }
2113
        }
2114
        throw new IllegalStateException("<References> has no closing tag");
2115

    
2116
    }
2117

    
2118

    
2119
//    this is more or less a duplicate Nomenclature import, maybe merge later
2120
    private Reference createReference(MarkupImportState state,
2121
            Map<String, String> refMap, XMLEvent parentEvent) {
2122

    
2123
        Reference reference;
2124

    
2125
        String type = getAndRemoveMapKey(refMap, PUBTYPE);
2126
        String authorStr = getAndRemoveMapKey(refMap, AUTHOR);
2127
        String titleStr = getAndRemoveMapKey(refMap, PUBTITLE);
2128
        String titleCache = getAndRemoveMapKey(refMap, PUBFULLNAME);
2129
        String volume = getAndRemoveMapKey(refMap, VOLUME);
2130
        String edition = getAndRemoveMapKey(refMap, EDITION);
2131
        String editors = getAndRemoveMapKey(refMap, EDITORS);
2132
        String year = getAndRemoveMapKey(refMap, YEAR);
2133
        String pubName = getAndRemoveMapKey(refMap, PUBNAME);
2134
        String pages = getAndRemoveMapKey(refMap, PAGES);
2135
        String publication = getAndRemoveMapKey(refMap, PUBLOCATION);
2136
        String publisher = getAndRemoveMapKey(refMap, PUBLISHER);
2137
        String appendix = getAndRemoveMapKey(refMap, APPENDIX);
2138
        String issue = getAndRemoveMapKey(refMap, ISSUE);
2139

    
2140
        reference = handleNonCitationSpecific(state, type, authorStr, titleStr,
2141
                    titleCache, volume, issue, edition, editors, pubName, appendix, pages, parentEvent);
2142

    
2143
        //year
2144
        VerbatimTimePeriod timeperiod = TimePeriodParser.parseStringVerbatim(year);
2145
        if (reference.getType().equals(ReferenceType.BookSection)){
2146
            reference.getInBook().setDatePublished(timeperiod);
2147
        }
2148
        reference.setDatePublished(timeperiod);
2149

    
2150
        //Quickfix for these 2 attributes (publication, publisher) used in feature.references
2151
        Reference inRef = reference.getInReference() == null ? reference : reference.getInReference();
2152
        //publication
2153
        if (isNotBlank(publisher)){
2154
            inRef.setPublisher(publisher);
2155
        }
2156

    
2157
        //publisher
2158
        if (isNotBlank(publication)){
2159
            inRef.setPlacePublished(publication);
2160
        }
2161

    
2162
        // TODO
2163
        String[] unhandledList = new String[] { ALTERNATEPUBTITLE, NOTES, STATUS };
2164
        for (String unhandled : unhandledList) {
2165
            String value = getAndRemoveMapKey(refMap, unhandled);
2166
            if (isNotBlank(value)) {
2167
                this.handleNotYetImplementedAttributeValue(parentEvent, CLASS, unhandled);
2168
            }
2169
        }
2170

    
2171
        for (String key : refMap.keySet()) {
2172
            if (!DETAILS.equalsIgnoreCase(key)) {
2173
                this.fireUnexpectedAttributeValue(parentEvent, CLASS, key);
2174
            }
2175
        }
2176

    
2177
        return reference;
2178
    }
2179

    
2180

    
2181
    /**
2182
     * Create reference for non nomenclatural references
2183
     * @return
2184
     */
2185
    protected Reference handleNonCitationSpecific(MarkupImportState state, String type, String authorStr,
2186
            String titleStr, String titleCache, String volume, String issue, String edition,
2187
            String editors, String pubName, String appendix, String pages, XMLEvent parentEvent) {
2188

    
2189
        Reference reference;
2190

    
2191
        //volume / issue
2192
        if (isBlank(volume) && isNotBlank(issue)){
2193
            String message = "Issue ('"+issue+"') exists but no volume";
2194
            fireWarningEvent(message, parentEvent, 4);
2195
            volume = issue;
2196
        }else if (isNotBlank(issue)){
2197
            volume = volume + "("+ issue + ")";
2198
        }
2199

    
2200
        //pubName / appendix
2201
        if (isNotBlank(appendix)){
2202
            pubName = pubName == null ?  appendix : (pubName + " " + appendix).replaceAll("  ", " ");
2203
        }
2204

    
2205
        if (isArticleNonCitation(type, pubName, volume, editors)) {
2206
            IArticle article = ReferenceFactory.newArticle();
2207
            if (pubName != null) {
2208
                IJournal journal = ReferenceFactory.newJournal();
2209
                journal.setTitle(pubName);
2210
                article.setInJournal(journal);
2211
            }else{
2212
                fireWarningEvent("Article has no journal", parentEvent, 4);
2213
            }
2214
            reference = (Reference) article;
2215
        } else {
2216
            if (isBookSection(type, authorStr, titleStr, editors, pubName, volume)){
2217
                IBookSection bookSection = ReferenceFactory.newBookSection();
2218
                if (pubName != null) {
2219
                    IBook book = ReferenceFactory.newBook();
2220
                    book.setTitle(pubName);
2221
                    bookSection.setInBook(book);
2222
                }
2223
                reference = (Reference)bookSection;
2224
            }else{
2225
                //??
2226
                Reference bookOrPartOf = ReferenceFactory.newGeneric();
2227
                if (pubName != null && titleStr != null) {
2228
                    Reference inReference = ReferenceFactory.newGeneric();
2229
                    inReference.setTitle(pubName);
2230
                    bookOrPartOf.setInReference(inReference);
2231
                }
2232
                reference = bookOrPartOf;
2233
            }
2234
        }
2235

    
2236
        //author
2237
        TeamOrPersonBase<?> author = createAuthor(state, authorStr);
2238
        reference.setAuthorship(author);
2239

    
2240
        //title
2241
        reference.setTitle(titleStr);
2242
        if (StringUtils.isNotBlank(titleCache)) {
2243
            reference.setTitleCache(titleCache, true);
2244
        }
2245

    
2246
        //edition
2247
        if(reference.getInReference() != null){
2248
            reference.getInReference().setEdition(edition);
2249
            reference.getInReference().setEditor(editors);
2250
        }else{
2251
            //edition
2252
            reference.setEdition(edition);
2253
            reference.setEditor(editors);
2254
        }
2255

    
2256
        //volume
2257
        reference.setVolume(volume);
2258

    
2259
        //pages
2260
        reference.setPages(pages);
2261

    
2262
        return reference;
2263
    }
2264

    
2265
    private boolean isBookSection(String type, String authorStr, String pubTitle,
2266
            String editors, String pubName, String volume) {
2267
        //type not yet handled
2268
        if (authorStr != null && editors != null
2269
                && pubTitle != null && pubName != null){
2270
            return true;
2271
        }else if (pubTitle != null && pubName != null && volume == null){
2272
            return true;
2273
        }else{
2274
            return false;
2275
        }
2276
    }
2277

    
2278

    
2279
    private boolean isArticleNonCitation(String type, String pubName, String volume, String editors) {
2280
        if ("journal".equalsIgnoreCase(type)){
2281
            return true;
2282
        }else if (volume != null && editors == null){
2283
            if (pubName != null && IJournal.guessIsJournalName(pubName)){
2284
                return true;
2285
            }else{
2286
                return false;  //unclear
2287
            }
2288
        }else{
2289
            return false;
2290
        }
2291
    }
2292

    
2293
    protected String handleRefPart(MarkupImportState state, XMLEventReader reader,
2294
            XMLEvent parentEvent, Map<String, String> refMap)
2295
            throws XMLStreamException {
2296
        String classValue = getClassOnlyAttribute(parentEvent);
2297

    
2298
        String text = "";
2299
        while (reader.hasNext()) {
2300
            XMLEvent next = readNoWhitespace(reader);
2301
            if (isMyEndingElement(next, parentEvent)) {
2302
                refMap.put(classValue, text);
2303
                return classValue;
2304
            } else if (next.isStartElement()) {
2305
                if (isStartingElement(next, ANNOTATION)) {
2306
                    handleNotYetImplementedElement(next); // TODO test handleSimpleAnnotation
2307
                } else if (isStartingElement(next, ITALICS)) {
2308
                    handleNotYetImplementedElement(next);
2309
                } else if (isStartingElement(next, BOLD)) {
2310
                    handleNotYetImplementedElement(next);
2311
                } else {
2312
                    handleUnexpectedStartElement(next.asStartElement());
2313
                }
2314
            } else if (next.isCharacters()) {
2315
                text += next.asCharacters().getData();
2316
            } else {
2317
                handleUnexpectedEndElement(next.asEndElement());
2318
            }
2319
        }
2320
        throw new IllegalStateException("RefPart has no closing tag");
2321
    }
2322

    
2323

    
2324
    private boolean isBlankOrPunctuation(String text) {
2325
        if (text == null){
2326
            return true;
2327
        } else {
2328
            return text.matches("^[\\s\\.,;:]*$");
2329
        }
2330
    }
2331

    
2332

    
2333
    /**
2334
     *Is heading an "habitat" type heading
2335
     * @param heading
2336
     * @return true if heading matches something like Eco(logy), Habitat(s) or Habitat & Ecology
2337
     */
2338
    private boolean isHabitatHeading(String heading) {
2339
        return heading.trim().matches("(Ecol(ogy)?|Habitat|Habitat\\s&\\sEcology)\\.?");
2340
    }
2341

    
2342

    
2343
	private String putCurrentSubheading(Map<String, SubheadingResult> subHeadingMap, String currentSubheading,
2344
	        String text, StringReferences fullReferences, List<IntextReference> inlineReferences) {
2345
		if (isNotBlank(text) || (fullReferences != null && isNotEmptyCollection(fullReferences.content))
2346
		        ||isNotEmptyCollection(inlineReferences)) {
2347
			SubheadingResult result = new SubheadingResult();
2348
			text = removeStartingMinus(text);
2349
			result.text = text.trim();
2350
			result.references = fullReferences == null ? new StringReferences() : fullReferences;
2351
			result.inlineReferences = inlineReferences;
2352
            subHeadingMap.put(currentSubheading, result);
2353
		}
2354
		return "";
2355
	}
2356

    
2357
	/**
2358
     * @param references2
2359
     * @return
2360
     */
2361
    protected boolean isNotEmptyCollection(Collection<?> list) {
2362
        return list != null && !list.isEmpty();
2363
    }
2364

    
2365

    
2366
    private String removeStartingMinus(String string) {
2367
		string = replaceStart(string, "-");
2368
		string = replaceStart(string, "\u002d");
2369
		string = replaceStart(string, "\u2013");
2370
		string = replaceStart(string, "\u2014");
2371
		string = replaceStart(string, "--");
2372
		return string;
2373
	}
2374

    
2375

    
2376
	/**
2377
	 * @param value
2378
	 * @param replacementString
2379
	 */
2380
	private String replaceStart(String value, String replacementString) {
2381
		if (value.startsWith(replacementString) ){
2382
			value = value.substring(replacementString.length()).trim();
2383
		}
2384
		while (value.startsWith("-") || value.startsWith("\u2014") ){
2385
			value = value.substring("-".length()).trim();
2386
		}
2387
		return value;
2388
	}
2389

    
2390

    
2391
	private String handleDistributionLocality(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent)throws XMLStreamException {
2392
		Map<String, Attribute> attributes = getAttributes(parentEvent);
2393
		String classValue = getAndRemoveRequiredAttributeValue(parentEvent, attributes, CLASS);
2394
		String statusValue =getAndRemoveAttributeValue(attributes, STATUS);
2395
		String frequencyValue =getAndRemoveAttributeValue(attributes, FREQUENCY);
2396

    
2397
		Taxon taxon = state.getCurrentTaxon();
2398
		// TODO which ref to take?
2399
		Reference sourceReference = state.getConfig().getSourceReference();
2400

    
2401
		String text = "";
2402
		while (reader.hasNext()) {
2403
			XMLEvent next = readNoWhitespace(reader);
2404
			if (isMyEndingElement(next, parentEvent)) {
2405
				if (StringUtils.isNotBlank(text)) {
2406
					String label = CdmUtils.removeTrailingDots(normalize(text));
2407
					TaxonDescription description = getExtractedMarkupMarkedDescription(state, taxon, sourceReference);
2408
					NamedAreaLevel level = makeNamedAreaLevel(state,classValue, next);
2409

    
2410
					//status
2411
					PresenceAbsenceTerm status = null;
2412
					if (isNotBlank(statusValue)){
2413
						try {
2414
							status = state.getTransformer().getPresenceTermByKey(statusValue);
2415
							if (status == null){
2416
							    UUID uuid = state.getTransformer().getPresenceTermUuid(statusValue);
2417
							    if (uuid != null){
2418
							        status = this.getPresenceAbsenceTerm(state, uuid, statusValue, statusValue, statusValue, false, null);
2419
							    }
2420
							}
2421
							if (status == null){
2422
								//TODO
2423
								String message = "The presence/absence status '%s' could not be transformed to an CDM status";
2424
								fireWarningEvent(String.format(message, statusValue), next, 4);
2425
							}
2426
						} catch (UndefinedTransformerMethodException e) {
2427
							throw new RuntimeException(e);
2428
						}
2429
					}else{
2430
						status = PresenceAbsenceTerm.PRESENT();
2431
					}
2432
					//frequency
2433
					if (isNotBlank(frequencyValue)){
2434
						if (frequencyValue.equalsIgnoreCase("absent") && PresenceAbsenceTerm.PRESENT().equals(status)){ //to be on the safe side that not real status has been defined yet.
2435
						    status = PresenceAbsenceTerm.ABSENT();
2436
						}else{
2437
						    String message = "The frequency attribute is currently not yet available in CDM";
2438
						    fireWarningEvent(message, parentEvent, 6);
2439
						}
2440
					}
2441

    
2442
					NamedArea higherArea = null;
2443
					List<NamedArea> areas = new ArrayList<>();
2444

    
2445
					String patSingleArea = "([^,\\(]{3,})";
2446
					String patSeparator = "(,|\\sand\\s)";
2447
					String hierarchiePattern = String.format("%s\\((%s(%s%s)*)\\)", patSingleArea, patSingleArea, patSeparator, patSingleArea);
2448
					Pattern patHierarchie = Pattern.compile(hierarchiePattern, Pattern.CASE_INSENSITIVE);
2449
					Matcher matcher = patHierarchie.matcher(label);
2450
					if (matcher.matches()){
2451
						String higherAreaStr = matcher.group(1).trim();
2452
						higherArea =  makeArea(state, higherAreaStr, level);
2453
						String[] innerAreas = matcher.group(2).split(patSeparator);
2454
						for (String innerArea : innerAreas){
2455
							if (isNotBlank(innerArea)){
2456
								NamedArea singleArea = makeArea(state, innerArea.trim(), level);
2457
								areas.add(singleArea);
2458
								NamedArea partOf = singleArea.getPartOf();
2459
//								if (partOf == null){
2460
//									singleArea.setPartOf(higherArea);
2461
//								}
2462
							}
2463
						}
2464
					}else{
2465
						NamedArea singleArea = makeArea(state, label, level);
2466
						areas.add(singleArea);
2467
					}
2468

    
2469
					for (NamedArea area : areas){
2470
						//create distribution
2471
						Distribution distribution = Distribution.NewInstance(area,status);
2472
						distribution.addPrimaryTaxonomicSource(sourceReference);
2473
						description.addElement(distribution);
2474
					}
2475
				} else {
2476
					String message = "Empty distribution locality";
2477
					fireWarningEvent(message, next, 4);
2478
				}
2479
				return text;
2480
			} else if (isStartingElement(next, COORDINATES)) {
2481
				//TODO
2482
				handleNotYetImplementedElement(next);
2483
			} else if (isEndingElement(next, COORDINATES)) {
2484
				//TODO
2485
				popUnimplemented(next.asEndElement());
2486
			} else if (next.isCharacters()) {
2487
				text += next.asCharacters().getData();
2488
			} else {
2489
				handleUnexpectedElement(next);
2490
			}
2491
		}
2492
		throw new IllegalStateException("<DistributionLocality> has no closing tag");
2493
	}
2494

    
2495
	   /**
2496
     * @param state
2497
     * @param taxon
2498
     * @param ref
2499
     * @return
2500
     */
2501
    protected TaxonDescription getExtractedMarkupMarkedDescription(MarkupImportState state, Taxon taxon, Reference sourceReference) {
2502
        MarkerType markerType = getMarkerType(
2503
                state,
2504
                MarkupTransformer.uuidMarkerExtractedMarkupData,
2505
                "Extracted factual data", "Marker type for factual data imported from markup where the markup for this data was included in parent markup that was also imported including the text from this markup.",
2506
                "Extr. data",
2507
                null);
2508
        String title = "Extracted markup data for " + taxon.getName().getTitleCache();
2509
        TaxonDescription description = getMarkedTaxonDescription(taxon, markerType, false, true, sourceReference, title);
2510
        return description;
2511
    }
2512

    
2513
}
(9-9/19)