Project

General

Profile

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

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

    
12
import java.net.MalformedURLException;
13
import java.net.URISyntaxException;
14
import java.net.URL;
15
import java.util.ArrayList;
16
import java.util.Arrays;
17
import java.util.HashMap;
18
import java.util.HashSet;
19
import java.util.LinkedList;
20
import java.util.List;
21
import java.util.Map;
22
import java.util.Queue;
23
import java.util.Set;
24
import java.util.UUID;
25

    
26
import javax.xml.stream.FactoryConfigurationError;
27
import javax.xml.stream.Location;
28
import javax.xml.stream.XMLEventReader;
29
import javax.xml.stream.XMLStreamException;
30
import javax.xml.stream.events.Attribute;
31
import javax.xml.stream.events.StartElement;
32
import javax.xml.stream.events.XMLEvent;
33

    
34
import org.apache.commons.lang.StringUtils;
35
import org.apache.log4j.Logger;
36
import org.springframework.beans.factory.annotation.Autowired;
37
import org.springframework.security.access.PermissionEvaluator;
38
import org.springframework.security.authentication.AuthenticationManager;
39
import org.springframework.security.core.Authentication;
40
import org.springframework.stereotype.Component;
41
import org.springframework.transaction.TransactionStatus;
42

    
43
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade;
44
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade.DerivedUnitType;
45
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeCacheStrategy;
46
import eu.etaxonomy.cdm.common.CdmUtils;
47
import eu.etaxonomy.cdm.io.common.ICdmIO;
48
import eu.etaxonomy.cdm.io.common.mapping.UndefinedTransformerMethodException;
49
import eu.etaxonomy.cdm.io.markup.UnmatchedLeads.UnmatchedLeadsKey;
50
import eu.etaxonomy.cdm.model.agent.AgentBase;
51
import eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor;
52
import eu.etaxonomy.cdm.model.agent.Team;
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.Figure;
61
import eu.etaxonomy.cdm.model.common.Language;
62
import eu.etaxonomy.cdm.model.common.TermVocabulary;
63
import eu.etaxonomy.cdm.model.common.TimePeriod;
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.KeyStatement;
68
import eu.etaxonomy.cdm.model.description.PolytomousKey;
69
import eu.etaxonomy.cdm.model.description.PolytomousKeyNode;
70
import eu.etaxonomy.cdm.model.description.PresenceAbsenceTermBase;
71
import eu.etaxonomy.cdm.model.description.TaxonDescription;
72
import eu.etaxonomy.cdm.model.description.TextData;
73
import eu.etaxonomy.cdm.model.location.NamedArea;
74
import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
75
import eu.etaxonomy.cdm.model.media.IdentifiableMediaEntity;
76
import eu.etaxonomy.cdm.model.media.Media;
77
import eu.etaxonomy.cdm.model.name.CultivarPlantName;
78
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
79
import eu.etaxonomy.cdm.model.name.NameTypeDesignationStatus;
80
import eu.etaxonomy.cdm.model.name.NomenclaturalCode;
81
import eu.etaxonomy.cdm.model.name.NomenclaturalStatus;
82
import eu.etaxonomy.cdm.model.name.NomenclaturalStatusType;
83
import eu.etaxonomy.cdm.model.name.NonViralName;
84
import eu.etaxonomy.cdm.model.name.Rank;
85
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignationStatus;
86
import eu.etaxonomy.cdm.model.name.TaxonNameBase;
87
import eu.etaxonomy.cdm.model.occurrence.Collection;
88
import eu.etaxonomy.cdm.model.occurrence.DerivedUnitBase;
89
import eu.etaxonomy.cdm.model.occurrence.FieldObservation;
90
import eu.etaxonomy.cdm.model.occurrence.Specimen;
91
import eu.etaxonomy.cdm.model.reference.IArticle;
92
import eu.etaxonomy.cdm.model.reference.IJournal;
93
import eu.etaxonomy.cdm.model.reference.Reference;
94
import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
95
import eu.etaxonomy.cdm.model.reference.ReferenceType;
96
import eu.etaxonomy.cdm.model.taxon.Classification;
97
import eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType;
98
import eu.etaxonomy.cdm.model.taxon.Taxon;
99
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
100
import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
101
import eu.etaxonomy.cdm.strategy.parser.NameTypeParser;
102
import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl;
103
import eu.etaxonomy.cdm.strategy.parser.SpecimenTypeParser;
104
import eu.etaxonomy.cdm.strategy.parser.SpecimenTypeParser.TypeInfo;
105

    
106
/**
107
 * @author a.mueller
108
 * 
109
 */
110
@Component
111
public class MarkupDocumentImport extends MarkupImportBase implements ICdmIO<MarkupImportState> {
112
	private static final String FREQUENCY = "frequency";
113

    
114
	private static final String TAXONTYPE = "taxontype";
115

    
116
	private static final String DEDICATION = "dedication";
117

    
118
	private static final String QUOTE = "quote";
119

    
120
	private static final Logger logger = Logger.getLogger(MarkupDocumentImport.class);
121

    
122
	private static final boolean CREATE_NEW = true;
123
	private static final boolean IS_IMAGE_GALLERY = true;
124
	private static final boolean NO_IMAGE_GALLERY = false;
125

    
126
	private static final String ACCEPTED = "accepted";
127
	private static final String ACCEPTED_NAME = "acceptedName";
128
	private static final String ADDENDA = "addenda";
129
	private static final String ALTERNATEPUBTITLE = "alternatepubtitle";
130
	private static final String ALTERNATIVE_COLLECTION_TYPE_STATUS = "alternativeCollectionTypeStatus";
131
	private static final String ALTERNATIVE_COLLECTOR = "alternativeCollector";
132
	private static final String ALTERNATIVE_FIELD_NUM = "alternativeFieldNum";
133
	private static final String ALTITUDE = "altitude";
134
	private static final String ANNOTATION = "annotation";
135
	private static final String AUTHOR = "author";
136
	private static final String BIBLIOGRAPHY = "bibliography";
137
	private static final String BIOGRAPHIES = "biographies";
138
	private static final String BOLD = "bold";
139
	private static final String BR = "br";
140
	private static final String CHAR = "char";
141
	private static final String CITATION = "citation";
142
	private static final String COLLECTION_TYPE_STATUS = "collectionTypeStatus";
143
	private static final String COLLECTOR = "collector";
144
	private static final String COORDINATES = "coordinates";
145
	private static final String COUPLET = "couplet";
146
	private static final String DATES = "dates";
147
	private static final String DEFAULT_MEDIA_URL = "defaultMediaUrl";
148
	private static final String DESTROYED = "destroyed";
149
	private static final String DETAILS = "details";
150
	private static final String DISTRIBUTION_LIST = "distributionList";
151
	private static final String DISTRIBUTION_LOCALITY = "distributionLocality";
152
	private static final String EDITION = "edition";
153
	private static final String EDITORS = "editors";
154
	private static final String FEATURE = "feature";
155
	private static final String FIGURE = "figure";
156
	private static final String FIGURE_LEGEND = "figureLegend";
157
	private static final String FIGURE_PART = "figurePart";
158
	private static final String FIGURE_REF = "figureRef";
159
	private static final String FIGURE_TITLE = "figureTitle";
160
	private static final String FOOTNOTE = "footnote";
161
	private static final String FOOTNOTE_REF = "footnoteRef";
162
	private static final String FOOTNOTE_STRING = "footnoteString";
163
	private static final String FIELD_NUM = "fieldNum";
164
	private static final String FULL_NAME = "fullName";
165
	private static final String FULL_TYPE = "fullType";
166
	private static final String GATHERING = "gathering";
167
	private static final String HEADING = "heading";
168
	private static final String HABITAT = "habitat";
169
	private static final String HABITAT_LIST = "habitatList";
170
	private static final String HOMONYM = "homonym";
171
	private static final String HOMOTYPES = "homotypes";
172
	private static final String ID = "id";
173
	private static final String INFRANK = "infrank";
174
	private static final String INFRAUT = "infraut";
175
	private static final String INFRPARAUT = "infrparaut";
176
	private static final String IS_SPOTCHARACTERS = "isSpotcharacters";
177
	private static final String ISSUE = "issue";
178
	private static final String ITALICS = "italics";
179
	private static final String KEY = "key";
180
	private static final String KEY_TITLE = "keyTitle";
181
	private static final String KEYNOTES = "keynotes";
182
	private static final String LIFE_CYCLE_PERIODS = "lifeCyclePeriods";
183
	private static final String LOCALITY = "locality";
184
	private static final String LOST = "lost";
185
	private static final String META_DATA = "metaData";
186
	private static final String NAME = "name";
187
	private static final String NAME_TYPE = "nameType";
188
	private static final String NOM = "nom";
189
	private static final String NOMENCLATURE = "nomenclature";
190
	private static final String NOT_FOUND = "notFound";
191
	private static final String NOT_SEEN = "notSeen";
192
	private static final String NOTES = "notes";
193
	private static final String NUM = "num";
194
	private static final String ORIGINAL_DETERMINATION = "originalDetermination";
195
	private static final String PARAUT = "paraut";
196
	private static final String PUBFULLNAME = "pubfullname";
197
	private static final String PUBLICATION = "publication";
198
	private static final String PUBNAME = "pubname";
199
	private static final String PUBTITLE = "pubtitle";
200
	private static final String PUBTYPE = "pubtype";
201
	private static final String QUESTION = "question";
202
	private static final String RANK = "rank";
203
	private static final String REF = "ref";
204
	private static final String REF_NUM = "refNum";
205
	private static final String REF_PART = "refPart";
206
	private static final String REFERENCE = "reference";
207
	private static final String REFERENCES = "references";
208
	private static final String TAXON = "taxon";
209
	private static final String TAXONTITLE = "taxontitle";
210
	private static final String TEXT = "text";
211
	private static final String TEXT_SECTION = "textSection";
212
	private static final String TO_COUPLET = "toCouplet";
213
	private static final String TO_KEY = "toKey";
214
	private static final String TO_TAXON = "toTaxon";
215
	private static final String TYPE = "type";
216
	private static final String TYPE_STATUS = "typeStatus";
217
	private static final String TREATMENT = "treatment";
218
	private static final String SERIALS_ABBREVIATIONS = "serialsAbbreviations";
219
	private static final String SPECIMEN_TYPE = "specimenType";
220
	private static final String STATUS = "status";
221
	private static final String STRING = "string";
222
	private static final String SUB_HEADING = "subHeading";
223
	private static final String SUB_COLLECTION = "subCollection";
224
	private static final String SYNONYM = "synonym";
225
	private static final String UNKNOWN = "unknown";
226
	private static final String URL = "url";
227
	private static final String USAGE = "usage";
228
	private static final String VOLUME = "volume";
229
	private static final String WRITER = "writer";
230
	private static final String YEAR = "year";
231

    
232
	private NonViralNameParserImpl parser = new NonViralNameParserImpl();
233

    
234
	// TODO make part of state, but state is renewed when invoking the import a
235
	// second time
236
	private UnmatchedLeads unmatchedLeads;
237

    
238
	// TODO remove preliminary
239
	@Autowired
240
	private AuthenticationManager authenticationManager;
241
	private Authentication authentication;
242
	private PermissionEvaluator permissionEvaluator;
243

    
244
	public MarkupDocumentImport() {
245
		super();
246
		System.out.println("TODO remove preliminary authentication");
247
		// UsernamePasswordAuthenticationToken token = new
248
		// UsernamePasswordAuthenticationToken("admin", "0000");
249
		// authentication = authenticationManager.authenticate(token);
250
		// SecurityContext context = SecurityContextHolder.getContext();
251
		// context.setAuthentication(authentication);
252
		// permissionEvaluator = new CdmPermissionEvaluator();
253
	}
254

    
255
	@Override
256
	public boolean doCheck(MarkupImportState state) {
257
		state.setCheck(true);
258
		doInvoke(state);
259
		state.setCheck(false);
260
		return state.isSuccess();
261
	}
262

    
263
	@Override
264
	public void doInvoke(MarkupImportState state) { 
265
		fireProgressEvent("Start import markup document", "Before start of document");
266
		
267
		Queue<CdmBase> outputStream = new LinkedList<CdmBase>();
268

    
269
		TransactionStatus tx = startTransaction();
270
		// FIXME reset state
271
		doAllTheOldOtherStuff(state);
272

    
273
		// START
274
		try {
275
			// StAX
276
			XMLEventReader reader = getStaxReader(state);
277
			state.setReader(reader);
278
			// start document
279
			if (!validateStartOfDocument(reader)) {
280
				state.setUnsuccessfull();
281
				return;
282
			}
283

    
284
			// publication
285
			String elName = PUBLICATION;
286
			boolean hasPublication = false;
287
			
288
			while (reader.hasNext()) {
289
				XMLEvent nextEvent = reader.nextEvent();
290
				if (isStartingElement(nextEvent, elName)) {
291
					handlePublication(state, reader, nextEvent, elName);
292
					hasPublication = true;
293
				} else if (nextEvent.isEndDocument()) {
294
					if (!hasPublication) {
295
						String message = "No publication root element found";
296
						fireWarningEvent(message, nextEvent, 8);
297
					}
298
					// done
299
				} else {
300
					fireSchemaConflictEventExpectedStartTag(elName, reader);
301
				}
302
			}
303
			commitTransaction(tx);
304

    
305
			// //SAX
306
			// ImportHandlerBase handler= new PublicationHandler(this);
307
			// parseSAX(state, handler);
308

    
309
		} catch (FactoryConfigurationError e1) {
310
			fireWarningEvent("Some error occurred while setting up xml factory. Data can't be imported", "Start", 16);
311
			state.setUnsuccessfull();
312
		} catch (XMLStreamException e1) {
313
			fireWarningEvent("An XMLStreamException occurred while parsing. Data can't be imported", "Start", 16);
314
			state.setUnsuccessfull();
315
			// } catch (ParserConfigurationException e) {
316
			// fireWarningEvent("A ParserConfigurationException occurred while parsing. Data can't be imported",
317
			// "Start", 16);
318
			// } catch (SAXException e) {
319
			// fireWarningEvent("A SAXException occurred while parsing. Data can't be imported",
320
			// "Start", 16);
321
			// } catch (IOException e) {
322
			// fireWarningEvent("An IO exception occurred while parsing. Data can't be imported",
323
			// "Start", 16);
324

    
325
		}
326
		
327
		return;
328

    
329
	}
330

    
331
	private void handlePublication(MarkupImportState state,
332
			XMLEventReader reader, XMLEvent currentEvent, String elName)
333
			throws XMLStreamException {
334

    
335
		// attributes
336
		StartElement element = currentEvent.asStartElement();
337
		Map<String, Attribute> attributes = getAttributes(element);
338
		handleUnexpectedAttributes(element.getLocation(), attributes,
339
				"noNamespaceSchemaLocation");
340

    
341
		while (reader.hasNext()) {
342
			XMLEvent event = readNoWhitespace(reader);
343
			// TODO cardinality of alternative
344
			if (event.isEndElement()) {
345
				if (isEndingElement(event, elName)) {
346
					return;
347
				} else {
348
					if (isEndingElement(event, BIOGRAPHIES)) {
349
						// NOT YET IMPLEMENTED
350
						popUnimplemented(event.asEndElement());
351
					} else if (isEndingElement(event, REFERENCES)) {
352
						// NOT YET IMPLEMENTED
353
						popUnimplemented(event.asEndElement());
354
					} else if (isEndingElement(event, TEXT_SECTION)) {
355
						// NOT YET IMPLEMENTED
356
						popUnimplemented(event.asEndElement());
357
					} else if (isEndingElement(event, ADDENDA)) {
358
						// NOT YET IMPLEMENTED
359
						popUnimplemented(event.asEndElement());
360
					} else {
361
						handleUnexpectedElement(event);
362
					}
363
				}
364
			} else if (event.isStartElement()) {
365
				if (isStartingElement(event, META_DATA)) {
366
					handleMetaData(state, reader, event);
367
				} else if (isStartingElement(event, TREATMENT)) {
368
					handleTreatment(state, reader, event);
369
				} else if (isStartingElement(event, BIOGRAPHIES)) {
370
					handleNotYetImplementedElement(event);
371
				} else if (isStartingElement(event, REFERENCES)) {
372
					handleNotYetImplementedElement(event);
373
				} else if (isStartingElement(event, TEXT_SECTION)) {
374
					handleNotYetImplementedElement(event);
375
				} else if (isStartingElement(event, ADDENDA)) {
376
					handleNotYetImplementedElement(event);
377
				} else {
378
					handleUnexpectedStartElement(event);
379
				}
380
			} else {
381
				handleUnexpectedElement(event);
382
			}
383
		}
384
		throw new IllegalStateException("Publication has no ending element");
385
	}
386

    
387
	private void handleMetaData(MarkupImportState state, XMLEventReader reader,
388
			XMLEvent parentEvent) throws XMLStreamException {
389
		checkNoAttributes(parentEvent);
390

    
391
		while (reader.hasNext()) {
392
			XMLEvent next = readNoWhitespace(reader);
393
			if (isMyEndingElement(next, parentEvent)) {
394
				return;
395
			} else if (isStartingElement(next, DEFAULT_MEDIA_URL)) {
396
				String baseUrl = getCData(state, reader, next);
397
				try {
398
					new URL(baseUrl);
399
					state.setBaseMediaUrl(baseUrl);
400
				} catch (MalformedURLException e) {
401
					String message = "defaultMediaUrl '%s' is not a valid URL";
402
					message = String.format(message, baseUrl);
403
					fireWarningEvent(message, next, 8);
404
				}
405
			} else {
406
				handleUnexpectedElement(next);
407
			}
408
		}
409
		throw new IllegalStateException("MetaData has no ending element");
410

    
411
	}
412

    
413
	private void handleTreatment(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
414
		checkNoAttributes(parentEvent);
415
		Taxon lastTaxon = null;
416
		while (reader.hasNext()) {
417
			XMLEvent next = readNoWhitespace(reader);
418
			if (isStartingElement(next, TAXON)) {
419
				Taxon thisTaxon = handleTaxon(state, reader, next.asStartElement());
420
				doTaxonRelation(state, thisTaxon, lastTaxon, parentEvent.getLocation());
421
				lastTaxon = thisTaxon;
422
				// TODO for imports spanning multiple documents ?? Still needed?
423
				state.getConfig().setLastTaxonUuid(lastTaxon.getUuid());
424
			} else if (isMyEndingElement(next, parentEvent)) {
425
				Set<PolytomousKeyNode> keyNodesToSave = state.getPolytomousKeyNodesToSave();
426
				//better save the key then the nodes
427
				Set<PolytomousKey> keySet = new HashSet<PolytomousKey>();
428
				for (PolytomousKeyNode node : keyNodesToSave){
429
					PolytomousKey key = node.getKey();
430
					keySet.add(key);
431
				}
432
				save(keySet, state);
433
				//unmatched key leads
434
				UnmatchedLeads unmatched = state.getUnmatchedLeads();
435
				if (unmatched.size() > 0){
436
					String message = "The following key leads are unmatched: %s";
437
					message = String.format(message, state.getUnmatchedLeads().toString());
438
					fireWarningEvent(message, next, 6);
439
				}
440
//				save(keyNodesToSave, state);
441

    
442
				return;
443
			} else {
444
				handleUnexpectedElement(next);
445
			}
446
		}
447
		return;
448
	}
449

    
450
	/**
451
	 * @param taxon
452
	 * @param lastTaxon
453
	 */
454
	private void doTaxonRelation(MarkupImportState state, Taxon taxon,
455
			Taxon lastTaxon, Location dataLocation) {
456

    
457
		Classification tree = makeTree(state, dataLocation);
458
		if (lastTaxon == null) {
459
			tree.addChildTaxon(taxon, null, null, null);
460
			return;
461
		}
462
		Rank thisRank = taxon.getName().getRank();
463
		Rank lastRank = lastTaxon.getName().getRank();
464
		if (lastTaxon.getTaxonNodes().size() > 0) {
465
			TaxonNode lastNode = lastTaxon.getTaxonNodes().iterator().next();
466
			if (thisRank.isLower(lastRank)) {
467
				lastNode.addChildTaxon(taxon, null, null, null);
468
				fillMissingEpithetsForTaxa(lastTaxon, taxon);
469
			} else if (thisRank.equals(lastRank)) {
470
				TaxonNode parent = lastNode.getParent();
471
				if (parent != null) {
472
					parent.addChildTaxon(taxon, null, null, null);
473
					fillMissingEpithetsForTaxa(parent.getTaxon(), taxon);
474
				} else {
475
					tree.addChildTaxon(taxon, null, null, null);
476
				}
477
			} else if (thisRank.isHigher(lastRank)) {
478
				doTaxonRelation(state, taxon, lastNode.getParent().getTaxon(),
479
						dataLocation);
480
				// TaxonNode parentNode = handleTaxonRelation(state, taxon,
481
				// lastNode.getParent().getTaxon());
482
				// parentNode.addChildTaxon(taxon, null, null, null);
483
			}
484
		} else {
485

    
486
			String message = "Last taxon has no node";
487
			fireWarningEvent(message, makeLocationStr(dataLocation), 6);
488
		}
489
	}
490

    
491
	/**
492
	 * @param state
493
	 * @param dataLocation 
494
	 * @return
495
	 */
496
	private Classification makeTree(MarkupImportState state, Location dataLocation) {
497
		Classification result = state.getTree(null);
498
		if (result == null) {
499
			UUID uuid = state.getConfig().getClassificationUuid();
500
			if (uuid == null) {
501
				String message = "No classification uuid is defined";
502
				fireWarningEvent(message, makeLocationStr(dataLocation), 6);
503
				result = createNewClassification(state);
504
			} else {
505
				result = getClassificationService().find(uuid);
506
				if (result == null) {
507
					result = createNewClassification(state);
508
					result.setUuid(uuid);
509
				}
510
			}
511
			state.putTree(null, result);
512
		}
513
		save(result, state);
514
		return result;
515
	}
516

    
517
	private Classification createNewClassification(MarkupImportState state) {
518
		Classification result;
519
		result = Classification.NewInstance(state.getConfig().getClassificationTitle());
520
		state.putTree(null, result);
521
		return result;
522
	}
523

    
524
	private Taxon handleTaxon(MarkupImportState state, XMLEventReader reader, StartElement parentEvent) throws XMLStreamException {
525
		// TODO progress monitoring
526
		Map<String, Attribute> attributes = getAttributes(parentEvent);
527
		Taxon taxon = createTaxonAndName(state, attributes);
528
		state.setCurrentTaxon(taxon);
529

    
530
		boolean hasTitle = false;
531
		boolean hasNomenclature = false;
532
		String taxonTitle = null;
533

    
534
		while (reader.hasNext()) {
535
			XMLEvent next = readNoWhitespace(reader);
536
			if (next.isEndElement()) {
537
				if (isMyEndingElement(next, parentEvent)) {
538
					checkMandatoryElement(hasTitle, parentEvent, TAXONTITLE);
539
					checkMandatoryElement(hasNomenclature, parentEvent,	NOMENCLATURE);
540
					handleUnexpectedAttributes(parentEvent.getLocation(),attributes);
541
					
542
					makeKeyNodes(state, parentEvent, taxonTitle);
543
					state.setCurrentTaxon(null);
544
					state.setCurrentTaxonNum(null);
545
					save(taxon, state);
546
					return taxon;
547
				} else {
548
					if (isEndingElement(next, HEADING)) {
549
						// NOT YET IMPLEMENTED
550
						popUnimplemented(next.asEndElement());
551
					} else if (isEndingElement(next, TEXT_SECTION)) {
552
						// NOT YET IMPLEMENTED
553
						popUnimplemented(next.asEndElement());
554
					} else if (isEndingElement(next, REFERENCES)) {
555
						// NOT YET IMPLEMENTED
556
						popUnimplemented(next.asEndElement());
557
					} else {
558
						handleUnexpectedEndElement(next.asEndElement());
559
					}
560
				}
561
			} else if (next.isStartElement()) {
562
				if (isStartingElement(next, HEADING)) {
563
					handleNotYetImplementedElement(next);
564
				} else if (isStartingElement(next, TAXONTITLE)) {
565
					taxonTitle = handleTaxonTitle(state, reader, next);
566
					hasTitle = true;
567
				} else if (isStartingElement(next, WRITER)) {
568
					makeKeyWriter(state, reader, taxon, taxonTitle, next);
569
				} else if (isStartingElement(next, TEXT_SECTION)) {
570
					handleNotYetImplementedElement(next);
571
				} else if (isStartingElement(next, KEY)) {
572
					handleKey(state, reader, next);
573
				} else if (isStartingElement(next, NOMENCLATURE)) {
574
					handleNomenclature(state, reader, next);
575
					hasNomenclature = true;
576
				} else if (isStartingElement(next, FEATURE)) {
577
					handleFeature(state, reader, next);
578
				} else if (isStartingElement(next, NOTES)) {
579
					// TODO is this the correct way to handle notes?
580
					String note = handleNotes(state, reader, next);
581

    
582
					UUID notesUuid;
583
					try {
584
						notesUuid = state.getTransformer().getFeatureUuid(
585
								"notes");
586
						Feature feature = getFeature(state, notesUuid, "Notes",
587
								"Notes", "note", null);
588
						TextData textData = TextData.NewInstance(feature);
589
						textData.putText(Language.DEFAULT(), note);
590
						TaxonDescription description = getTaxonDescription(
591
								taxon, false, true);
592
						description.addElement(textData);
593
					} catch (UndefinedTransformerMethodException e) {
594
						String message = "getFeatureUuid method not yet implemented";
595
						fireWarningEvent(message, next, 8);
596
					}
597
				} else if (isStartingElement(next, REFERENCES)) {
598
					handleNotYetImplementedElement(next);
599
				} else if (isStartingElement(next, FIGURE)) {
600
					handleFigure(state, reader, next);
601
				} else if (isStartingElement(next, FOOTNOTE)) {
602
					FootnoteDataHolder footnote = handleFootnote(state, reader,
603
							next);
604
					if (footnote.isRef()) {
605
						String message = "Ref footnote not implemented here";
606
						fireWarningEvent(message, next, 4);
607
					} else {
608
						registerGivenFootnote(state, footnote);
609
					}
610
				} else {
611
					handleUnexpectedStartElement(next);
612
				}
613
			} else {
614
				handleUnexpectedElement(next);
615
			}
616
		}
617
		throw new IllegalStateException("<Taxon> has no closing tag");
618
	}
619

    
620
	private void makeKeyNodes(MarkupImportState state, XMLEvent event, String taxonTitle) {
621
		Taxon taxon = state.getCurrentTaxon();
622
		String num = state.getCurrentTaxonNum();
623
		
624
		String nameString = CdmBase.deproxy(taxon.getName(), NonViralName.class).getNameCache();
625
//		String nameString = taxonTitle;
626
		
627
		//try to find matching lead nodes 
628
		UnmatchedLeadsKey leadsKey = UnmatchedLeadsKey.NewInstance(num, nameString);
629
		Set<PolytomousKeyNode> matchingNodes = handleMatchingNodes(state, taxon, leadsKey);
630
		
631
		if (num != null){//same without using the num
632
			UnmatchedLeadsKey noNumLeadsKey = UnmatchedLeadsKey.NewInstance("", nameString);
633
			Set<PolytomousKeyNode> noNumMatchingNodes = handleMatchingNodes(state, taxon, noNumLeadsKey);
634
			if(noNumMatchingNodes.size() > 0){
635
				String message ="Taxon matches additional key node when not considering <num> attribute in taxontitle. This may be correct but may also indicate an error.";
636
				fireWarningEvent(message, event, 1);
637
			}
638
		}
639
		//report missing match, if num exists
640
		if (matchingNodes.isEmpty() && num != null){
641
			String message = "Taxon has <num> attribute in taxontitle but no matching key nodes exist: %s, Key: %s";
642
			message = String.format(message, num, leadsKey.toString());
643
			fireWarningEvent(message, event, 1);
644
		}
645
		
646
	}
647
	
648
	private Set<PolytomousKeyNode> handleMatchingNodes(MarkupImportState state, Taxon taxon, UnmatchedLeadsKey leadsKey) {
649
		Set<PolytomousKeyNode> matchingNodes = state.getUnmatchedLeads().getNodes(leadsKey);
650
		for (PolytomousKeyNode matchingNode : matchingNodes){
651
			state.getUnmatchedLeads().removeNode(leadsKey, matchingNode);
652
			matchingNode.setTaxon(taxon);
653
			state.getPolytomousKeyNodesToSave().add(matchingNode);
654
		}
655
		return matchingNodes;
656
	}
657

    
658
	private void handleKey(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
659
		// attributes
660
		Map<String, Attribute> attributes = getAttributes(parentEvent);
661
		String isSpotcharacters = getAndRemoveAttributeValue(attributes, IS_SPOTCHARACTERS);
662
		if (isNotBlank(isSpotcharacters) ) {
663
			//TODO isSpotcharacters
664
			String message = "Attribute isSpotcharacters not yet implemented for <key>";
665
			fireWarningEvent(message, parentEvent, 4);
666
		}
667
		
668
		PolytomousKey key = PolytomousKey.NewInstance();
669
		key.addTaxonomicScope(state.getCurrentTaxon());
670
		state.setCurrentKey(key);
671
		
672
		boolean isFirstCouplet = true;
673
		while (reader.hasNext()) {
674
			XMLEvent next = readNoWhitespace(reader);
675
			if (isMyEndingElement(next, parentEvent)) {
676
				save(key, state);
677
				state.setCurrentKey(null);
678
				return;
679
			} else if (isEndingElement(next, KEYNOTES)){
680
				popUnimplemented(next.asEndElement());
681
			} else if (isStartingElement(next, KEY_TITLE)) {
682
					handleKeyTitle(state, reader, next);
683
			} else if (isStartingElement(next, KEYNOTES)) {
684
				//TODO
685
				handleNotYetImplementedElement(next);
686
			} else if (isStartingElement(next, COUPLET)) {
687
				PolytomousKeyNode node = null;
688
				if (isFirstCouplet){
689
					node = key.getRoot();
690
					isFirstCouplet = false;
691
				}
692
				handleCouplet(state, reader, next, node);
693
			} else {
694
				handleUnexpectedElement(next);
695
			}
696
		}
697
		throw new IllegalStateException("<key> has no closing tag");
698
	}
699

    
700
	/**
701
	 * @param state
702
	 * @param reader
703
	 * @param key
704
	 * @param next
705
	 * @throws XMLStreamException
706
	 */
707
	private void handleKeyTitle(MarkupImportState state, XMLEventReader reader, XMLEvent next) throws XMLStreamException {
708
		PolytomousKey key = state.getCurrentKey();
709
		String keyTitle = getCData(state, reader, next);
710
		String standardTitles = "(?i)(Key\\sto\\sthe\\s(genera|species|varieties|forms))";
711
		
712
		if (isNotBlank(keyTitle) ){
713
			if (!state.getConfig().isReplaceStandardKeyTitles() || ! keyTitle.matches(standardTitles)){
714
				key.setTitleCache(keyTitle, true);
715
			}
716
		}
717
	}
718

    
719
	private void handleCouplet(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, PolytomousKeyNode parentNode) throws XMLStreamException {
720
		String num = getOnlyAttribute(parentEvent, NUM, true);
721
		List<PolytomousKeyNode> childList = new ArrayList<PolytomousKeyNode>(); 
722
		
723
		while (reader.hasNext()) {
724
			XMLEvent next = readNoWhitespace(reader);
725
			if (isMyEndingElement(next, parentEvent)) {
726
				completeCouplet(state, parentEvent, parentNode, num, childList);
727
				return;
728
			} else if (isStartingElement(next, QUESTION)) {
729
				handleQuestion(state, reader, next, childList);
730
			} else if (isStartingElement(next, KEYNOTES)) {
731
				//TODO
732
				handleNotYetImplementedElement(next);
733
			} else if (isEndingElement(next, KEYNOTES)) {
734
				//TODO
735
				popUnimplemented(next.asEndElement());
736
			} else {
737
				handleUnexpectedElement(next);
738
			}
739
		}
740
		throw new IllegalStateException("<couplet> has no closing tag");
741
	}
742

    
743
	/**
744
	 * @param state
745
	 * @param parentEvent
746
	 * @param parentNode
747
	 * @param num
748
	 * @param childList
749
	 */
750
	private void completeCouplet(MarkupImportState state, XMLEvent parentEvent,
751
			PolytomousKeyNode parentNode, String num,
752
			List<PolytomousKeyNode> childList) {
753
		if (parentNode != null){
754
			for (PolytomousKeyNode childNode : childList){
755
				parentNode.addChild(childNode);
756
			}
757
		}else if (isNotBlank(num)){
758
			UnmatchedLeadsKey unmatchedKey = UnmatchedLeadsKey.NewInstance(state.getCurrentKey(), num);
759
			Set<PolytomousKeyNode> nodes = state.getUnmatchedLeads().getNodes(unmatchedKey);
760
			for(PolytomousKeyNode nodeToMatch: nodes){
761
				for (PolytomousKeyNode childNode : childList){
762
					nodeToMatch.addChild(childNode);
763
				}
764
				state.getUnmatchedLeads().removeNode(unmatchedKey, nodeToMatch);
765
			}
766
		}else{
767
			String message = "Parent num could not be matched. Please check if num (%s) is correct";
768
			message = String.format(message, num);
769
			fireWarningEvent(message, parentEvent, 6);
770
		}
771
	}
772

    
773
	private void handleQuestion(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, List<PolytomousKeyNode> nodesList) throws XMLStreamException {
774
		// attributes
775
		Map<String, Attribute> attributes = getAttributes(parentEvent);
776
		//needed only for data lineage
777
		String questionNum = getAndRemoveRequiredAttributeValue(parentEvent, attributes, NUM);
778
		
779
		PolytomousKeyNode myNode = PolytomousKeyNode.NewInstance();
780
		myNode.setKey(state.getCurrentKey());  //to avoid NPE while computing num in PolytomousKeyNode in case this node is not matched correctly with a parent
781
		nodesList.add(myNode);
782
		
783
		while (reader.hasNext()) {
784
			XMLEvent next = readNoWhitespace(reader);
785
			if (isMyEndingElement(next, parentEvent)) {
786
				return;
787
			} else if (isStartingElement(next, TEXT)) {
788
				String text = getCData(state, reader, next);
789
				KeyStatement statement = KeyStatement.NewInstance(text);
790
				myNode.setStatement(statement);
791
			} else if (isStartingElement(next, COUPLET)) {
792
				//TODO test
793
				handleCouplet(state, reader, next, myNode);
794
			} else if (isStartingElement(next, TO_COUPLET)) {
795
				handleToCouplet(state, reader, next, myNode);
796
			} else if (isStartingElement(next, TO_TAXON)) {
797
				handleToTaxon(state, reader, next, myNode);
798
			
799
			} else if (isStartingElement(next, TO_KEY)) {
800
				//TODO
801
				handleNotYetImplementedElement(next);
802
			} else if (isEndingElement(next, TO_KEY)){
803
				//TODO
804
				popUnimplemented(next.asEndElement());
805
			} else if (isStartingElement(next, KEYNOTES)) {
806
				//TODO
807
				handleNotYetImplementedElement(next);
808
			} else if (isEndingElement(next, KEYNOTES)){
809
				//TODO
810
				popUnimplemented(next.asEndElement());
811
			} else {
812
				handleUnexpectedElement(next);
813
			}
814
		}
815
		throw new IllegalStateException("<question> has no closing tag");
816
	}
817

    
818
	private void handleToCouplet(MarkupImportState state, XMLEventReader reader, XMLEvent next, PolytomousKeyNode node) throws XMLStreamException {
819
		String num = getOnlyAttribute(next, NUM, true);
820
		String cData = getCData(state, reader, next, false);
821
		if (isNotBlank(cData) && ! cData.equals(num)){
822
			String message = "CData ('%s') not handled in <toCouplet>";
823
			message = String.format(message, cData);
824
			fireWarningEvent(message, next, 4);
825
		}
826
		UnmatchedLeadsKey unmatched = UnmatchedLeadsKey.NewInstance(state.getCurrentKey(), num);
827
		state.getUnmatchedLeads().addKey(unmatched, node);
828
	}
829

    
830
	private void handleToTaxon(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, PolytomousKeyNode node) throws XMLStreamException {
831
		Map<String, Attribute> attributes = getAttributes(parentEvent);
832
		String num = getAndRemoveAttributeValue(attributes, NUM);
833
		String taxonStr = getCData(state, reader, parentEvent, false);
834
		//TODO ?
835
		taxonStr = makeTaxonKey(taxonStr, state.getCurrentTaxon());
836
		UnmatchedLeadsKey unmatched = UnmatchedLeadsKey.NewInstance(num, taxonStr);
837
		state.getUnmatchedLeads().addKey(unmatched, node);
838
		return;
839
	}
840
	
841
	private String makeTaxonKey(String strGoto, Taxon taxon) {
842
		String result = "";
843
		if (strGoto == null){
844
			return "";
845
		}
846
		
847
		NonViralName<?> name = CdmBase.deproxy(taxon.getName(), NonViralName.class);
848
		String strGenusName = name.getGenusOrUninomial();
849
		
850
		
851
		strGoto = strGoto.replaceAll("\\([^\\(\\)]*\\)", "");  //replace all brackets
852
		strGoto = strGoto.replaceAll("\\s+", " "); //replace multiple whitespaces by exactly one whitespace
853
		
854
		strGoto = strGoto.trim();  
855
		String[] split = strGoto.split("\\s");
856
		for (int i = 0; i<split.length; i++){
857
			String single = split[i];
858
			if (isGenusAbbrev(single, strGenusName)){
859
				split[i] = strGenusName;
860
			}
861
			if (isInfraSpecificMarker(single)){
862
				String strSpeciesEpi = name.getSpecificEpithet();
863
				if (isBlank(result)){
864
					result += strGenusName + " " + strSpeciesEpi;
865
				}
866
			}
867
			result = (result + " " + split[i]).trim();
868
		}
869
		return result;
870
	}
871
	
872

    
873
	private boolean isInfraSpecificMarker(String single) {
874
		try {
875
			if (Rank.getRankByAbbreviation(single).isInfraSpecific()){
876
				return true;
877
			}else{
878
				return false;
879
			}
880
		} catch (UnknownCdmTypeException e) {
881
			return false;
882
		}
883
	}
884
	
885
	private boolean isGenusAbbrev(String single, String strGenusName) {
886
		if (! single.matches("[A-Z]\\.?")) {
887
			return false;
888
		}else if (single.length() == 0 || strGenusName == null || strGenusName.length() == 0){
889
			return false; 
890
		}else{
891
			return single.charAt(0) == strGenusName.charAt(0);
892
		}
893
	}
894

    
895
	/**
896
	 * @param state
897
	 * @param reader
898
	 * @param taxon
899
	 * @param taxonTitle
900
	 * @param next
901
	 * @throws XMLStreamException
902
	 */
903
	private void makeKeyWriter(MarkupImportState state, XMLEventReader reader, Taxon taxon, String taxonTitle, XMLEvent next) throws XMLStreamException {
904
		WriterDataHolder writer = handleWriter(state, reader, next);
905
		taxon.addExtension(writer.extension);
906
		// TODO what if taxonTitle comes later
907
		if (StringUtils.isNotBlank(taxonTitle)
908
				&& writer.extension != null) {
909
			Reference<?> sec = ReferenceFactory.newBookSection();
910
			sec.setTitle(taxonTitle);
911
			TeamOrPersonBase<?> author = createAuthor(writer.writer);
912
			sec.setAuthorTeam(author);
913
			sec.setInReference(state.getConfig()
914
					.getSourceReference());
915
			taxon.setSec(sec);
916
			registerFootnotes(state, sec, writer.footnotes);
917
		} else {
918
			String message = "No taxontitle exists for writer";
919
			fireWarningEvent(message, next, 6);
920
		}
921
	}
922

    
923
	private String handleNotes(MarkupImportState state, XMLEventReader reader,
924
			XMLEvent parentEvent) throws XMLStreamException {
925
		checkNoAttributes(parentEvent);
926

    
927
		String text = "";
928
		while (reader.hasNext()) {
929
			XMLEvent next = readNoWhitespace(reader);
930
			if (isMyEndingElement(next, parentEvent)) {
931
				return text;
932
			} else if (next.isEndElement()) {
933
				if (isEndingElement(next, HEADING)) {
934
					popUnimplemented(next.asEndElement());
935
				} else if (isEndingElement(next, WRITER)) {
936
					popUnimplemented(next.asEndElement());
937
				} else if (isEndingElement(next, NUM)) {
938
					popUnimplemented(next.asEndElement());
939
				} else {
940
					handleUnexpectedEndElement(next.asEndElement());
941
				}
942
			} else if (next.isStartElement()) {
943
				if (isStartingElement(next, HEADING)) {
944
					handleNotYetImplementedElement(next);
945
				} else if (isStartingElement(next, SUB_HEADING)) {
946
					String subheading = getCData(state, reader, next).trim();
947
					if (! isNoteHeading(subheading)) {
948
						fireNotYetImplementedElement(next.getLocation(), next.asStartElement().getName(), 0);
949
					}
950
				} else if (isStartingElement(next, WRITER)) {
951
					handleNotYetImplementedElement(next);
952
				} else if (isStartingElement(next, NUM)) {
953
					handleNotYetImplementedElement(next);
954
				} else if (isStartingElement(next, STRING)) {
955
					// TODO why multiple strings in schema?
956
					text = makeNotesString(state, reader, text, next);
957
				} else {
958
					handleUnexpectedStartElement(next.asStartElement());
959
				}
960
			} else {
961
				handleUnexpectedElement(next);
962
			}
963
		}
964
		throw new IllegalStateException("<Notes> has no closing tag");
965
	}
966

    
967
	/**
968
	 * @param state
969
	 * @param reader
970
	 * @param text
971
	 * @param next
972
	 * @return
973
	 * @throws XMLStreamException
974
	 */
975
	private String makeNotesString(MarkupImportState state,	XMLEventReader reader, String text, XMLEvent next) throws XMLStreamException {
976
		Map<String, String> stringMap = handleString(state, reader,	next, null);
977
		if (stringMap.size() == 0){
978
			String message = "No text available in <notes>";
979
			fireWarningEvent(message, next, 4);
980
		}else if (stringMap.size() > 1){
981
			String message = "Subheadings not yet supported in <notes>";
982
			fireWarningEvent(message, next, 4);
983
		}else{
984
			String firstSubheading = stringMap.keySet().iterator().next();
985
			if ( firstSubheading != null && ! isNoteHeading (firstSubheading) )  {
986
				String message = "Subheadings not yet supported in <notes>";
987
				fireWarningEvent(message, next, 4);
988
			}
989
		}
990
		for (String subheading : stringMap.keySet()){
991
			text += subheading;
992
			text += stringMap.get(subheading);
993
		}
994
		return text;
995
	}
996

    
997
	private boolean isNoteHeading(String heading) {
998
		String excludePattern = "(i?)(Notes?):?";
999
		return heading.matches(excludePattern);
1000
	}
1001

    
1002
	/**
1003
	 * @param state
1004
	 * @param attributes
1005
	 */
1006
	private Taxon createTaxonAndName(MarkupImportState state,
1007
			Map<String, Attribute> attributes) {
1008
		NonViralName<?> name;
1009
		Rank rank = Rank.SPECIES(); // default
1010
		boolean isCultivar = checkAndRemoveAttributeValue(attributes, CLASS,
1011
				"cultivated");
1012
		if (isCultivar) {
1013
			name = CultivarPlantName.NewInstance(rank);
1014
		} else {
1015
			name = createNameByCode(state, rank);
1016
		}
1017
		Taxon taxon = Taxon.NewInstance(name, state.getConfig()
1018
				.getSourceReference());
1019
		if (checkAndRemoveAttributeValue(attributes, CLASS, "dubious")) {
1020
			taxon.setDoubtful(true);
1021
		} else if (checkAndRemoveAttributeValue(attributes, CLASS, "excluded")) {
1022
			taxon.setExcluded(true);
1023
		}
1024
		// TODO insufficient, new, expected
1025
		handleNotYetImplementedAttribute(attributes, CLASS);
1026
		// From old version
1027
		// MarkerType markerType = getMarkerType(state, attrValue);
1028
		// if (markerType == null){
1029
		// logger.warn("Class attribute value for taxon not yet supported: " +
1030
		// attrValue);
1031
		// }else{
1032
		// taxon.addMarker(Marker.NewInstance(markerType, true));
1033
		// }
1034

    
1035
		// save(name, state);
1036
		// save(taxon, state);
1037
		return taxon;
1038
	}
1039

    
1040
	/**
1041
	 * @param state
1042
	 * @param rank
1043
	 * @return
1044
	 */
1045
	private NonViralName<?> createNameByCode(MarkupImportState state, Rank rank) {
1046
		NonViralName<?> name;
1047
		NomenclaturalCode nc = makeNomenclaturalCode(state);
1048
		name = (NonViralName<?>) nc.getNewTaxonNameInstance(rank);
1049
		return name;
1050
	}
1051

    
1052
	/**
1053
	 * @param state
1054
	 * @return
1055
	 */
1056
	private NomenclaturalCode makeNomenclaturalCode(MarkupImportState state) {
1057
		NomenclaturalCode nc = state.getConfig().getNomenclaturalCode();
1058
		if (nc == null) {
1059
			nc = NomenclaturalCode.ICBN; // default;
1060
		}
1061
		return nc;
1062
	}
1063

    
1064
	private String handleTaxonTitle(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
1065
		//attributes
1066
		String text = "";
1067
		Map<String, Attribute> attributes = getAttributes(parentEvent);
1068
		String rankAttr = getAndRemoveAttributeValue(attributes, RANK);
1069
		Rank rank = makeRank(state, rankAttr, false);
1070
		String num = getAndRemoveAttributeValue(attributes, NUM);
1071
		state.setCurrentTaxonNum(num);
1072
		checkNoAttributes(attributes, parentEvent);
1073

    
1074
		// TODO handle attributes
1075
		while (reader.hasNext()) {
1076
			XMLEvent next = readNoWhitespace(reader);
1077
			if (next.isEndElement()) {
1078
				if (isMyEndingElement(next, parentEvent)) {
1079
					Taxon taxon = state.getCurrentTaxon();
1080
					String titleText = null;
1081
					if (checkMandatoryText(text, parentEvent)) {
1082
						titleText = normalize(text);
1083
						UUID uuidTitle = MarkupTransformer.uuidTaxonTitle;
1084
						ExtensionType titleExtension = this.getExtensionType(state, uuidTitle, "Taxon Title ","taxon title", "title");
1085
						taxon.addExtension(titleText, titleExtension);
1086
					}
1087
					taxon.getName().setRank(rank);
1088
					// TODO check title exists
1089
					return titleText;
1090
				} else {
1091
					if (isEndingElement(next, FOOTNOTE)) {
1092
						// NOT YET IMPLEMENTED
1093
						popUnimplemented(next.asEndElement());
1094
					} else {
1095
						handleUnexpectedEndElement(next.asEndElement());
1096
						state.setUnsuccessfull();
1097
					}
1098
				}
1099
			} else if (next.isStartElement()) {
1100
				if (isStartingElement(next, FOOTNOTE)) {
1101
					handleNotYetImplementedElement(next);
1102
				} else {
1103
					handleUnexpectedStartElement(next);
1104
					state.setUnsuccessfull();
1105
				}
1106
			} else if (next.isCharacters()) {
1107
				text += next.asCharacters().getData();
1108

    
1109
			} else {
1110
				handleUnexpectedElement(next);
1111
				state.setUnsuccessfull();
1112
			}
1113
		}
1114
		return null;
1115

    
1116
	}
1117

    
1118
	private WriterDataHolder handleWriter(MarkupImportState state,
1119
			XMLEventReader reader, XMLEvent parentEvent)
1120
			throws XMLStreamException {
1121
		String text = "";
1122
		checkNoAttributes(parentEvent);
1123
		WriterDataHolder dataHolder = new WriterDataHolder();
1124
		List<FootnoteDataHolder> footnotes = new ArrayList<FootnoteDataHolder>();
1125

    
1126
		// TODO handle attributes
1127
		while (reader.hasNext()) {
1128
			XMLEvent next = readNoWhitespace(reader);
1129
			if (isMyEndingElement(next, parentEvent)) {
1130
				text = CdmUtils.removeBrackets(text);
1131
				if (checkMandatoryText(text, parentEvent)) {
1132
					text = normalize(text);
1133
					dataHolder.writer = text;
1134
					dataHolder.footnotes = footnotes;
1135

    
1136
					// Extension
1137
					UUID uuidWriterExtension = MarkupTransformer.uuidWriterExtension;
1138
					ExtensionType writerExtensionType = this
1139
							.getExtensionType(state, uuidWriterExtension,
1140
									"Writer", "writer", "writer");
1141
					Extension extension = Extension.NewInstance();
1142
					extension.setType(writerExtensionType);
1143
					extension.setValue(text);
1144
					dataHolder.extension = extension;
1145

    
1146
					// Annotation
1147
					UUID uuidWriterAnnotation = MarkupTransformer.uuidWriterAnnotation;
1148
					AnnotationType writerAnnotationType = this.getAnnotationType(state, uuidWriterAnnotation, "Writer", "writer", "writer", null);
1149
					Annotation annotation = Annotation.NewInstance(text, writerAnnotationType, Language.DEFAULT());
1150
					dataHolder.annotation = annotation;
1151

    
1152
					return dataHolder;
1153
				} else {
1154
					return null;
1155
				}
1156
			} else if (isStartingElement(next, FOOTNOTE_REF)) {
1157
				FootnoteDataHolder footNote = handleFootnoteRef(state, reader, next);
1158
				if (footNote.isRef()) {
1159
					footnotes.add(footNote);
1160
				} else {
1161
					logger.warn("Non ref footnotes not yet impelemnted");
1162
				}
1163
			} else if (next.isCharacters()) {
1164
				text += next.asCharacters().getData();
1165

    
1166
			} else {
1167
				handleUnexpectedElement(next);
1168
				state.setUnsuccessfull();
1169
			}
1170
		}
1171
		throw new IllegalStateException("<writer> has no end tag");
1172
	}
1173

    
1174
	private void registerFootnotes(MarkupImportState state, AnnotatableEntity entity, List<FootnoteDataHolder> footnotes) {
1175
		for (FootnoteDataHolder footNote : footnotes) {
1176
			registerFootnoteDemand(state, entity, footNote);
1177
		}
1178
	}
1179

    
1180
	private void registerGivenFootnote(MarkupImportState state, FootnoteDataHolder footnote) {
1181
		state.registerFootnote(footnote);
1182
		Set<AnnotatableEntity> demands = state.getFootnoteDemands(footnote.id);
1183
		if (demands != null) {
1184
			for (AnnotatableEntity entity : demands) {
1185
				attachFootnote(state, entity, footnote);
1186
			}
1187
		}
1188
	}
1189

    
1190
	private void registerGivenFigure(MarkupImportState state, String id, Media figure) {
1191
		state.registerFigure(id, figure);
1192
		Set<AnnotatableEntity> demands = state.getFigureDemands(id);
1193
		if (demands != null) {
1194
			for (AnnotatableEntity entity : demands) {
1195
				attachFigure(state, entity, figure);
1196
			}
1197
		}
1198
	}
1199

    
1200
	private void registerFootnoteDemand(MarkupImportState state, AnnotatableEntity entity, FootnoteDataHolder footnote) {
1201
		FootnoteDataHolder existingFootnote = state.getFootnote(footnote.ref);
1202
		if (existingFootnote != null) {
1203
			attachFootnote(state, entity, existingFootnote);
1204
		} else {
1205
			Set<AnnotatableEntity> demands = state.getFootnoteDemands(footnote.ref);
1206
			if (demands == null) {
1207
				demands = new HashSet<AnnotatableEntity>();
1208
				state.putFootnoteDemands(footnote.ref, demands);
1209
			}
1210
			demands.add(entity);
1211
		}
1212
	}
1213

    
1214
	private void registerFigureDemand(MarkupImportState state, AnnotatableEntity entity, String figureRef) {
1215
		Media existingFigure = state.getFigure(figureRef);
1216
		if (existingFigure != null) {
1217
			attachFigure(state, entity, existingFigure);
1218
		} else {
1219
			Set<AnnotatableEntity> demands = state.getFigureDemands(figureRef);
1220
			if (demands == null) {
1221
				demands = new HashSet<AnnotatableEntity>();
1222
				state.putFigureDemands(figureRef, demands);
1223
			}
1224
			demands.add(entity);
1225
		}
1226
	}
1227

    
1228
	private void attachFootnote(MarkupImportState state, AnnotatableEntity entity, FootnoteDataHolder footnote) {
1229
		AnnotationType annotationType = this.getAnnotationType(state, MarkupTransformer.uuidFootnote, "Footnote", "An e-flora footnote", "fn", null);
1230
		Annotation annotation = Annotation.NewInstance(footnote.string,
1231
				annotationType, Language.DEFAULT());
1232
		// TODO transient objects
1233
		entity.addAnnotation(annotation);
1234
		save(entity, state);
1235
	}
1236

    
1237
	private void attachFigure(MarkupImportState state,
1238
			AnnotatableEntity entity, Media figure) {
1239
		// IdentifiableEntity<?> toSave;
1240
		if (entity.isInstanceOf(TextData.class)) {
1241
			TextData deb = CdmBase.deproxy(entity, TextData.class);
1242
			deb.addMedia(figure);
1243
			// toSave = ((TaxonDescription)deb.getInDescription()).getTaxon();
1244
		} else if (entity.isInstanceOf(IdentifiableMediaEntity.class)) {
1245
			IdentifiableMediaEntity<?> ime = CdmBase.deproxy(entity,
1246
					IdentifiableMediaEntity.class);
1247
			ime.addMedia(figure);
1248
			// toSave = ime;
1249
		} else {
1250
			String message = "Unsupported entity to attach media: %s";
1251
			message = String.format(message, entity.getClass().getName());
1252
			// toSave = null;
1253
		}
1254
		save(entity, state);
1255
	}
1256

    
1257
	private void handleFigure(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
1258
		// FigureDataHolder result = new FigureDataHolder();
1259

    
1260
		Map<String, Attribute> attributes = getAttributes(parentEvent);
1261
		String id = getAndRemoveAttributeValue(attributes, ID);
1262
		String type = getAndRemoveAttributeValue(attributes, TYPE);
1263
		checkNoAttributes(attributes, parentEvent);
1264

    
1265
		String urlString = null;
1266
		String legendString = null;
1267
		String titleString = null;
1268
		String numString = null;
1269
		String text = null;
1270
		while (reader.hasNext()) {
1271
			XMLEvent next = readNoWhitespace(reader);
1272
			if (isMyEndingElement(next, parentEvent)) {
1273
				makeFigure(state, id, type, urlString, legendString, titleString, numString, next);
1274
				return;
1275
			} else if (isStartingElement(next, FIGURE_LEGEND)) {
1276
				// TODO same as figurestring ?
1277
				legendString = handleFootnoteString(state, reader, next);
1278
			} else if (isStartingElement(next, FIGURE_TITLE)) {
1279
				titleString = getCData(state, reader, next);
1280
			} else if (isStartingElement(next, URL)) {
1281
				String localUrl = getCData(state, reader, next);
1282
				urlString = CdmUtils.Nz(state.getBaseMediaUrl()) + localUrl;
1283
			} else if (isStartingElement(next, NUM)) {
1284
				numString = getCData(state, reader, next);
1285
			} else if (next.isCharacters()) {
1286
				text += next.asCharacters().getData();
1287
			} else {
1288
				fireUnexpectedEvent(next, 0);
1289
			}
1290
		}
1291
		throw new IllegalStateException("<figure> has no end tag");
1292
	}
1293

    
1294
	/**
1295
	 * @param state
1296
	 * @param id
1297
	 * @param type
1298
	 * @param urlString
1299
	 * @param legendString
1300
	 * @param titleString
1301
	 * @param numString
1302
	 * @param next
1303
	 */
1304
	private void makeFigure(MarkupImportState state, String id, String type, String urlString, 
1305
						String legendString, String titleString, String numString, XMLEvent next) {
1306
		Media media = null;
1307
		boolean isFigure = false;
1308
		try {
1309
			//TODO maybe everything is a figure as it is all taken from a book
1310
			if ("lineart".equals(type)) {
1311
				isFigure = true;
1312
//				media = Figure.NewInstance(url.toURI(), null, null,	null);
1313
			} else if (type == null || "photo".equals(type)
1314
					|| "signature".equals(type)
1315
					|| "others".equals(type)) {
1316
			} else {
1317
				String message = "Unknown figure type '%s'";
1318
				message = String.format(message, type);
1319
				fireWarningEvent(message, next, 2);
1320
			}
1321
			media = getImageMedia(urlString, READ_MEDIA_DATA, isFigure);
1322
			
1323
			// title
1324
			if (StringUtils.isNotBlank(titleString)) {
1325
				media.putTitle(Language.DEFAULT(), titleString);
1326
			}
1327
			// legend
1328
			if (StringUtils.isNotBlank(legendString)) {
1329
				media.addDescription(legendString, Language.DEFAULT());
1330
			}
1331
			if (StringUtils.isNotBlank(numString)) {
1332
				// TODO use concrete source (e.g. DAPHNIPHYLLACEAE in FM
1333
				// vol.13)
1334
				Reference<?> citation = state.getConfig().getSourceReference();
1335
				media.addSource(numString, "num", citation, null);
1336
				// TODO name used in source if available
1337
			}
1338
			// TODO which citation
1339
			if (StringUtils.isNotBlank(id)) {
1340
				media.addSource(id, null, state.getConfig()
1341
						.getSourceReference(), null);
1342
			} else {
1343
				String message = "Figure id should never be empty or null";
1344
				fireWarningEvent(message, next, 6);
1345
			}
1346
			// text
1347
			// do nothing
1348

    
1349
		} catch (MalformedURLException e) {
1350
			String message = "Media uri has incorrect syntax: %s";
1351
			message = String.format(message, urlString);
1352
			fireWarningEvent(message, next, 4);
1353
//		} catch (URISyntaxException e) {
1354
//			String message = "Media uri has incorrect syntax: %s";
1355
//			message = String.format(message, urlString);
1356
//			fireWarningEvent(message, next, 4);
1357
		}
1358

    
1359
		registerGivenFigure(state, id, media);
1360
	}
1361

    
1362
	private FigureDataHolder handleFigureRef(MarkupImportState state,
1363
			XMLEventReader reader, XMLEvent parentEvent)
1364
			throws XMLStreamException {
1365
		FigureDataHolder result = new FigureDataHolder();
1366
		Map<String, Attribute> attributes = getAttributes(parentEvent);
1367
		result.ref = getAndRemoveAttributeValue(attributes, REF);
1368
		checkNoAttributes(attributes, parentEvent);
1369

    
1370
		// text is not handled, needed only for debugging purposes
1371
		String text = "";
1372
		while (reader.hasNext()) {
1373
			XMLEvent next = readNoWhitespace(reader);
1374
			if (isMyEndingElement(next, parentEvent)) {
1375
				return result;
1376
			} else if (isStartingElement(next, NUM)) {
1377
				String num = getCData(state, reader, next);
1378
				result.num = num; // num is not handled during import
1379
			} else if (isStartingElement(next, FIGURE_PART)) {
1380
				result.figurePart = getCData(state, reader, next);
1381
			} else if (next.isCharacters()) {
1382
				text += next.asCharacters().getData();
1383
			} else {
1384
				fireUnexpectedEvent(next, 0);
1385
			}
1386
		}
1387
		throw new IllegalStateException("<figureRef> has no end tag");
1388
	}
1389

    
1390
	private FootnoteDataHolder handleFootnote(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
1391
		FootnoteDataHolder result = new FootnoteDataHolder();
1392
		Map<String, Attribute> attributes = getAttributes(parentEvent);
1393
		result.id = getAndRemoveAttributeValue(attributes, ID);
1394
		// result.ref = getAndRemoveAttributeValue(attributes, REF);
1395
		checkNoAttributes(attributes, parentEvent);
1396

    
1397
		while (reader.hasNext()) {
1398
			XMLEvent next = readNoWhitespace(reader);
1399
			if (isStartingElement(next, FOOTNOTE_STRING)) {
1400
				String string = handleFootnoteString(state, reader, next);
1401
				result.string = string;
1402
			} else if (isMyEndingElement(next, parentEvent)) {
1403
				return result;
1404
			} else {
1405
				fireUnexpectedEvent(next, 0);
1406
			}
1407
		}
1408
		return result;
1409
	}
1410

    
1411
	private FootnoteDataHolder handleFootnoteRef(MarkupImportState state,
1412
			XMLEventReader reader, XMLEvent parentEvent)
1413
			throws XMLStreamException {
1414
		FootnoteDataHolder result = new FootnoteDataHolder();
1415
		Map<String, Attribute> attributes = getAttributes(parentEvent);
1416
		result.ref = getAndRemoveAttributeValue(attributes, REF);
1417
		checkNoAttributes(attributes, parentEvent);
1418

    
1419
		// text is not handled, needed only for debugging purposes
1420
		String text = "";
1421
		while (reader.hasNext()) {
1422
			XMLEvent next = readNoWhitespace(reader);
1423
			// if (isStartingElement(next, FOOTNOTE_STRING)){
1424
			// String string = handleFootnoteString(state, reader, next);
1425
			// result.string = string;
1426
			// }else
1427
			if (isMyEndingElement(next, parentEvent)) {
1428
				return result;
1429
			} else if (next.isCharacters()) {
1430
				text += next.asCharacters().getData();
1431

    
1432
			} else {
1433
				fireUnexpectedEvent(next, 0);
1434
			}
1435
		}
1436
		return result;
1437
	}
1438

    
1439
	private void handleNomenclature(MarkupImportState state,
1440
			XMLEventReader reader, XMLEvent parentEvent)
1441
			throws XMLStreamException {
1442
		checkNoAttributes(parentEvent);
1443

    
1444
		while (reader.hasNext()) {
1445
			XMLEvent next = readNoWhitespace(reader);
1446
			if (isStartingElement(next, HOMOTYPES)) {
1447
				handleHomotypes(state, reader, next.asStartElement());
1448
			} else if (isMyEndingElement(next, parentEvent)) {
1449
				return;
1450
			} else {
1451
				fireSchemaConflictEventExpectedStartTag(HOMOTYPES, reader);
1452
				state.setUnsuccessfull();
1453
			}
1454
		}
1455
		return;
1456
	}
1457

    
1458
	private String handleFootnoteString(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
1459
		boolean isTextMode = true;
1460
		String text = "";
1461
		while (reader.hasNext()) {
1462
			XMLEvent next = readNoWhitespace(reader);
1463
			if (isMyEndingElement(next, parentEvent)) {
1464
				return text;
1465
			} else if (next.isEndElement()) {
1466
				if (isEndingElement(next, FULL_NAME)) {
1467
					popUnimplemented(next.asEndElement());
1468
				} else if (isEndingElement(next, BR)) {
1469
					isTextMode = true;
1470
				} else if (isHtml(next)) {
1471
					text += getXmlTag(next);
1472
				} else {
1473
					handleUnexpectedEndElement(next.asEndElement());
1474
				}
1475
			} else if (next.isStartElement()) {
1476
				if (isStartingElement(next, FULL_NAME)) {
1477
					handleNotYetImplementedElement(next);
1478
				} else if (isStartingElement(next, GATHERING)) {
1479
					text += handleInLineGathering(state, reader, next);
1480
				} else if (isStartingElement(next, REFERENCES)) {
1481
					text += " " + handleInLineReferences(state, reader, next)+ " ";
1482
				} else if (isStartingElement(next, BR)) {
1483
					text += "<br/>";
1484
					isTextMode = false;
1485
				} else if (isHtml(next)) {
1486
					text += getXmlTag(next);
1487
				} else {
1488
					handleUnexpectedStartElement(next.asStartElement());
1489
				}
1490
			} else if (next.isCharacters()) {
1491
				if (!isTextMode) {
1492
					String message = "footnoteString is not in text mode";
1493
					fireWarningEvent(message, next, 6);
1494
				} else {
1495
					text += next.asCharacters().getData().trim(); 
1496
					// getCData(state, reader, next); does not work as we have inner tags like <references>
1497
				}
1498
			} else {
1499
				handleUnexpectedEndElement(next.asEndElement());
1500
			}
1501
		}
1502
		throw new IllegalStateException("<footnoteString> has no closing tag");
1503

    
1504
	}
1505

    
1506
	private String handleInLineGathering(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
1507
		DerivedUnitFacade facade = DerivedUnitFacade.NewInstance(DerivedUnitType.DerivedUnit.FieldObservation);
1508
		handleGathering(state, reader, parentEvent, null, facade);
1509
		FieldObservation fieldObservation = facade.innerFieldObservation();
1510
		String result = "<cdm:specimen uuid='%s'>%s</specimen>";
1511
		result = String.format(result, fieldObservation.getUuid(), fieldObservation.getTitleCache());
1512
		save(fieldObservation, state);
1513
		return result;	
1514
	}
1515

    
1516
	private String handleInLineReferences(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
1517
		checkNoAttributes(parentEvent);
1518

    
1519
		boolean hasReference = false;
1520
		String text = "";
1521
		while (reader.hasNext()) {
1522
			XMLEvent next = readNoWhitespace(reader);
1523
			if (isMyEndingElement(next, parentEvent)) {
1524
				checkMandatoryElement(hasReference, parentEvent.asStartElement(), REFERENCE);
1525
				return text;
1526
			} else if (isStartingElement(next, REFERENCE)) {
1527
				text += handleInLineReference(state, reader, next);
1528
				hasReference = true;
1529
			} else {
1530
				handleUnexpectedElement(next);
1531
			}
1532
		}
1533
		throw new IllegalStateException("<References> has no closing tag");
1534
	}
1535

    
1536
	private String handleInLineReference(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent)throws XMLStreamException {
1537
		Reference<?> reference = handleReference(state, reader, parentEvent);
1538
		String result = "<cdm:ref uuid='%s'>%s</ref>";
1539
		result = String.format(result, reference.getUuid(), reference.getTitleCache());
1540
		save(reference, state);
1541
		return result;
1542
	}
1543

    
1544
	private Reference<?> handleReference(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent)throws XMLStreamException {
1545
		checkNoAttributes(parentEvent);
1546

    
1547
		boolean hasRefPart = false;
1548
		Map<String, String> refMap = new HashMap<String, String>();
1549
		while (reader.hasNext()) {
1550
			XMLEvent next = readNoWhitespace(reader);
1551
			if (isMyEndingElement(next, parentEvent)) {
1552
				checkMandatoryElement(hasRefPart, parentEvent.asStartElement(),
1553
						REF_PART);
1554
				Reference<?> reference = createReference(state, refMap, next);
1555
				return reference;
1556
			} else if (isStartingElement(next, REF_PART)) {
1557
				handleRefPart(state, reader, next, refMap);
1558
				hasRefPart = true;
1559
			} else {
1560
				handleUnexpectedElement(next);
1561
			}
1562
		}
1563
		// TODO handle missing end element
1564
		throw new IllegalStateException("<Reference> has no closing tag");
1565
	}
1566

    
1567
	private void handleHomotypes(MarkupImportState state,
1568
			XMLEventReader reader, StartElement parentEvent)
1569
			throws XMLStreamException {
1570
		checkNoAttributes(parentEvent);
1571

    
1572
		HomotypicalGroup homotypicalGroup = null;
1573

    
1574
		boolean hasNom = false;
1575
		while (reader.hasNext()) {
1576
			XMLEvent next = readNoWhitespace(reader);
1577
			if (next.isEndElement()) {
1578
				if (isMyEndingElement(next, parentEvent)) {
1579
					checkMandatoryElement(hasNom, parentEvent, NOM);
1580
					return;
1581
				} else {
1582
					if (isEndingElement(next, NAME_TYPE)) {
1583
						state.setNameType(false);
1584
					} else if (isEndingElement(next, NOTES)) {
1585
						// NOT YET IMPLEMENTED
1586
						popUnimplemented(next.asEndElement());
1587
					} else {
1588
						handleUnexpectedEndElement(next.asEndElement());
1589
					}
1590
				}
1591
			} else if (next.isStartElement()) {
1592
				if (isStartingElement(next, NOM)) {
1593
					NonViralName<?> name = handleNom(state, reader, next,
1594
							homotypicalGroup);
1595
					homotypicalGroup = name.getHomotypicalGroup();
1596
					hasNom = true;
1597
				} else if (isStartingElement(next, NAME_TYPE)) {
1598
					state.setNameType(true);
1599
					handleNameType(state, reader, next, homotypicalGroup);
1600
				} else if (isStartingElement(next, SPECIMEN_TYPE)) {
1601
					handleSpecimenType(state, reader, next, homotypicalGroup);
1602
				} else if (isStartingElement(next, NOTES)) {
1603
					handleNotYetImplementedElement(next);
1604
				} else {
1605
					handleUnexpectedStartElement(next);
1606
				}
1607
			} else {
1608
				handleUnexpectedElement(next);
1609
			}
1610
		}
1611
		// TODO handle missing end element
1612
		throw new IllegalStateException("Homotypes has no closing tag");
1613

    
1614
	}
1615

    
1616
	private void handleNameType(MarkupImportState state, XMLEventReader reader,
1617
			XMLEvent parentEvent, HomotypicalGroup homotypicalGroup)
1618
			throws XMLStreamException {
1619
		Map<String, Attribute> attributes = getAttributes(parentEvent);
1620
		String typeStatus = getAndRemoveAttributeValue(attributes, TYPE_STATUS);
1621
		checkNoAttributes(attributes, parentEvent);
1622

    
1623
		NameTypeDesignationStatus status;
1624
		try {
1625
			status = NameTypeParser.parseNameTypeStatus(typeStatus);
1626
		} catch (UnknownCdmTypeException e) {
1627
			String message = "Type status could not be recognized: %s";
1628
			message = String.format(message, typeStatus);
1629
			fireWarningEvent(message, parentEvent, 4);
1630
			status = null;
1631
		}
1632

    
1633
		boolean hasNom = false;
1634
		while (reader.hasNext()) {
1635
			XMLEvent next = readNoWhitespace(reader);
1636
			if (next.isEndElement()) {
1637
				if (isMyEndingElement(next, parentEvent)) {
1638
					checkMandatoryElement(hasNom, parentEvent.asStartElement(),
1639
							NOM);
1640
					state.setNameType(false);
1641
					return;
1642
				} else {
1643
					if (isEndingElement(next, ACCEPTED_NAME)) {
1644
						// NOT YET IMPLEMENTED
1645
						popUnimplemented(next.asEndElement());
1646
					} else {
1647
						handleUnexpectedEndElement(next.asEndElement());
1648
					}
1649
				}
1650
			} else if (next.isStartElement()) {
1651
				if (isStartingElement(next, NOM)) {
1652
					// TODO should we check if the type is always a species, is
1653
					// this a rule?
1654
					NonViralName<?> speciesName = handleNom(state, reader,
1655
							next, null);
1656
					for (TaxonNameBase<?, ?> name : homotypicalGroup
1657
							.getTypifiedNames()) {
1658
						name.addNameTypeDesignation(speciesName, null, null,
1659
								null, status, false, false, false, false);
1660
					}
1661
					hasNom = true;
1662
				} else if (isStartingElement(next, ACCEPTED_NAME)) {
1663
					handleNotYetImplementedElement(next);
1664
				} else {
1665
					handleUnexpectedStartElement(next);
1666
				}
1667
			} else {
1668
				handleUnexpectedElement(next);
1669
			}
1670
		}
1671
		// TODO handle missing end element
1672
		throw new IllegalStateException("Homotypes has no closing tag");
1673

    
1674
	}
1675

    
1676
	private void handleSpecimenType(MarkupImportState state,
1677
			XMLEventReader reader, XMLEvent parentEvent,
1678
			HomotypicalGroup homotypicalGroup) throws XMLStreamException {
1679
		// attributes
1680
		Map<String, Attribute> attributes = getAttributes(parentEvent);
1681
		String typeStatus = getAndRemoveAttributeValue(attributes, TYPE_STATUS);
1682
		String notSeen = getAndRemoveAttributeValue(attributes, NOT_SEEN);
1683
		String unknown = getAndRemoveAttributeValue(attributes, UNKNOWN);
1684
		String notFound = getAndRemoveAttributeValue(attributes, NOT_FOUND);
1685
		String destroyed = getAndRemoveAttributeValue(attributes, DESTROYED);
1686
		String lost = getAndRemoveAttributeValue(attributes, LOST);
1687
		checkNoAttributes(attributes, parentEvent);
1688
		if (StringUtils.isNotEmpty(typeStatus)) {
1689
			// TODO
1690
			// currently not needed
1691
		} else if (StringUtils.isNotEmpty(notSeen)) {
1692
			handleNotYetImplementedAttribute(attributes, NOT_SEEN);
1693
		} else if (StringUtils.isNotEmpty(unknown)) {
1694
			handleNotYetImplementedAttribute(attributes, UNKNOWN);
1695
		} else if (StringUtils.isNotEmpty(notFound)) {
1696
			handleNotYetImplementedAttribute(attributes, NOT_FOUND);
1697
		} else if (StringUtils.isNotEmpty(destroyed)) {
1698
			handleNotYetImplementedAttribute(attributes, DESTROYED);
1699
		} else if (StringUtils.isNotEmpty(lost)) {
1700
			handleNotYetImplementedAttribute(attributes, LOST);
1701
		}
1702

    
1703
		NonViralName<?> firstName = null;
1704
		Set<TaxonNameBase> names = homotypicalGroup.getTypifiedNames();
1705
		if (names.isEmpty()) {
1706
			String message = "There is no name in a homotypical group. Can't create the specimen type";
1707
			fireWarningEvent(message, parentEvent, 8);
1708
		} else {
1709
			firstName = CdmBase.deproxy(names.iterator().next(),
1710
					NonViralName.class);
1711
		}
1712

    
1713
		DerivedUnitFacade facade = DerivedUnitFacade
1714
				.NewInstance(DerivedUnitType.Specimen);
1715
		String text = "";
1716
		// elements
1717
		while (reader.hasNext()) {
1718
			XMLEvent next = readNoWhitespace(reader);
1719
			if (next.isEndElement()) {
1720
				if (isMyEndingElement(next, parentEvent)) {
1721
					makeSpecimenType(state, facade, text, firstName,
1722
							parentEvent);
1723
					return;
1724
				} else {
1725
					if (isEndingElement(next, FULL_TYPE)) {
1726
						// NOT YET IMPLEMENTED
1727
						popUnimplemented(next.asEndElement());
1728
					} else if (isEndingElement(next, TYPE_STATUS)) {
1729
						// NOT YET IMPLEMENTED
1730
						popUnimplemented(next.asEndElement());
1731
					} else if (isEndingElement(next, ORIGINAL_DETERMINATION)) {
1732
						// NOT YET IMPLEMENTED
1733
						popUnimplemented(next.asEndElement());
1734
					} else if (isEndingElement(next, SPECIMEN_TYPE)) {
1735
						// NOT YET IMPLEMENTED
1736
						popUnimplemented(next.asEndElement());
1737
					} else if (isEndingElement(next, CITATION)) {
1738
						// NOT YET IMPLEMENTED
1739
						popUnimplemented(next.asEndElement());
1740
					} else if (isEndingElement(next, NOTES)) {
1741
						// NOT YET IMPLEMENTED
1742
						popUnimplemented(next.asEndElement());
1743
					} else if (isEndingElement(next, ANNOTATION)) {
1744
						// NOT YET IMPLEMENTED
1745
						popUnimplemented(next.asEndElement());
1746
					} else {
1747
						handleUnexpectedEndElement(next.asEndElement());
1748
					}
1749
				}
1750
			} else if (next.isStartElement()) {
1751
				if (isStartingElement(next, FULL_TYPE)) {
1752
					handleNotYetImplementedElement(next);
1753
					// homotypicalGroup = handleNom(state, reader, next, taxon,
1754
					// homotypicalGroup);
1755
				} else if (isStartingElement(next, TYPE_STATUS)) {
1756
					handleNotYetImplementedElement(next);
1757
				} else if (isStartingElement(next, GATHERING)) {
1758
					handleGathering(state, reader, next, homotypicalGroup, facade);
1759
				} else if (isStartingElement(next, ORIGINAL_DETERMINATION)) {
1760
					handleNotYetImplementedElement(next);
1761
				} else if (isStartingElement(next, SPECIMEN_TYPE)) {
1762
					handleNotYetImplementedElement(next);
1763
				} else if (isStartingElement(next, NOTES)) {
1764
					handleNotYetImplementedElement(next);
1765
				} else if (isStartingElement(next, ANNOTATION)) {
1766
					handleNotYetImplementedElement(next);
1767
				} else {
1768
					handleUnexpectedStartElement(next);
1769
				}
1770
			} else if (next.isCharacters()) {
1771
				text += next.asCharacters().getData();
1772
			} else {
1773
				handleUnexpectedElement(next);
1774
			}
1775
		}
1776
		// TODO handle missing end element
1777
		throw new IllegalStateException("Specimen type has no closing tag"); 
1778
	}
1779

    
1780
	private void makeSpecimenType(MarkupImportState state,
1781
			DerivedUnitFacade facade, String text, NonViralName name,
1782
			XMLEvent parentEvent) {
1783
		text = text.trim();
1784
		// remove brackets
1785
		if (text.matches("^\\(.*\\)\\.?$")) {
1786
			text = text.replaceAll("\\.", "");
1787
			text = text.substring(1, text.length() - 1);
1788
		}
1789
		String[] split = text.split("[;,]");
1790
		for (String str : split) {
1791
			str = str.trim();
1792
			boolean addToAllNamesInGroup = true;
1793
			TypeInfo typeInfo = makeSpecimenTypeTypeInfo(str, parentEvent);
1794
			SpecimenTypeDesignationStatus typeStatus = typeInfo.status;
1795
			Collection collection = createCollection(typeInfo.collectionString);
1796

    
1797
			// TODO improve cache strategy handling
1798
			DerivedUnitBase typeSpecimen = facade.addDuplicate(collection,
1799
					null, null, null, null);
1800
			typeSpecimen.setCacheStrategy(new DerivedUnitFacadeCacheStrategy());
1801
			name.addSpecimenTypeDesignation((Specimen) typeSpecimen, typeStatus, null, null, null, false, addToAllNamesInGroup);
1802
		}
1803
	}
1804

    
1805
	private Collection createCollection(String code) {
1806
		// TODO deduplicate
1807
		// TODO code <-> name
1808
		Collection result = Collection.NewInstance();
1809
		result.setCode(code);
1810
		return result;
1811
	}
1812

    
1813
	private TypeInfo makeSpecimenTypeTypeInfo(String originalString,
1814
			XMLEvent event) {
1815
		TypeInfo result = new TypeInfo();
1816
		String[] split = originalString.split("\\s+");
1817
		for (String str : split) {
1818
			if (str.matches(SpecimenTypeParser.typeTypePattern)) {
1819
				SpecimenTypeDesignationStatus status;
1820
				try {
1821
					status = SpecimenTypeParser.parseSpecimenTypeStatus(str);
1822
				} catch (UnknownCdmTypeException e) {
1823
					String message = "Specimen type status '%s' not recognized by parser";
1824
					message = String.format(message, str);
1825
					fireWarningEvent(message, event, 4);
1826
					status = null;
1827
				}
1828
				result.status = status;
1829
			} else if (str.matches(SpecimenTypeParser.collectionPattern)) {
1830
				result.collectionString = str;
1831
			} else {
1832
				String message = "Type part '%s' could not be recognized";
1833
				message = String.format(message, str);
1834
				fireWarningEvent(message, event, 2);
1835
			}
1836
		}
1837

    
1838
		return result;
1839
	}
1840

    
1841
	
1842
	private void handleGathering(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, HomotypicalGroup homotypicalGroup, DerivedUnitFacade facade) throws XMLStreamException {
1843
		checkNoAttributes(parentEvent);
1844
		boolean hasCollector = false;
1845
		boolean hasFieldNum = false;
1846

    
1847
		// elements
1848
		while (reader.hasNext()) {
1849
			XMLEvent next = readNoWhitespace(reader);
1850
			if (next.isEndElement()) {
1851
				if (isMyEndingElement(next, parentEvent)) {
1852
					checkMandatoryElement(hasCollector,
1853
							parentEvent.asStartElement(), COLLECTOR);
1854
					checkMandatoryElement(hasFieldNum,
1855
							parentEvent.asStartElement(), FIELD_NUM);
1856
					return;
1857
				} else {
1858
					if (isEndingElement(next, ALTERNATIVE_COLLECTOR)) {
1859
						// NOT YET IMPLEMENTED
1860
						popUnimplemented(next.asEndElement());
1861
					} else if (isEndingElement(next, ALTERNATIVE_FIELD_NUM)) {
1862
						// NOT YET IMPLEMENTED
1863
						popUnimplemented(next.asEndElement());
1864
					} else if (isEndingElement(next, COLLECTION_TYPE_STATUS)) {
1865
						// NOT YET IMPLEMENTED
1866
						popUnimplemented(next.asEndElement());
1867
					} else if (isEndingElement(next,
1868
							ALTERNATIVE_COLLECTION_TYPE_STATUS)) {
1869
						// NOT YET IMPLEMENTED
1870
						popUnimplemented(next.asEndElement());
1871
					} else if (isEndingElement(next, SUB_COLLECTION)) {
1872
						// NOT YET IMPLEMENTED
1873
						popUnimplemented(next.asEndElement());
1874
					} else if (isEndingElement(next, DATES)) {
1875
						// NOT YET IMPLEMENTED
1876
						popUnimplemented(next.asEndElement());
1877
					} else if (isEndingElement(next, NOTES)) {
1878
						// NOT YET IMPLEMENTED
1879
						popUnimplemented(next.asEndElement());
1880
					} else {
1881
						handleUnexpectedEndElement(next.asEndElement());
1882
					}
1883
				}
1884
			} else if (next.isStartElement()) {
1885
				if (isStartingElement(next, COLLECTOR)) {
1886
					hasCollector = true;
1887
					String collectorStr = getCData(state, reader, next);
1888
					AgentBase<?> collector = createCollector(collectorStr);
1889
					facade.setCollector(collector);
1890
				} else if (isStartingElement(next, ALTERNATIVE_COLLECTOR)) {
1891
					handleNotYetImplementedElement(next);
1892
				} else if (isStartingElement(next, FIELD_NUM)) {
1893
					hasFieldNum = true;
1894
					String fieldNumStr = getCData(state, reader, next);
1895
					facade.setFieldNumber(fieldNumStr);
1896
				} else if (isStartingElement(next, ALTERNATIVE_FIELD_NUM)) {
1897
					handleNotYetImplementedElement(next);
1898
				} else if (isStartingElement(next, COLLECTION_TYPE_STATUS)) {
1899
					handleNotYetImplementedElement(next);
1900
				} else if (isStartingElement(next,
1901
						ALTERNATIVE_COLLECTION_TYPE_STATUS)) {
1902
					handleNotYetImplementedElement(next);
1903
				} else if (isStartingElement(next, SUB_COLLECTION)) {
1904
					handleNotYetImplementedElement(next);
1905
				} else if (isStartingElement(next, LOCALITY)) {
1906
					handleLocality(state, reader, next, facade);
1907
				} else if (isStartingElement(next, DATES)) {
1908
					handleNotYetImplementedElement(next);
1909
				} else if (isStartingElement(next, NOTES)) {
1910
					handleNotYetImplementedElement(next);
1911
				} else {
1912
					handleUnexpectedStartElement(next);
1913
				}
1914
			} else {
1915
				handleUnexpectedElement(next);
1916
			}
1917
		}
1918
		// TODO handle missing end element
1919
		throw new IllegalStateException("Collection has no closing tag");
1920

    
1921
	}
1922

    
1923
	private void handleLocality(MarkupImportState state, XMLEventReader reader,XMLEvent parentEvent, DerivedUnitFacade facade)throws XMLStreamException {
1924
		String classValue = getClassOnlyAttribute(parentEvent);
1925
		boolean isLocality = false;
1926
		NamedAreaLevel areaLevel = null;
1927
		if ("locality".equalsIgnoreCase(classValue)) {
1928
			isLocality = true;
1929
		} else {
1930
			areaLevel = makeNamedAreaLevel(state, classValue, parentEvent);
1931
		}
1932

    
1933
		String text = "";
1934
		// elements
1935
		while (reader.hasNext()) {
1936
			XMLEvent next = readNoWhitespace(reader);
1937
			if (next.isEndElement()) {
1938
				if (StringUtils.isNotBlank(text)) {
1939
					if (isMyEndingElement(next, parentEvent)) {
1940
						if (isLocality) {
1941
							facade.setLocality(text);
1942
						} else {
1943
							NamedArea area = createArea(text, areaLevel, state);
1944
							facade.addCollectingArea(area);
1945
						}
1946
					}
1947
					// TODO
1948
					return;
1949
				} else {
1950
					if (isEndingElement(next, ALTITUDE)) {
1951
						// NOT YET IMPLEMENTED
1952
						popUnimplemented(next.asEndElement());
1953
					} else if (isEndingElement(next, COORDINATES)) {
1954
						// NOT YET IMPLEMENTED
1955
						popUnimplemented(next.asEndElement());
1956
					} else if (isEndingElement(next, ANNOTATION)) {
1957
						// NOT YET IMPLEMENTED
1958
						popUnimplemented(next.asEndElement());
1959
					} else {
1960
						handleUnexpectedEndElement(next.asEndElement());
1961
					}
1962
				}
1963
			} else if (next.isStartElement()) {
1964
				if (isStartingElement(next, ALTITUDE)) {
1965
					handleNotYetImplementedElement(next);
1966
					// homotypicalGroup = handleNom(state, reader, next, taxon,
1967
					// homotypicalGroup);
1968
				} else if (isStartingElement(next, COORDINATES)) {
1969
					handleNotYetImplementedElement(next);
1970
				} else if (isStartingElement(next, ANNOTATION)) {
1971
					handleNotYetImplementedElement(next);
1972
				} else {
1973
					handleUnexpectedStartElement(next);
1974
				}
1975
			} else if (next.isCharacters()) {
1976
				text += next.asCharacters().getData();
1977
			} else {
1978
				handleUnexpectedElement(next);
1979
			}
1980
		}
1981
		// TODO handle missing end element
1982
		throw new IllegalStateException("Specimen type has no closing tag"); // TODO
1983
																				// Auto-generated
1984
																				// method
1985
																				// stub
1986

    
1987
	}
1988

    
1989
	private NamedArea createArea(String text, NamedAreaLevel areaLevel, MarkupImportState state) {
1990
		NamedArea area = NamedArea.NewInstance(text, text, null);
1991
		area.setLevel(areaLevel);
1992
		save(area, state);
1993
		return area;
1994
	}
1995

    
1996
	private AgentBase<?> createCollector(String collectorStr) {
1997
		return createAuthor(collectorStr);
1998
	}
1999

    
2000
	private String getCData(MarkupImportState state, XMLEventReader reader, XMLEvent next) throws XMLStreamException {
2001
		return getCData(state, reader, next, true);
2002
	}
2003
		
2004
	/**
2005
	 * Reads character data. Any element other than character data or the ending
2006
	 * tag will fire an unexpected element event.
2007
	 * 
2008
	 * @param state
2009
	 * @param reader
2010
	 * @param next
2011
	 * @return
2012
	 * @throws XMLStreamException
2013
	 */
2014
	private String getCData(MarkupImportState state, XMLEventReader reader, XMLEvent next,boolean checkAttributes) throws XMLStreamException {
2015
		if (checkAttributes){
2016
			checkNoAttributes(next);
2017
		}
2018

    
2019
		String text = "";
2020
		while (reader.hasNext()) {
2021
			XMLEvent myNext = readNoWhitespace(reader);
2022
			if (isMyEndingElement(myNext, next)) {
2023
				return text;
2024
			} else if (myNext.isCharacters()) {
2025
				text += myNext.asCharacters().getData();
2026
			} else {
2027
				handleUnexpectedElement(myNext);
2028
			}
2029
		}
2030
		throw new IllegalStateException("Event has no closing tag");
2031

    
2032
	}
2033

    
2034
	/**
2035
	 * Creates the name defined by a nom tag. Adds it to the given homotypical
2036
	 * group (if not null).
2037
	 * 
2038
	 * @param state
2039
	 * @param reader
2040
	 * @param parentEvent
2041
	 * @param homotypicalGroup
2042
	 * @return
2043
	 * @throws XMLStreamException
2044
	 */
2045
	private NonViralName<?> handleNom(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent,
2046
			HomotypicalGroup homotypicalGroup) throws XMLStreamException {
2047
		boolean isSynonym = false;
2048
		boolean isNameType = state.isNameType();
2049
		// attributes
2050
		String classValue = getClassOnlyAttribute(parentEvent);
2051
		NonViralName<?> name;
2052
		if (!isNameType && ACCEPTED.equalsIgnoreCase(classValue)) {
2053
			isSynonym = false;
2054
			name = createName(state, homotypicalGroup, isSynonym);
2055
		} else if (!isNameType && SYNONYM.equalsIgnoreCase(classValue)) {
2056
			isSynonym = true;
2057
			name = createName(state, homotypicalGroup, isSynonym);
2058
		} else if (isNameType && NAME_TYPE.equalsIgnoreCase(classValue)) {
2059
			// TODO do we need to define the rank here?
2060
			name = createNameByCode(state, null);
2061
		} else {
2062
			fireUnexpectedAttributeValue(parentEvent, CLASS, classValue);
2063
			name = createNameByCode(state, null);
2064
		}
2065

    
2066
		Map<String, String> nameMap = new HashMap<String, String>();
2067

    
2068
		while (reader.hasNext()) {
2069
			XMLEvent next = readNoWhitespace(reader);
2070
			if (next.isEndElement()) {
2071
				if (isMyEndingElement(next, parentEvent)) {
2072
					// fill the name with all data gathered
2073
					fillName(state, nameMap, name, next);
2074
					return name;
2075
				} else {
2076
					if (isEndingElement(next, FULL_NAME)) {
2077
						// NOT YET IMPLEMENTED
2078
						popUnimplemented(next.asEndElement());
2079
					} else if (isEndingElement(next, NUM)) {
2080
						// NOT YET IMPLEMENTED
2081
						popUnimplemented(next.asEndElement());
2082
					} else if (isEndingElement(next, HOMONYM)) {
2083
						// NOT YET IMPLEMENTED
2084
						popUnimplemented(next.asEndElement());
2085
					} else if (isEndingElement(next, NOTES)) {
2086
						// NOT YET IMPLEMENTED
2087
						popUnimplemented(next.asEndElement());
2088
					} else if (isEndingElement(next, ANNOTATION)) {
2089
						// NOT YET IMPLEMENTED
2090
						popUnimplemented(next.asEndElement());
2091
					} else {
2092
						handleUnexpectedEndElement(next.asEndElement());
2093
					}
2094
				}
2095
			} else if (next.isStartElement()) {
2096
				if (isStartingElement(next, FULL_NAME)) {
2097
					handleNotYetImplementedElement(next);
2098
					// homotypicalGroup = handleNom(state, reader, next, taxon,
2099
					// homotypicalGroup);
2100
				} else if (isStartingElement(next, NUM)) {
2101
					handleNotYetImplementedElement(next);
2102
				} else if (isStartingElement(next, NAME)) {
2103
					handleName(state, reader, next, nameMap);
2104
				} else if (isStartingElement(next, CITATION)) {
2105
					handleCitation(state, reader, next, name);
2106
				} else if (isStartingElement(next, HOMONYM)) {
2107
					handleNotYetImplementedElement(next);
2108
				} else if (isStartingElement(next, NOTES)) {
2109
					handleNotYetImplementedElement(next);
2110
				} else if (isStartingElement(next, ANNOTATION)) {
2111
					handleNotYetImplementedElement(next);
2112
				} else {
2113
					handleUnexpectedStartElement(next);
2114
				}
2115
			} else {
2116
				handleUnexpectedElement(next);
2117
			}
2118
		}
2119
		// TODO handle missing end element
2120
		throw new IllegalStateException("Nom has no closing tag");
2121

    
2122
	}
2123

    
2124
	private void fillName(MarkupImportState state, Map<String, String> nameMap,
2125
			NonViralName name, XMLEvent event) {
2126

    
2127
		// Ranks: family, subfamily, tribus, genus, subgenus, section,
2128
		// subsection, species, subspecies, variety, subvariety, forma
2129
		// infrank, paraut, author, infrparaut, infraut, status, notes
2130

    
2131
		String infrank = getAndRemoveMapKey(nameMap, INFRANK);
2132
		String authorStr = getAndRemoveMapKey(nameMap, AUTHOR);
2133
		String paraut = getAndRemoveMapKey(nameMap, PARAUT);
2134

    
2135
		String infrParAut = getAndRemoveMapKey(nameMap, INFRPARAUT);
2136
		String infrAut = getAndRemoveMapKey(nameMap, INFRAUT);
2137

    
2138
		String statusStr = getAndRemoveMapKey(nameMap, STATUS);
2139
		String notes = getAndRemoveMapKey(nameMap, NOTES);
2140

    
2141
		makeRankDecision(state, nameMap, name, event, infrank);
2142

    
2143
		// test consistency of rank and authors
2144
		testRankAuthorConsistency(name, event, authorStr, paraut, infrParAut,infrAut);
2145

    
2146
		// authors
2147
		makeNomenclaturalAuthors(name, event, authorStr, paraut, infrParAut,infrAut);
2148

    
2149
		// status
2150
		// TODO handle pro parte, pro syn. etc.
2151
		if (StringUtils.isNotBlank(statusStr)) {
2152
			String proPartePattern = "(pro parte|p.p.)";
2153
			if (statusStr.matches(proPartePattern)) {
2154
				state.setProParte(true);
2155
			}
2156
			try {
2157
				// TODO handle trim earlier
2158
				statusStr = statusStr.trim();
2159
				NomenclaturalStatusType nomStatusType = NomenclaturalStatusType
2160
						.getNomenclaturalStatusTypeByAbbreviation(statusStr);
2161
				name.addStatus(NomenclaturalStatus.NewInstance(nomStatusType));
2162
			} catch (UnknownCdmTypeException e) {
2163
				String message = "Status '%s' could not be recognized";
2164
				message = String.format(message, statusStr);
2165
				fireWarningEvent(message, event, 4);
2166
			}
2167
		}
2168

    
2169
		// notes
2170
		if (StringUtils.isNotBlank(notes)) {
2171
			handleNotYetImplementedAttributeValue(event, CLASS, NOTES);
2172
		}
2173

    
2174
		return;
2175
	}
2176

    
2177
	/**
2178
	 * @param state
2179
	 * @param nameMap
2180
	 * @param name
2181
	 * @param event
2182
	 * @param infrankStr
2183
	 */
2184
	private void makeRankDecision(MarkupImportState state,
2185
			Map<String, String> nameMap, NonViralName<?> name, XMLEvent event,
2186
			String infrankStr) {
2187
		// TODO ranks
2188
		for (String key : nameMap.keySet()) {
2189
			Rank rank = makeRank(state, key, false);
2190
			if (rank == null) {
2191
				handleNotYetImplementedAttributeValue(event, CLASS, key);
2192
			} else {
2193
				if (name.getRank() == null || rank.isLower(name.getRank())) {
2194
					name.setRank(rank);
2195
				}
2196
				String value = nameMap.get(key);
2197
				if (rank.isSupraGeneric() || rank.isGenus()) {
2198
					name.setGenusOrUninomial(value);
2199
				} else if (rank.isInfraGeneric()) {
2200
					name.setInfraGenericEpithet(value);
2201
				} else if (rank.isSpecies()) {
2202
					name.setSpecificEpithet(value);
2203
				} else if (rank.isInfraSpecific()) {
2204
					name.setInfraSpecificEpithet(value);
2205
				} else {
2206
					String message = "Invalid rank '%s'. Can't decide which epithet to fill with '%s'";
2207
					message = String.format(message, rank.getTitleCache(),value);
2208
					fireWarningEvent(message, event, 4);
2209
				}
2210
			}
2211

    
2212
		}
2213
		// handle given infrank marker
2214
		if (StringUtils.isNotBlank(infrankStr)) {
2215
			Rank infRank = makeRank(state, infrankStr, true);
2216

    
2217
			if (infRank == null) {
2218
				String message = "Infrank '%s' rank not recognized";
2219
				message = String.format(message, infrankStr);
2220
				fireWarningEvent(message, event, 4);
2221
			} else {
2222
				if (name.getRank() == null) {
2223
					name.setRank(infRank);
2224
				} else if (infRank.isLower(name.getRank())) {
2225
					String message = "InfRank '%s' is lower than existing rank ";
2226
					message = String.format(message, infrankStr);
2227
					fireWarningEvent(message, event, 2);
2228
					name.setRank(infRank);
2229
				} else if (infRank.equals(name.getRank())) {
2230
					// nothing
2231
				} else {
2232
					String message = "InfRank '%s' is higher than existing rank ";
2233
					message = String.format(message, infrankStr);
2234
					fireWarningEvent(message, event, 2);
2235
				}
2236
			}
2237
		}
2238
	}
2239

    
2240
	/**
2241
	 * @param name
2242
	 * @param event
2243
	 * @param authorStr
2244
	 * @param paraut
2245
	 * @param infrParAut
2246
	 * @param infrAut
2247
	 */
2248
	private void makeNomenclaturalAuthors(NonViralName name, XMLEvent event,
2249
				String authorStr, String paraut, String infrParAut, String infrAut) {
2250
		if (name.getRank() != null && name.getRank().isInfraSpecific()) {
2251
			if (StringUtils.isNotBlank(infrAut)) {
2252
				INomenclaturalAuthor[] authorAndEx = authorAndEx(infrAut, event);
2253
				name.setCombinationAuthorTeam(authorAndEx[0]);
2254
				name.setExCombinationAuthorTeam(authorAndEx[1]);
2255
			}
2256
			if (StringUtils.isNotBlank(infrParAut)) {
2257
				INomenclaturalAuthor[] authorAndEx = authorAndEx(infrParAut, event);
2258
				name.setBasionymAuthorTeam(authorAndEx[0]);
2259
				name.setExBasionymAuthorTeam(authorAndEx[1]);
2260
			}
2261
		} else {
2262
			if (name.getRank() == null){
2263
				String message = "No rank defined. Check correct usage of authors!";
2264
				fireWarningEvent(message, event, 4);
2265
				if (isNotBlank(infrParAut) || isNotBlank(infrAut)){
2266
					authorStr = infrAut;
2267
					paraut = infrParAut;
2268
				}
2269
			}
2270
			if (StringUtils.isNotBlank(authorStr)) {
2271
				INomenclaturalAuthor[] authorAndEx = authorAndEx(authorStr, event);
2272
				name.setCombinationAuthorTeam(authorAndEx[0]);
2273
				name.setExCombinationAuthorTeam(authorAndEx[1]);
2274
			}
2275
			if (StringUtils.isNotBlank(paraut)) {
2276
				INomenclaturalAuthor[] authorAndEx = authorAndEx(paraut, event);
2277
				name.setBasionymAuthorTeam(authorAndEx[0]);
2278
				name.setExBasionymAuthorTeam(authorAndEx[1]);
2279
			}
2280
		}
2281
	}
2282

    
2283
	private TeamOrPersonBase[] authorAndEx(String authorAndEx, XMLEvent xmlEvent) {
2284
		authorAndEx = authorAndEx.trim();
2285
		TeamOrPersonBase[] result = new TeamOrPersonBase[2];
2286

    
2287
		String[] split = authorAndEx.split("\\sex\\s");
2288
		if (split.length > 2) {
2289
			String message = "There is more then 1 ' ex ' in author string. Can't separate author and ex-author";
2290
			fireWarningEvent(message, xmlEvent, 4);
2291
			result[0] = createAuthor(authorAndEx);
2292
		} else if (split.length == 2) {
2293
			result[0] = createAuthor(split[1]);
2294
			result[1] = createAuthor(split[0]);
2295
		} else {
2296
			result[0] = createAuthor(split[0]);
2297
		}
2298
		return result;
2299
	}
2300

    
2301
	/**
2302
	 * Tests if the names rank is consistent with the given author strings.
2303
	 * @param name
2304
	 * @param event
2305
	 * @param authorStr
2306
	 * @param paraut
2307
	 * @param infrParAut
2308
	 * @param infrAut
2309
	 */
2310
	private void testRankAuthorConsistency(NonViralName name, XMLEvent event,
2311
					String authorStr, String paraut, String infrParAut, String infrAut) {
2312
		if (name.getRank() == null){
2313
			return;
2314
		}
2315
		if (name.getRank().isInfraSpecific()) {
2316
			if (StringUtils.isBlank(infrParAut)
2317
					&& StringUtils.isNotBlank(infrAut)
2318
					&& (StringUtils.isNotBlank(paraut) || StringUtils.isNotBlank(authorStr))) {
2319
				String message = "Rank is infraspecicific but has only specific or higher author(s)";
2320
				fireWarningEvent(message, event, 4);
2321
			}
2322
		} else {
2323
			// is not infraspecific
2324
			if (StringUtils.isNotBlank(infrParAut) 	|| StringUtils.isNotBlank(infrAut)) {
2325
				String message = "Rank is not infraspecicific but name has infra author(s)";
2326
				fireWarningEvent(message, event, 4);
2327
			}
2328
		}
2329
	}
2330

    
2331
	/**
2332
	 * Returns the (empty) name with the correct homotypical group depending on
2333
	 * the taxon status. Throws NPE if no currentTaxon is set in state.
2334
	 * 
2335
	 * @param state
2336
	 * @param homotypicalGroup
2337
	 * @param isSynonym
2338
	 * @return
2339
	 */
2340
	private NonViralName<?> createName(MarkupImportState state,
2341
			HomotypicalGroup homotypicalGroup, boolean isSynonym) {
2342
		NonViralName<?> name;
2343
		Taxon taxon = state.getCurrentTaxon();
2344
		if (isSynonym) {
2345
			Rank defaultRank = Rank.SPECIES(); // can be any
2346
			name = createNameByCode(state, defaultRank);
2347
			if (homotypicalGroup != null) {
2348
				name.setHomotypicalGroup(homotypicalGroup);
2349
			}
2350
			SynonymRelationshipType synonymType = SynonymRelationshipType
2351
					.HETEROTYPIC_SYNONYM_OF();
2352
			if (taxon.getHomotypicGroup().equals(homotypicalGroup)) {
2353
				synonymType = SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF();
2354
			}
2355
			taxon.addSynonymName(name, synonymType);
2356
		} else {
2357
			name = CdmBase.deproxy(taxon.getName(), NonViralName.class);
2358
		}
2359
		return name;
2360
	}
2361

    
2362
	private void handleName(MarkupImportState state, XMLEventReader reader,
2363
			XMLEvent parentEvent, Map<String, String> nameMap)
2364
			throws XMLStreamException {
2365
		String classValue = getClassOnlyAttribute(parentEvent);
2366

    
2367
		String text = "";
2368
		while (reader.hasNext()) {
2369
			XMLEvent next = readNoWhitespace(reader);
2370
			if (isMyEndingElement(next, parentEvent)) {
2371
				nameMap.put(classValue, text);
2372
				return;
2373
			} else if (next.isStartElement()) {
2374
				if (isStartingElement(next, ANNOTATION)) {
2375
					handleNotYetImplementedElement(next);
2376
				} else {
2377
					handleUnexpectedStartElement(next.asStartElement());
2378
				}
2379
			} else if (next.isCharacters()) {
2380
				text += next.asCharacters().getData();
2381
			} else {
2382
				handleUnexpectedEndElement(next.asEndElement());
2383
			}
2384
		}
2385
		throw new IllegalStateException("name has no closing tag");
2386

    
2387
	}
2388

    
2389
	/**
2390
	 * @param state
2391
	 * @param classValue
2392
	 * @param byAbbrev
2393
	 * @return
2394
	 */
2395
	private Rank makeRank(MarkupImportState state, String value,
2396
			boolean byAbbrev) {
2397
		Rank rank = null;
2398
		if (StringUtils.isBlank(value)) {
2399
			return null;
2400
		}
2401
		try {
2402
			boolean useUnknown = true;
2403
			NomenclaturalCode nc = makeNomenclaturalCode(state);
2404
			if (byAbbrev) {
2405
				rank = Rank.getRankByAbbreviation(value, nc, useUnknown);
2406
			} else {
2407
				rank = Rank.getRankByEnglishName(value, nc, useUnknown);
2408
			}
2409
			if (rank.equals(Rank.UNKNOWN_RANK())) {
2410
				rank = null;
2411
			}
2412
		} catch (UnknownCdmTypeException e) {
2413
			// doNothing
2414
		}
2415
		return rank;
2416
	}
2417

    
2418
	// public void handleNameNotRank(MarkupImportState state, XMLEventReader
2419
	// reader, XMLEvent parentEvent, String classValue, NonViralName name)
2420
	// throws XMLStreamException {
2421
	// if (ACCEPTED.equalsIgnoreCase(classValue)){
2422
	// }else if (SYNONYM.equalsIgnoreCase(classValue)){
2423
	// }else{
2424
	// //TODO Not yet implemented
2425
	// handleNotYetImplementedAttributeValue(parentEvent, CLASS, classValue);
2426
	// }
2427
	// }
2428

    
2429
	private void handleCitation(MarkupImportState state, XMLEventReader reader,
2430
			XMLEvent parentEvent, NonViralName name) throws XMLStreamException {
2431
		String classValue = getClassOnlyAttribute(parentEvent);
2432

    
2433
		state.setCitation(true);
2434
		boolean hasRefPart = false;
2435
		Map<String, String> refMap = new HashMap<String, String>();
2436
		while (reader.hasNext()) {
2437
			XMLEvent next = readNoWhitespace(reader);
2438
			if (isMyEndingElement(next, parentEvent)) {
2439
				checkMandatoryElement(hasRefPart, parentEvent.asStartElement(),
2440
						REF_PART);
2441
				Reference reference = createReference(state, refMap, next);
2442
				String microReference = refMap.get(DETAILS);
2443
				doCitation(state, name, classValue, reference, microReference,
2444
						parentEvent);
2445
				state.setCitation(false);
2446
				return;
2447
			} else if (isStartingElement(next, REF_PART)) {
2448
				handleRefPart(state, reader, next, refMap);
2449
				hasRefPart = true;
2450
			} else {
2451
				handleUnexpectedElement(next);
2452
			}
2453
		}
2454
		throw new IllegalStateException("Citation has no closing tag");
2455

    
2456
	}
2457

    
2458
	private void handleRefPart(MarkupImportState state, XMLEventReader reader,XMLEvent parentEvent, Map<String, String> refMap) throws XMLStreamException {
2459
		String classValue = getClassOnlyAttribute(parentEvent);
2460

    
2461
		String text = "";
2462
		while (reader.hasNext()) {
2463
			XMLEvent next = readNoWhitespace(reader);
2464
			if (isMyEndingElement(next, parentEvent)) {
2465
				refMap.put(classValue, text);
2466
				return;
2467
			} else if (next.isStartElement()) {
2468
				if (isStartingElement(next, ANNOTATION)) {
2469
					handleNotYetImplementedElement(next);
2470
				} else if (isStartingElement(next, ITALICS)) {
2471
					handleNotYetImplementedElement(next);
2472
				} else if (isStartingElement(next, BOLD)) {
2473
					handleNotYetImplementedElement(next);
2474
				} else {
2475
					handleUnexpectedStartElement(next.asStartElement());
2476
				}
2477
			} else if (next.isCharacters()) {
2478
				text += next.asCharacters().getData();
2479
			} else {
2480
				handleUnexpectedEndElement(next.asEndElement());
2481
			}
2482
		}
2483
		throw new IllegalStateException("RefPart has no closing tag");
2484

    
2485
	}
2486

    
2487
	private Reference createReference(MarkupImportState state, Map<String, String> refMap, XMLEvent parentEvent) {
2488
		// TODO
2489
		Reference reference;
2490

    
2491
		String type = getAndRemoveMapKey(refMap, PUBTYPE);
2492
		String authorStr = getAndRemoveMapKey(refMap, AUTHOR);
2493
		String titleStr = getAndRemoveMapKey(refMap, PUBTITLE);
2494
		String titleCache = getAndRemoveMapKey(refMap, PUBFULLNAME);
2495
		String volume = getAndRemoveMapKey(refMap, VOLUME);
2496
		String edition = getAndRemoveMapKey(refMap, EDITION);
2497
		String editors = getAndRemoveMapKey(refMap, EDITORS);
2498
		String year = getAndRemoveMapKey(refMap, YEAR);
2499
		String pubName = getAndRemoveMapKey(refMap, PUBNAME);
2500

    
2501
		if (state.isCitation()) {
2502
			if (volume != null || "journal".equalsIgnoreCase(type)) {
2503
				IArticle article = ReferenceFactory.newArticle();
2504
				if (pubName != null) {
2505
					IJournal journal = ReferenceFactory.newJournal();
2506
					journal.setTitle(pubName);
2507
					article.setInJournal(journal);
2508
				}
2509
				reference = (Reference) article;
2510

    
2511
			} else {
2512
				// TODO
2513
				Reference bookOrPartOf = ReferenceFactory.newGeneric();
2514
				reference = bookOrPartOf;
2515
			}
2516
			// TODO use existing author from name or before
2517
			TeamOrPersonBase author = createAuthor(authorStr);
2518
			reference.setAuthorTeam(author);
2519

    
2520
		} else {
2521
			if (volume != null || "journal".equalsIgnoreCase(type)) {
2522
				IArticle article = ReferenceFactory.newArticle();
2523
				if (pubName != null) {
2524
					IJournal journal = ReferenceFactory.newJournal();
2525
					journal.setTitle(pubName);
2526
					article.setInJournal(journal);
2527
				}
2528
				reference = (Reference) article;
2529

    
2530
			} else {
2531
				Reference bookOrPartOf = ReferenceFactory.newGeneric();
2532
				reference = bookOrPartOf;
2533
			}
2534

    
2535
			// TODO type
2536
			TeamOrPersonBase author = createAuthor(authorStr);
2537
			reference.setAuthorTeam(author);
2538

    
2539
			reference.setTitle(titleStr);
2540
			if (StringUtils.isNotBlank(titleCache)) {
2541
				reference.setTitleCache(titleCache, true);
2542
			}
2543
			reference.setEdition(edition);
2544
			reference.setEditor(editors);
2545

    
2546
			if (pubName != null) {
2547
				Reference inReference;
2548
				if (reference.getType().equals(ReferenceType.Article)) {
2549
					inReference = ReferenceFactory.newJournal();
2550
				} else {
2551
					inReference = ReferenceFactory.newGeneric();
2552
				}
2553
				inReference.setTitle(pubName);
2554
				reference.setInReference(inReference);
2555
			}
2556
		}
2557
		reference.setVolume(volume);
2558
		reference.setDatePublished(TimePeriod.parseString(year));
2559

    
2560
		// TODO
2561
		String[] unhandledList = new String[]{ALTERNATEPUBTITLE, ISSUE, NOTES, STATUS};
2562
		for (String unhandled : unhandledList){
2563
			String value = getAndRemoveMapKey(refMap, unhandled);
2564
			if (isNotBlank(value)){
2565
				this.handleNotYetImplementedAttributeValue(parentEvent, CLASS, unhandled);
2566
			}
2567
		}
2568
		
2569
		for (String key : refMap.keySet()) {
2570
			if (!DETAILS.equalsIgnoreCase(key)) {
2571
				this.fireUnexpectedAttributeValue(parentEvent, CLASS, key);
2572
			}
2573
		}
2574

    
2575
		return reference;
2576
	}
2577

    
2578
	private TeamOrPersonBase createAuthor(String authorTitle) {
2579
		// TODO atomize and also use by name creation
2580
		TeamOrPersonBase result = Team.NewTitledInstance(authorTitle,
2581
				authorTitle);
2582
		return result;
2583
	}
2584

    
2585
	private String getAndRemoveMapKey(Map<String, String> map, String key) {
2586
		String result = map.get(key);
2587
		map.remove(key);
2588
		if (result != null) {
2589
			result = normalize(result);
2590
		}
2591
		return StringUtils.stripToNull(result);
2592
	}
2593

    
2594
	private void doCitation(MarkupImportState state, NonViralName name,
2595
			String classValue, Reference reference, String microCitation,
2596
			XMLEvent parentEvent) {
2597
		if (PUBLICATION.equalsIgnoreCase(classValue)) {
2598
			name.setNomenclaturalReference(reference);
2599
			name.setNomenclaturalMicroReference(microCitation);
2600
		} else if (USAGE.equalsIgnoreCase(classValue)) {
2601
			Taxon taxon = state.getCurrentTaxon();
2602
			TaxonDescription td = this.getTaxonDescription(taxon, state
2603
					.getConfig().getSourceReference(), false, true);
2604
			TextData citation = TextData.NewInstance(Feature.CITATION());
2605
			// TODO name used in source
2606
			citation.addSource(null, null, reference, microCitation);
2607
			td.addElement(citation);
2608
		} else if (TYPE.equalsIgnoreCase(classValue)) {
2609
			handleNotYetImplementedAttributeValue(parentEvent, CLASS,
2610
					classValue);
2611
		} else {
2612
			// TODO Not yet implemented
2613
			handleNotYetImplementedAttributeValue(parentEvent, CLASS,
2614
					classValue);
2615
		}
2616
	}
2617

    
2618
	private void handleFeature(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
2619
		String classValue = getClassOnlyAttribute(parentEvent);
2620
		Feature feature = makeFeature(classValue, state, parentEvent);
2621
		Taxon taxon = state.getCurrentTaxon();
2622
		TaxonDescription taxonDescription = getTaxonDescription(taxon, state.getConfig().getSourceReference(), NO_IMAGE_GALLERY, CREATE_NEW);
2623
		// TextData figureHolderTextData = null; //for use with one TextData for
2624
		// all figure only
2625

    
2626
		boolean isDescription = feature.equals(Feature.DESCRIPTION());
2627
		DescriptionElementBase lastDescriptionElement = null;
2628
		
2629
		while (reader.hasNext()) {
2630
			XMLEvent next = readNoWhitespace(reader);
2631
			if (isMyEndingElement(next, parentEvent)) {
2632
				return;
2633
			} else if (isEndingElement(next, DISTRIBUTION_LIST) || isEndingElement(next, HABITAT_LIST)) { 
2634
				// only handle list elements
2635
			} else if (isStartingElement(next, HEADING)) {
2636
				makeFeatureHeading(state, reader, classValue, feature, next);
2637
			} else if (isStartingElement(next, WRITER)) {
2638
				makeFeatureWriter(state, reader, feature, taxon, next);
2639
//			} else if (isStartingElement(next, DISTRIBUTION_LOCALITY)) {
2640
//				if (!feature.equals(Feature.DISTRIBUTION())) {
2641
//					String message = "Distribution locality only allowed for feature of type 'distribution'";
2642
//					fireWarningEvent(message, next, 4);
2643
//				}
2644
//				handleDistributionLocality(state, reader, next);
2645
			} else if (isStartingElement(next, DISTRIBUTION_LIST) || isStartingElement(next, HABITAT_LIST)) {
2646
				// only handle single list elements
2647
			} else if (isStartingElement(next, HABITAT)) {
2648
				if (!(feature.equals(Feature.HABITAT())
2649
						|| feature.equals(Feature.HABITAT_ECOLOGY()) 
2650
						|| feature.equals(Feature.ECOLOGY()))) {
2651
					String message = "Habitat only allowed for feature of type 'habitat','habitat ecology' or 'ecology'";
2652
					fireWarningEvent(message, next, 4);
2653
				}
2654
				handleHabitat(state, reader, next);
2655
			} else if (isStartingElement(next, CHAR)) {
2656
				TextData textData = handleChar(state, reader, next);
2657
				taxonDescription.addElement(textData);
2658
			} else if (isStartingElement(next, STRING)) {
2659
				lastDescriptionElement = makeFeatureString(state, reader,feature, taxonDescription, lastDescriptionElement,next);
2660
			} else if (isStartingElement(next, FIGURE_REF)) {
2661
				lastDescriptionElement = makeFeatureFigureRef(state, reader, taxonDescription, isDescription, lastDescriptionElement, next);
2662
			} else if (isStartingElement(next, REFERENCES)) {
2663
				// TODO details/microcitation ??
2664

    
2665
				List<Reference<?>> refs = handleReferences(state, reader, next);
2666
				if (!refs.isEmpty()) {
2667
					// TODO
2668
					Reference<?> descriptionRef = state.getConfig().getSourceReference();
2669
					TaxonDescription description = getTaxonDescription(taxon, descriptionRef, false, true);
2670
					TextData featurePlaceholder = getFeaturePlaceholder(state, description, feature, true);
2671
					for (Reference<?> citation : refs) {
2672
						featurePlaceholder.addSource(null, null, citation, null);
2673
					}
2674
				} else {
2675
					String message = "No reference found in references";
2676
					fireWarningEvent(message, next, 6);
2677
				}
2678
			} else if (isStartingElement(next, NUM)) {
2679
				//TODO
2680
				handleNotYetImplementedElement(next);
2681
			} else if (isEndingElement(next, NUM)) {
2682
				//TODO
2683
				popUnimplemented(next.asEndElement());
2684
			} else {
2685
				handleUnexpectedElement(next);
2686
			}
2687
		}
2688
		throw new IllegalStateException("<Feature> has no closing tag");
2689
	}
2690

    
2691
	/**
2692
	 * @param state
2693
	 * @param reader
2694
	 * @param taxonDescription
2695
	 * @param isDescription
2696
	 * @param lastDescriptionElement
2697
	 * @param next
2698
	 * @return
2699
	 * @throws XMLStreamException
2700
	 */
2701
	private DescriptionElementBase makeFeatureFigureRef(MarkupImportState state, XMLEventReader reader,TaxonDescription taxonDescription, 
2702
					boolean isDescription, DescriptionElementBase lastDescriptionElement, XMLEvent next)throws XMLStreamException {
2703
		FigureDataHolder figureHolder = handleFigureRef(state, reader, next);
2704
		Feature figureFeature = getFeature(state,MarkupTransformer.uuidFigures, "Figures", "Figures", "Fig.",null);
2705
		if (isDescription) {
2706
			TextData figureHolderTextData = null;
2707
			// if (figureHolderTextData == null){
2708
			figureHolderTextData = TextData.NewInstance(figureFeature);
2709
			if (StringUtils.isNotBlank(figureHolder.num)) {
2710
				String annotationText = "<num>" + figureHolder.num.trim()
2711
						+ "</num>";
2712
				Annotation annotation = Annotation.NewInstance(annotationText,
2713
						AnnotationType.TECHNICAL(), Language.DEFAULT());
2714
				figureHolderTextData.addAnnotation(annotation);
2715
			}
2716
			if (StringUtils.isNotBlank(figureHolder.figurePart)) {
2717
				String annotationText = "<figurePart>"+ figureHolder.figurePart.trim() + "</figurePart>";
2718
				Annotation annotation = Annotation.NewInstance(annotationText,AnnotationType.EDITORIAL(), Language.DEFAULT());
2719
				figureHolderTextData.addAnnotation(annotation);
2720
			}
2721
			// if (StringUtils.isNotBlank(figureText)){
2722
			// figureHolderTextData.putText(Language.DEFAULT(), figureText);
2723
			// }
2724
			taxonDescription.addElement(figureHolderTextData);
2725
			// }
2726
			registerFigureDemand(state, figureHolderTextData, figureHolder.ref);
2727
		} else {
2728
			if (lastDescriptionElement == null) {
2729
				String message = "No description element created yet that can be referred by figure. Create new TextData instead";
2730
				fireWarningEvent(message, next, 4);
2731
				lastDescriptionElement = TextData.NewInstance(figureFeature);
2732
				taxonDescription.addElement(lastDescriptionElement);
2733
			}
2734
			registerFigureDemand(state, lastDescriptionElement,
2735
					figureHolder.ref);
2736
		}
2737
		return lastDescriptionElement;
2738
	}
2739

    
2740
	/**
2741
	 * @param state
2742
	 * @param reader
2743
	 * @param feature
2744
	 * @param taxonDescription
2745
	 * @param lastDescriptionElement
2746
	 * @param distributionList 
2747
	 * @param next
2748
	 * @return
2749
	 * @throws XMLStreamException
2750
	 */
2751
	private DescriptionElementBase makeFeatureString(MarkupImportState state,XMLEventReader reader, Feature feature, 
2752
				TaxonDescription taxonDescription, DescriptionElementBase lastDescriptionElement, XMLEvent next) throws XMLStreamException {
2753
		Map<String, String> subheadingMap = handleString(state, reader, next, feature);
2754
		for (String subheading : subheadingMap.keySet()) {
2755
			Feature subheadingFeature = feature;
2756
			if (StringUtils.isNotBlank(subheading) && subheadingMap.size() > 1) {
2757
				subheadingFeature = makeFeature(subheading, state, next);
2758
			}
2759
			TextData textData = TextData.NewInstance(subheadingFeature);
2760
			textData.putText(Language.DEFAULT(), subheadingMap.get(subheading));
2761
			taxonDescription.addElement(textData);
2762
			// TODO how to handle figures when these data are split in
2763
			// subheadings
2764
			lastDescriptionElement = textData;
2765
		}
2766
		return lastDescriptionElement;
2767
	}
2768

    
2769
	/**
2770
	 * @param state
2771
	 * @param reader
2772
	 * @param feature
2773
	 * @param taxon
2774
	 * @param next
2775
	 * @throws XMLStreamException
2776
	 */
2777
	private void makeFeatureWriter(MarkupImportState state,XMLEventReader reader, Feature feature, Taxon taxon, XMLEvent next) throws XMLStreamException {
2778
		WriterDataHolder writer = handleWriter(state, reader, next);
2779
		if (isNotBlank(writer.writer)) {
2780
			// TODO
2781
			Reference<?> ref = state.getConfig().getSourceReference();
2782
			TaxonDescription description = getTaxonDescription(taxon, ref,
2783
					false, true);
2784
			TextData featurePlaceholder = getFeaturePlaceholder(state,
2785
					description, feature, true);
2786
			featurePlaceholder.addAnnotation(writer.annotation);
2787
			registerFootnotes(state, featurePlaceholder, writer.footnotes);
2788
		} else {
2789
			String message = "Writer element is empty";
2790
			fireWarningEvent(message, next, 4);
2791
		}
2792
	}
2793

    
2794
	/**
2795
	 * @param state
2796
	 * @param reader
2797
	 * @param classValue
2798
	 * @param feature
2799
	 * @param next
2800
	 * @throws XMLStreamException
2801
	 */
2802
	private void makeFeatureHeading(MarkupImportState state, XMLEventReader reader, String classValue, Feature feature, XMLEvent next) throws XMLStreamException {
2803
		String heading = handleHeading(state, reader, next);
2804
		if (StringUtils.isNotBlank(heading)) {
2805
			if (!heading.equalsIgnoreCase(classValue)) {
2806
				try {
2807
					if (!feature.equals(state.getTransformer().getFeatureByKey(
2808
							heading))) {
2809
						UUID headerFeatureUuid = state.getTransformer()
2810
								.getFeatureUuid(heading);
2811
						if (!feature.getUuid().equals(headerFeatureUuid)) {
2812
							String message = "Feature heading '%s' differs from feature class '%s' and can not be transformed to feature";
2813
							message = String.format(message, heading,
2814
									classValue);
2815
							fireWarningEvent(message, next, 1);
2816
						}
2817
					}
2818
				} catch (UndefinedTransformerMethodException e) {
2819
					throw new RuntimeException(e);
2820
				}
2821
			} else {
2822
				// do nothing
2823
			}
2824
		}
2825
	}
2826

    
2827
	private List<Reference<?>> handleReferences(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
2828
		// attributes
2829
		Map<String, Attribute> attributes = getAttributes(parentEvent);
2830
		String bibliography = getAndRemoveAttributeValue(attributes,
2831
				BIBLIOGRAPHY);
2832
		String serialsAbbreviations = getAndRemoveAttributeValue(attributes,
2833
				SERIALS_ABBREVIATIONS);
2834
		if (isNotBlank(bibliography) || isNotBlank(serialsAbbreviations)) {
2835
			String message = "Attributes not yet implemented for <references>";
2836
			fireWarningEvent(message, parentEvent, 4);
2837
		}
2838

    
2839
		List<Reference<?>> result = new ArrayList<Reference<?>>();
2840

    
2841
		// elements
2842
		while (reader.hasNext()) {
2843
			XMLEvent next = readNoWhitespace(reader);
2844
			if (next.isEndElement()) {
2845
				if (isMyEndingElement(next, parentEvent)) {
2846
					return result;
2847
				} else {
2848
					if (isEndingElement(next, HEADING)) {
2849
						// NOT YET IMPLEMENTED
2850
						popUnimplemented(next.asEndElement());
2851
					} else if (isEndingElement(next, WRITER)) {
2852
						// NOT YET IMPLEMENTED
2853
						popUnimplemented(next.asEndElement());
2854
					} else if (isEndingElement(next, FOOTNOTE)) {
2855
						// NOT YET IMPLEMENTED
2856
						popUnimplemented(next.asEndElement());
2857
					} else if (isEndingElement(next, STRING)) {
2858
						// NOT YET IMPLEMENTED
2859
						popUnimplemented(next.asEndElement());
2860
					} else if (isEndingElement(next, REF_NUM)) {
2861
						// NOT YET IMPLEMENTED
2862
						popUnimplemented(next.asEndElement());
2863
					} else {
2864
						handleUnexpectedEndElement(next.asEndElement());
2865
					}
2866
				}
2867
			} else if (next.isStartElement()) {
2868
				if (isStartingElement(next, HEADING)) {
2869
					handleNotYetImplementedElement(next);
2870
				} else if (isStartingElement(next, SUB_HEADING)) {
2871
					String subheading = getCData(state, reader, next).trim();
2872
					String excludePattern = "(i?)(References?|Literature):?";
2873
					if (!subheading.matches(excludePattern)) {
2874
						fireNotYetImplementedElement(next.getLocation(), next.asStartElement().getName(), 0);
2875
					}
2876
				} else if (isStartingElement(next, WRITER)) {
2877
					handleNotYetImplementedElement(next);
2878
				} else if (isStartingElement(next, FOOTNOTE)) {
2879
					handleNotYetImplementedElement(next);
2880
				} else if (isStartingElement(next, STRING)) {
2881
					handleNotYetImplementedElement(next);
2882
				} else if (isStartingElement(next, REF_NUM)) {
2883
					handleNotYetImplementedElement(next);
2884
				} else if (isStartingElement(next, REFERENCE)) {
2885
					Reference<?> ref = handleReference(state, reader, next);
2886
					result.add(ref);
2887
				} else {
2888
					handleUnexpectedStartElement(next);
2889
				}
2890
			} else {
2891
				handleUnexpectedElement(next);
2892
			}
2893
		}
2894
		throw new IllegalStateException("<References> has no closing tag");
2895
	}
2896

    
2897
	private void handleHabitat(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
2898
		checkNoAttributes(parentEvent);
2899
		Taxon taxon = state.getCurrentTaxon();
2900
		// TODO which ref to take?
2901
		Reference<?> ref = state.getConfig().getSourceReference();
2902

    
2903
		String text = "";
2904
		while (reader.hasNext()) {
2905
			XMLEvent next = readNoWhitespace(reader);
2906
			if (isMyEndingElement(next, parentEvent)) {
2907
				TaxonDescription description = getTaxonDescription(taxon, ref,
2908
						false, true);
2909
				UUID uuidExtractedHabitat = MarkupTransformer.uuidExtractedHabitat;
2910
				Feature feature = getFeature(
2911
						state,
2912
						uuidExtractedHabitat,
2913
						"Extracted Habitat",
2914
						"An structured habitat that was extracted from a habitat text",
2915
						"extr. habit.", null);
2916
				TextData habitat = TextData.NewInstance(feature);
2917
				habitat.putText(Language.DEFAULT(), text);
2918
				description.addElement(habitat);
2919

    
2920
				return;
2921
			} else if (next.isStartElement()) {
2922
				if (isStartingElement(next, ALTITUDE)) {
2923
					text = text.trim() + getTaggedCData(state, reader, next);
2924
				} else if (isStartingElement(next, LIFE_CYCLE_PERIODS)) {
2925
					handleNotYetImplementedElement(next);
2926
				} else {
2927
					handleUnexpectedStartElement(next.asStartElement());
2928
				}
2929
			} else if (next.isCharacters()) {
2930
				text += next.asCharacters().getData();
2931
			} else {
2932
				handleUnexpectedElement(next);
2933
			}
2934
		}
2935
		throw new IllegalStateException("<Habitat> has no closing tag");
2936
	}
2937

    
2938
	private String getTaggedCData(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
2939
		checkNoAttributes(parentEvent);
2940

    
2941
		String text = getXmlTag(parentEvent);
2942
		while (reader.hasNext()) {
2943
			XMLEvent next = readNoWhitespace(reader);
2944
			if (isMyEndingElement(next, parentEvent)) {
2945
				text += getXmlTag(next);
2946
				return text;
2947
			} else if (next.isStartElement()) {
2948
				text += getTaggedCData(state, reader, next);
2949
			} else if (next.isEndElement()) {
2950
				text += getTaggedCData(state, reader, next);
2951
			} else if (next.isCharacters()) {
2952
				text += next.asCharacters().getData();
2953
			} else {
2954
				handleUnexpectedEndElement(next.asEndElement());
2955
			}
2956
		}
2957
		throw new IllegalStateException("Some tag has no closing tag");
2958

    
2959
	}
2960

    
2961
	private void handleDistributionLocality(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent)throws XMLStreamException {
2962
		Map<String, Attribute> attributes = getAttributes(parentEvent);
2963
		String classValue = getAndRemoveRequiredAttributeValue(parentEvent, attributes, CLASS);
2964
		String statusValue =getAndRemoveAttributeValue(attributes, STATUS);
2965
		String frequencyValue =getAndRemoveAttributeValue(attributes, FREQUENCY);
2966
		
2967

    
2968
		Taxon taxon = state.getCurrentTaxon();
2969
		// TODO which ref to take?
2970
		Reference<?> ref = state.getConfig().getSourceReference();
2971

    
2972
		String text = "";
2973
		while (reader.hasNext()) {
2974
			XMLEvent next = readNoWhitespace(reader);
2975
			if (isMyEndingElement(next, parentEvent)) {
2976
				if (StringUtils.isNotBlank(text)) {
2977
					TaxonDescription description = getTaxonDescription(taxon, ref, false, true);
2978
					NamedAreaLevel level = makeNamedAreaLevel(state,classValue, next);
2979
					NamedArea area = createArea(text, level, state);
2980

    
2981
					PresenceAbsenceTermBase<?> status = null;
2982
					try {
2983
						status = state.getTransformer().getPresenceTermByKey(statusValue);
2984
					} catch (UndefinedTransformerMethodException e) {
2985
						throw new RuntimeException(e);
2986
					}
2987
					Distribution distribution = Distribution.NewInstance(area,status);
2988
					description.addElement(distribution);
2989
					if (isNotBlank(frequencyValue)){
2990
						String message = "The frequency attribute is currently not yet available in CDM";
2991
						fireWarningEvent(message, parentEvent, 6);
2992
					}
2993
				} else {
2994
					String message = "Empty distribution locality";
2995
					fireWarningEvent(message, next, 4);
2996
				}
2997
				return;
2998
			} else if (isStartingElement(next, COORDINATES)) {
2999
				//TODO
3000
				handleNotYetImplementedElement(next);
3001
			} else if (isEndingElement(next, COORDINATES)) {
3002
				//TODO
3003
				popUnimplemented(next.asEndElement());
3004
			} else if (next.isCharacters()) {
3005
				text += next.asCharacters().getData();
3006
			} else {
3007
				handleUnexpectedEndElement(next.asEndElement());
3008
			}
3009
		}
3010
		throw new IllegalStateException("<DistributionLocality> has no closing tag");
3011
	}
3012

    
3013
	/**
3014
	 * @param state
3015
	 * @param levelString
3016
	 * @param next
3017
	 * @return
3018
	 */
3019
	private NamedAreaLevel makeNamedAreaLevel(MarkupImportState state,
3020
			String levelString, XMLEvent next) {
3021
		NamedAreaLevel level;
3022
		try {
3023
			level = state.getTransformer().getNamedAreaLevelByKey(levelString);
3024
			if (level == null) {
3025
				UUID levelUuid = state.getTransformer().getNamedAreaLevelUuid(levelString);
3026
				if (levelUuid == null) {
3027
					String message = "Unknown distribution locality class (named area level): %s. Create new level instead.";
3028
					message = String.format(message, levelString);
3029
					fireWarningEvent(message, next, 6);
3030
				}
3031
				level = getNamedAreaLevel(state, levelUuid, levelString,
3032
						levelString, levelString, null);
3033
			}
3034
		} catch (UndefinedTransformerMethodException e) {
3035
			throw new RuntimeException(e);
3036
		}
3037
		return level;
3038
	}
3039

    
3040
	private String handleHeading(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent)throws XMLStreamException {
3041
		checkNoAttributes(parentEvent);
3042

    
3043
		String text = "";
3044
		while (reader.hasNext()) {
3045
			XMLEvent next = readNoWhitespace(reader);
3046
			if (isMyEndingElement(next, parentEvent)) {
3047
				return text;
3048
			} else if (next.isStartElement()) {
3049
				if (isStartingElement(next, FOOTNOTE)) {
3050
					handleNotYetImplementedElement(next);
3051
				} else {
3052
					handleUnexpectedStartElement(next.asStartElement());
3053
				}
3054
			} else if (next.isCharacters()) {
3055
				text += next.asCharacters().getData();
3056
			} else {
3057
				handleUnexpectedEndElement(next.asEndElement());
3058
			}
3059
		}
3060
		throw new IllegalStateException("<String> has no closing tag");
3061

    
3062
	}
3063

    
3064
	/**
3065
	 * Handle string
3066
	 * @param state
3067
	 * @param reader
3068
	 * @param parentEvent
3069
	 * @param feature only needed for distributionLocalities
3070
	 * @return
3071
	 * @throws XMLStreamException
3072
	 */
3073
	private Map<String, String> handleString(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, Feature feature)throws XMLStreamException {
3074
		// attributes
3075
		String classValue = getClassOnlyAttribute(parentEvent, false);
3076
		if (StringUtils.isNotBlank(classValue)) {
3077
			String message = "class attribute for <string> not yet implemented";
3078
			fireWarningEvent(message, parentEvent, 2);
3079
		}
3080

    
3081
		// subheadings
3082
		Map<String, String> subHeadingMap = new HashMap<String, String>();
3083
		String currentSubheading = null;
3084

    
3085
		boolean isTextMode = true;
3086
		String text = "";
3087
		while (reader.hasNext()) {
3088
			XMLEvent next = readNoWhitespace(reader);
3089
			if (isMyEndingElement(next, parentEvent)) {
3090
				putCurrentSubheading(subHeadingMap, currentSubheading, text);
3091
				return subHeadingMap;
3092
			} else if (isStartingElement(next, BR)) {
3093
				text += "<br/>";
3094
				isTextMode = false;
3095
			} else if (isEndingElement(next, BR)) {
3096
				isTextMode = true;
3097
			} else if (isHtml(next)) {
3098
				text += getXmlTag(next);
3099
			} else if (isStartingElement(next, SUB_HEADING)) {
3100
				text = putCurrentSubheading(subHeadingMap,currentSubheading, text);
3101
				// TODO footnotes
3102
				currentSubheading = getCData(state, reader, next).trim();
3103
			} else if (isStartingElement(next, DISTRIBUTION_LOCALITY)) {
3104
				if (feature != null && !feature.equals(Feature.DISTRIBUTION())) {
3105
					String message = "Distribution locality only allowed for feature of type 'distribution'";
3106
					fireWarningEvent(message, next, 4);
3107
				}
3108
				handleDistributionLocality(state, reader, next);
3109
			
3110
			} else if (next.isCharacters()) {
3111
				if (!isTextMode) {
3112
					String message = "String is not in text mode";
3113
					fireWarningEvent(message, next, 6);
3114
				} else {
3115
					text += next.asCharacters().getData();
3116
				}
3117
			} else if (isStartingElement(next, HEADING)) {
3118
				//TODO
3119
				handleNotYetImplementedElement(next);
3120
			} else if (isEndingElement(next, HEADING)) {
3121
				//TODO
3122
				popUnimplemented(next.asEndElement());
3123
			} else if (isStartingElement(next, QUOTE)) {
3124
				//TODO
3125
				handleNotYetImplementedElement(next);
3126
			} else if (isEndingElement(next, QUOTE)) {
3127
				//TODO
3128
				popUnimplemented(next.asEndElement());
3129
			} else if (isStartingElement(next, DEDICATION)) {
3130
				//TODO
3131
				handleNotYetImplementedElement(next);
3132
			} else if (isEndingElement(next, DEDICATION)) {
3133
				//TODO
3134
				popUnimplemented(next.asEndElement());
3135
			} else if (isStartingElement(next, TAXONTYPE)) {
3136
				//TODO
3137
				handleNotYetImplementedElement(next);
3138
			} else if (isEndingElement(next, TAXONTYPE)) {
3139
				//TODO
3140
				popUnimplemented(next.asEndElement());
3141
			} else if (isStartingElement(next, FULL_NAME)) {
3142
				//TODO
3143
				handleNotYetImplementedElement(next);
3144
			} else if (isEndingElement(next, FULL_NAME)) {
3145
				//TODO
3146
				popUnimplemented(next.asEndElement());
3147
			}else if (isStartingElement(next, REFERENCES)) {
3148
				//TODO
3149
				handleNotYetImplementedElement(next);
3150
			} else if (isEndingElement(next, REFERENCES)) {
3151
				//TODO
3152
				popUnimplemented(next.asEndElement());
3153
			} else if (isStartingElement(next, GATHERING)) {
3154
				//TODO
3155
				handleNotYetImplementedElement(next);
3156
			} else if (isEndingElement(next, GATHERING)) {
3157
				//TODO
3158
				popUnimplemented(next.asEndElement());
3159
			} else if (isStartingElement(next, ANNOTATION)) {
3160
				//TODO
3161
				handleNotYetImplementedElement(next);
3162
			} else if (isEndingElement(next, ANNOTATION)) {
3163
				//TODO
3164
				popUnimplemented(next.asEndElement());
3165
			} else if (isStartingElement(next, HABITAT)) {
3166
				//TODO
3167
				handleNotYetImplementedElement(next);
3168
			} else if (isEndingElement(next, HABITAT)) {
3169
				//TODO
3170
				popUnimplemented(next.asEndElement());
3171
			} else if (isStartingElement(next, FIGURE_REF)) {
3172
				//TODO
3173
				handleNotYetImplementedElement(next);
3174
			} else if (isEndingElement(next, FIGURE_REF)) {
3175
				//TODO
3176
				popUnimplemented(next.asEndElement());
3177
			} else if (isStartingElement(next, FIGURE)) {
3178
				//TODO
3179
				handleNotYetImplementedElement(next);
3180
			} else if (isEndingElement(next, FIGURE)) {
3181
				//TODO
3182
				popUnimplemented(next.asEndElement());
3183
			} else if (isStartingElement(next, FOOTNOTE_REF)) {
3184
				//TODO
3185
				handleNotYetImplementedElement(next);
3186
			} else if (isEndingElement(next, FOOTNOTE_REF)) {
3187
				//TODO
3188
				popUnimplemented(next.asEndElement());
3189
			} else if (isStartingElement(next, FOOTNOTE)) {
3190
				//TODO
3191
				handleNotYetImplementedElement(next);
3192
			} else if (isEndingElement(next, FOOTNOTE)) {
3193
				//TODO
3194
				popUnimplemented(next.asEndElement());
3195
			} else if (isStartingElement(next, WRITER)) {
3196
				//TODO
3197
				handleNotYetImplementedElement(next);
3198
			} else if (isEndingElement(next, WRITER)) {
3199
				//TODO
3200
				popUnimplemented(next.asEndElement());
3201
			} else if (isStartingElement(next, DATES)) {
3202
				//TODO
3203
				handleNotYetImplementedElement(next);
3204
			} else if (isEndingElement(next, DATES)) {
3205
				//TODO
3206
				popUnimplemented(next.asEndElement());
3207
			} else {
3208
				handleUnexpectedElement(next);
3209
			}
3210
		}
3211
		throw new IllegalStateException("<String> has no closing tag");
3212
	}
3213

    
3214
	/**
3215
	 * @param subHeadingMap
3216
	 * @param currentSubheading
3217
	 * @param text
3218
	 * @return
3219
	 */
3220
	private String putCurrentSubheading(Map<String, String> subHeadingMap, String currentSubheading, String text) {
3221
		if (StringUtils.isNotBlank(text)) {
3222
			text = removeStartingMinus(text);
3223
			subHeadingMap.put(currentSubheading, text.trim());
3224
		}
3225
		return "";
3226
	}
3227

    
3228
	private String removeStartingMinus(String string) {
3229
		string = replaceStart(string, "-");
3230
		string = replaceStart(string, "\u002d");
3231
		string = replaceStart(string, "\u2013");
3232
		string = replaceStart(string, "\u2014");
3233
		string = replaceStart(string, "--");
3234
		return string;
3235
	}
3236
	
3237
	/**
3238
	 * @param value
3239
	 * @param replacementString
3240
	 */
3241
	private String replaceStart(String value, String replacementString) {
3242
		if (value.startsWith(replacementString) ){
3243
			value = value.substring(replacementString.length()).trim();
3244
		}
3245
		while (value.startsWith("-") || value.startsWith("\u2014") ){
3246
			value = value.substring("-".length()).trim();
3247
		}
3248
		return value;
3249
	}
3250
	
3251
	private String getXmlTag(XMLEvent event) {
3252
		String result;
3253
		if (event.isStartElement()) {
3254
			result = "<" + event.asStartElement().getName().getLocalPart()
3255
					+ ">";
3256
		} else if (event.isEndElement()) {
3257
			result = "</" + event.asEndElement().getName().getLocalPart() + ">";
3258
		} else {
3259
			String message = "Only start or end elements are allowed as Html tags";
3260
			throw new IllegalStateException(message);
3261
		}
3262
		return result;
3263
	}
3264

    
3265
	protected static final List<String> htmlList = Arrays.asList("sub", "sup",
3266
			"ol", "ul", "li", "i", "b", "table", "br");
3267

    
3268
	private boolean isHtml(XMLEvent event) {
3269
		if (event.isStartElement()) {
3270
			String tag = event.asStartElement().getName().getLocalPart();
3271
			return htmlList.contains(tag);
3272
		} else if (event.isEndElement()) {
3273
			String tag = event.asEndElement().getName().getLocalPart();
3274
			return htmlList.contains(tag);
3275
		} else {
3276
			return false;
3277
		}
3278

    
3279
	}
3280

    
3281
	private TextData handleChar(MarkupImportState state, XMLEventReader reader,
3282
			XMLEvent parentEvent) throws XMLStreamException {
3283
		String classValue = getClassOnlyAttribute(parentEvent);
3284
		Feature feature = makeFeature(classValue, state, parentEvent);
3285

    
3286
		String text = "";
3287
		while (reader.hasNext()) {
3288
			XMLEvent next = readNoWhitespace(reader);
3289
			if (isMyEndingElement(next, parentEvent)) {
3290
				TextData textData = TextData.NewInstance(feature);
3291
				textData.putText(Language.DEFAULT(), text);
3292
				return textData;
3293
			} else if (isStartingElement(next, FIGURE_REF)) {
3294
				//TODO
3295
				handleNotYetImplementedElement(next);
3296
			} else if (isEndingElement(next, FIGURE_REF)) {
3297
				//TODO
3298
				popUnimplemented(next.asEndElement());
3299
			} else if (next.isStartElement()) {
3300
				if (isStartingElement(next, ANNOTATION)) {
3301
					handleNotYetImplementedElement(next);
3302
				} else if (isStartingElement(next, ITALICS)) {
3303
					handleNotYetImplementedElement(next);
3304
				} else if (isStartingElement(next, BOLD)) {
3305
					handleNotYetImplementedElement(next);
3306
				} else {
3307
					handleUnexpectedStartElement(next.asStartElement());
3308
				}
3309
			} else if (next.isCharacters()) {
3310
				text += next.asCharacters().getData();
3311
			} else {
3312
				handleUnexpectedEndElement(next.asEndElement());
3313
			}
3314
		}
3315
		throw new IllegalStateException("RefPart has no closing tag");
3316
	}
3317

    
3318
	/**
3319
	 * @param classValue
3320
	 * @param state
3321
	 * @param parentEvent
3322
	 * @return
3323
	 * @throws UndefinedTransformerMethodException
3324
	 */
3325
	private Feature makeFeature(String classValue, MarkupImportState state, XMLEvent parentEvent) {
3326
		UUID uuid;
3327
		try {
3328
			Feature feature = state.getTransformer().getFeatureByKey(classValue);
3329
			if (feature != null) {
3330
				return feature;
3331
			}
3332
			uuid = state.getTransformer().getFeatureUuid(classValue);
3333
			if (uuid == null) {
3334
				// TODO
3335
				String message = "Uuid is not defined for %s";
3336
				message = String.format(message, classValue);
3337
				fireWarningEvent(message, parentEvent, 8);
3338
			}
3339
			String featureText = StringUtils.capitalize(classValue);
3340

    
3341
			// TODO eFlora vocabulary
3342
			TermVocabulary<Feature> voc = null;
3343
			feature = getFeature(state, uuid, featureText, featureText, classValue, voc);
3344
			if (feature == null) {
3345
				throw new NullPointerException(classValue + " not recognized as a feature");
3346
			}
3347
			return feature;
3348
		} catch (Exception e) {
3349
			String message = "Could not create feature for %s: %s";
3350
			message = String.format(message, classValue, e.getMessage());
3351
			fireWarningEvent(message, parentEvent, 4);
3352
			return Feature.UNKNOWN();
3353
		}
3354
	}
3355

    
3356
	/**
3357
	 * This comes from the old version, needs to be checked on need
3358
	 * 
3359
	 * @param state
3360
	 */
3361
	private void doAllTheOldOtherStuff(MarkupImportState state) {
3362
		state.putTree(null, null);
3363
		if (unmatchedLeads == null) {
3364
			unmatchedLeads = UnmatchedLeads.NewInstance();
3365
		}
3366
		state.setUnmatchedLeads(unmatchedLeads);
3367

    
3368
		// TransactionStatus tx = startTransaction();
3369
		unmatchedLeads.saveToSession(getPolytomousKeyNodeService());
3370

    
3371
		// TODO generally do not store the reference object in the config
3372
		Reference sourceReference = state.getConfig().getSourceReference();
3373
		getReferenceService().saveOrUpdate(sourceReference);
3374
	}
3375

    
3376
	/*
3377
	 * (non-Javadoc)
3378
	 * 
3379
	 * @see
3380
	 * eu.etaxonomy.cdm.io.common.CdmIoBase#isIgnore(eu.etaxonomy.cdm.io.common
3381
	 * .IImportConfigurator)
3382
	 */
3383
	protected boolean isIgnore(MarkupImportState state) {
3384
		return !state.getConfig().isDoTaxa();
3385
	}
3386

    
3387
}
(4-4/11)