Project

General

Profile

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

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

    
12
import java.util.HashMap;
13
import java.util.Map;
14

    
15
import javax.xml.stream.XMLEventReader;
16
import javax.xml.stream.XMLStreamException;
17
import javax.xml.stream.events.Attribute;
18
import javax.xml.stream.events.StartElement;
19
import javax.xml.stream.events.XMLEvent;
20

    
21
import org.apache.commons.lang.StringUtils;
22
import org.apache.log4j.Logger;
23

    
24
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
25
import eu.etaxonomy.cdm.model.common.OriginalSourceType;
26
import eu.etaxonomy.cdm.model.common.TimePeriod;
27
import eu.etaxonomy.cdm.model.description.Feature;
28
import eu.etaxonomy.cdm.model.description.TaxonDescription;
29
import eu.etaxonomy.cdm.model.description.TextData;
30
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
31
import eu.etaxonomy.cdm.model.name.INonViralName;
32
import eu.etaxonomy.cdm.model.name.NameTypeDesignationStatus;
33
import eu.etaxonomy.cdm.model.name.NomenclaturalStatus;
34
import eu.etaxonomy.cdm.model.name.NomenclaturalStatusType;
35
import eu.etaxonomy.cdm.model.name.Rank;
36
import eu.etaxonomy.cdm.model.name.TaxonName;
37
import eu.etaxonomy.cdm.model.reference.IArticle;
38
import eu.etaxonomy.cdm.model.reference.IBook;
39
import eu.etaxonomy.cdm.model.reference.IJournal;
40
import eu.etaxonomy.cdm.model.reference.Reference;
41
import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
42
import eu.etaxonomy.cdm.model.reference.ReferenceType;
43
import eu.etaxonomy.cdm.model.taxon.SynonymType;
44
import eu.etaxonomy.cdm.model.taxon.Taxon;
45
import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
46
import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
47
import eu.etaxonomy.cdm.strategy.parser.NameTypeParser;
48
import eu.etaxonomy.cdm.strategy.parser.TimePeriodParser;
49

    
50
/**
51
 * @author a.mueller
52
 * @since 30.05.2012
53
 *
54
 */
55
public class MarkupNomenclatureImport extends MarkupImportBase {
56
	@SuppressWarnings("unused")
57
	private static final Logger logger = Logger.getLogger(MarkupNomenclatureImport.class);
58

    
59

    
60
//	private NonViralNameParserImpl parser = new NonViralNameParserImpl();
61

    
62
	private final MarkupSpecimenImport specimenImport;
63

    
64
	public MarkupNomenclatureImport(MarkupDocumentImport docImport, MarkupSpecimenImport specimenImport) {
65
		super(docImport);
66
		this.specimenImport = specimenImport;
67
	}
68

    
69
	public void handleNomenclature(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent)
70
			throws XMLStreamException {
71
		checkNoAttributes(parentEvent);
72

    
73
		while (reader.hasNext()) {
74
			XMLEvent next = readNoWhitespace(reader);
75
			if (isMyEndingElement(next, parentEvent)) {
76
				return;
77
			} else if (isStartingElement(next, HOMOTYPES)) {
78
				handleHomotypes(state, reader, next.asStartElement());
79
			} else if (isStartingElement(next, NOMENCLATURAL_NOTES)) {
80
				handleAmbigousManually(state, reader, next.asStartElement());
81
			} else  {
82
				fireSchemaConflictEventExpectedStartTag(HOMOTYPES, reader);
83
				state.setUnsuccessfull();
84
			}
85
		}
86
		return;
87
	}
88

    
89

    
90
	private void handleHomotypes(MarkupImportState state,
91
			XMLEventReader reader, StartElement parentEvent)
92
			throws XMLStreamException {
93
		checkNoAttributes(parentEvent);
94

    
95
		HomotypicalGroup homotypicalGroup = null;
96

    
97
		boolean hasNom = false;
98
		while (reader.hasNext()) {
99
			XMLEvent next = readNoWhitespace(reader);
100
			if (isMyEndingElement(next, parentEvent)) {
101
				checkMandatoryElement(hasNom, parentEvent, NOM);
102
				state.setLatestAuthorInHomotype(null);
103
				return;
104
			} else if (isEndingElement(next, NAME_TYPE)) {
105
				state.setNameType(false);
106
			} else if (isStartingElement(next, NOM)) {
107
				INonViralName name = handleNom(state, reader, next, homotypicalGroup);
108
				homotypicalGroup = name.getHomotypicalGroup();
109
				hasNom = true;
110
			} else if (isStartingElement(next, NAME_TYPE)) {
111
				state.setNameType(true);
112
				handleNameType(state, reader, next, homotypicalGroup);
113
			} else if (isStartingElement(next, SPECIMEN_TYPE)) {
114
				specimenImport.handleSpecimenType(state, reader, next, homotypicalGroup);
115
			} else if (isStartingElement(next, NOTES)) {
116
				handleNotYetImplementedElement(next);
117
			} else {
118
				handleUnexpectedElement(next);
119
			}
120
		}
121
		state.setLatestAuthorInHomotype(null);
122
		// TODO handle missing end element
123
		throw new IllegalStateException("Homotypes has no closing tag");
124

    
125
	}
126

    
127
	private void handleNameType(MarkupImportState state, XMLEventReader reader,
128
			XMLEvent parentEvent, HomotypicalGroup homotypicalGroup)
129
			throws XMLStreamException {
130
		Map<String, Attribute> attributes = getAttributes(parentEvent);
131
		String typeStatus = getAndRemoveAttributeValue(attributes, TYPE_STATUS);
132
		checkNoAttributes(attributes, parentEvent);
133

    
134
		NameTypeDesignationStatus status;
135
		try {
136
			status = NameTypeParser.parseNameTypeStatus(typeStatus);
137
		} catch (UnknownCdmTypeException e) {
138
			String message = "Type status could not be recognized: %s";
139
			message = String.format(message, typeStatus);
140
			fireWarningEvent(message, parentEvent, 4);
141
			status = null;
142
		}
143

    
144
		boolean hasNom = false;
145
		while (reader.hasNext()) {
146
			XMLEvent next = readNoWhitespace(reader);
147
			if (next.isEndElement()) {
148
				if (isMyEndingElement(next, parentEvent)) {
149
					checkMandatoryElement(hasNom, parentEvent.asStartElement(),
150
							NOM);
151
					state.setNameType(false);
152
					return;
153
				} else {
154
					if (isEndingElement(next, ACCEPTED_NAME)) {
155
						// NOT YET IMPLEMENTED
156
						popUnimplemented(next.asEndElement());
157
					} else {
158
						handleUnexpectedEndElement(next.asEndElement());
159
					}
160
				}
161
			} else if (next.isStartElement()) {
162
				if (isStartingElement(next, NOM)) {
163
					// TODO should we check if the type is always a species, is
164
					// this a rule?
165
					TaxonName speciesName = TaxonName.castAndDeproxy(
166
					        handleNom(state, reader, next, null));
167
					for (TaxonName name : homotypicalGroup
168
							.getTypifiedNames()) {
169
						name.addNameTypeDesignation(speciesName, null, null,
170
								null, status, false, false, false, false);
171
					}
172
					hasNom = true;
173
				} else if (isStartingElement(next, ACCEPTED_NAME)) {
174
					handleNotYetImplementedElement(next);
175
				} else {
176
					handleUnexpectedStartElement(next);
177
				}
178
			} else {
179
				handleUnexpectedElement(next);
180
			}
181
		}
182
		// TODO handle missing end element
183
		throw new IllegalStateException("Homotypes has no closing tag");
184

    
185
	}
186

    
187
	/**
188
	 * Creates the name defined by a nom tag. Adds it to the given homotypical
189
	 * group (if not null).
190
	 *
191
	 * @param state
192
	 * @param reader
193
	 * @param parentEvent
194
	 * @param homotypicalGroup
195
	 * @return
196
	 * @throws XMLStreamException
197
	 */
198
	private INonViralName handleNom(MarkupImportState state, XMLEventReader reader,
199
			XMLEvent parentEvent, HomotypicalGroup homotypicalGroup) throws XMLStreamException {
200
		boolean isSynonym = false;
201
		boolean isNameType = state.isNameType();
202
		// attributes
203
		Map<String, Attribute> attributes = getAttributes(parentEvent);
204
        boolean isMisidentification = getAndRemoveBooleanAttributeValue(parentEvent, attributes, "misidentification", false);
205
		String classValue = getAndRemoveRequiredAttributeValue(parentEvent, attributes, "class");
206
        checkNoAttributes(attributes, parentEvent);
207

    
208
		INonViralName name;
209
		TaxonRelationship misappliedRelation = null;
210
		if (isMisidentification) {
211
            if (isNameType || ACCEPTED.equalsIgnoreCase(classValue) ){
212
                fireWarningEvent("Misidentification only defined for synonyms", parentEvent, 4);
213
            }
214
            name = createNameByCode(state, null);
215
            Taxon acc = state.getCurrentTaxon();
216
            Taxon misapplied = Taxon.NewInstance(name, null);
217
            misappliedRelation = acc.addMisappliedName(misapplied, null, null);
218
        }else if (!isNameType && ACCEPTED.equalsIgnoreCase(classValue)) {
219
			isSynonym = false;
220
			name = createName(state, homotypicalGroup, isSynonym);
221
		} else if (!isNameType && SYNONYM.equalsIgnoreCase(classValue)) {
222
            isSynonym = true;
223
            name = createName(state, homotypicalGroup, isSynonym);
224
        } else if (isNameType && NAME_TYPE.equalsIgnoreCase(classValue)) {
225
			// TODO do we need to define the rank here?
226
			name = createNameByCode(state, null);
227
		} else {
228
			fireUnexpectedAttributeValue(parentEvent, CLASS, classValue);
229
			name = createNameByCode(state, null);
230
		}
231

    
232
		Map<String, String> nameMap = new HashMap<>();
233
		String text = "";
234

    
235
		boolean nameFilled = false;
236
		state.setNameStatus(null);
237
		while (reader.hasNext()) {
238
			XMLEvent next = readNoWhitespace(reader);
239
			if (isMyEndingElement(next, parentEvent)) {
240
				// fill the name with all data gathered, if not yet done before
241
				if (nameFilled == false){
242
					fillName(state, nameMap, name, misappliedRelation, next);
243
				}
244
				handleNomText(state, parentEvent, text, isNameType);
245
				state.getDeduplicationHelper(docImport).replaceAuthorNamesAndNomRef(state, name);
246
				handleNameStatus(state, name, next);
247
				state.setNameStatus(null);
248
		        return name;
249
			} else if (isEndingElement(next, ANNOTATION)) {
250
				// NOT YET IMPLEMENTED //TODO test
251
				// handleSimpleAnnotation
252
				popUnimplemented(next.asEndElement());
253
			}else if (isStartingElement(next, FULL_NAME)) {
254
				handleFullName(state, reader, name, next);
255
			} else if (isStartingElement(next, NUM)) {
256
				handleNomNum(state, reader, next);
257
			} else if (isStartingElement(next, NAME)) {
258
				handleName(state, reader, next, nameMap);
259
			} else if (isStartingElement(next, CITATION)) {
260
				//we need to fill the name here to have nomenclatural author available for the following citations
261
				fillName(state, nameMap, name, misappliedRelation, next);
262
				nameFilled = true;
263
				handleCitation(state, reader, next, name,  misappliedRelation);
264
			} else if (next.isCharacters()) {
265
				text += next.asCharacters().getData();
266
			} else if (isStartingElement(next, HOMONYM)) {
267
				handleNotYetImplementedElement(next);
268
			} else if (isStartingElement(next, NOTES)) {
269
				handleNotYetImplementedElement(next);
270
			} else if (isStartingElement(next, NOMENCLATURAL_NOTES)) {
271
                handleNotYetImplementedElement(next);
272
            } else if (isStartingElement(next, ANNOTATION)) {
273
				handleNotYetImplementedElement(next);
274
			} else {
275
				handleUnexpectedElement(next);
276
			}
277
		}
278
		throw new IllegalStateException("Nom has no closing tag");
279
	}
280

    
281
	/**
282
     * @param state
283
     * @param name
284
     * @param next
285
     */
286
    private void handleNameStatus(MarkupImportState state, INonViralName name, XMLEvent next) {
287
        if (isNotBlank(state.getNameStatus())){
288
            String nameStatus = state.getNameStatus().trim();
289
            try {
290
                NomenclaturalStatusType nomStatusType = NomenclaturalStatusType
291
                        .getNomenclaturalStatusTypeByAbbreviation(nameStatus, name);
292
                name.addStatus(NomenclaturalStatus.NewInstance(nomStatusType));
293
            } catch (UnknownCdmTypeException e) {
294
                String message = "Status '%s' could not be recognized";
295
                message = String.format(message, nameStatus);
296
                fireWarningEvent(message, next, 4);
297
            }
298
        }
299
    }
300

    
301
    /**
302
	 * Handles appearance of text within <nom> tags.
303
	 * Usually this is not expected except for some information that is already handled
304
	 * elsewhere, e.g. the string Nametype is holding information that is available already
305
	 * via the surrounding nametype tag. Therefore this information can be neglected.
306
	 * This method is open for upcoming cases which need to be handled.
307
	 * @param state
308
	 * @param event
309
	 * @param text
310
	 * @param isNameType
311
	 */
312
	private void handleNomText(MarkupImportState state, XMLEvent event, String text, boolean isNameType) {
313
		if (isBlank(text)){
314
			return;
315
		}
316
		text = text.trim();
317
		//neglect known redundant strings
318
		if (isNameType && (text.matches("(?i)^Esp[\u00E8\u00C8]ce[·\\-\\s]type\\:$")
319
							|| charIsSimpleType(text) )){
320
			return;
321
		}//neglect meaningless punctuation
322
		else if (isPunctuation(text)){
323
			return;
324
		}//neglect mea
325
		else if (isPunctuation(text)){
326
			return;
327
		}else{
328
			String message = "Unhandled text in <nom> tag: \"%s\"";
329
			fireWarningEvent(String.format(message, text), event, 4);
330
		}
331
	}
332

    
333
	/**
334
	 * @param state
335
	 * @param reader
336
	 * @param next
337
	 * @throws XMLStreamException
338
	 */
339
	private void handleNomNum(MarkupImportState state, XMLEventReader reader,
340
			XMLEvent next) throws XMLStreamException {
341
		String num = getCData(state, reader, next);
342
		num = num.replace(".", "");
343
		num = num.replace(")", "");
344
		if (StringUtils.isNotBlank(num)) {
345
			if (state.getCurrentTaxonNum() != null
346
					&& !state.getCurrentTaxonNum().equals(num)) {
347
				String message = "Taxontitle num and homotypes/nom/num differ ( %s <-> %s ). I use the later one.";
348
				message = String.format(message,
349
						state.getCurrentTaxonNum(), num);
350
				fireWarningEvent(message, next, 4);
351
			}
352
			state.setCurrentTaxonNum(num);
353
		}
354
	}
355

    
356

    
357

    
358
	private void handleName(MarkupImportState state, XMLEventReader reader,
359
			XMLEvent parentEvent, Map<String, String> nameMap)
360
			throws XMLStreamException {
361
		String classValue = getClassOnlyAttribute(parentEvent);
362

    
363
		String text = "";
364
		while (reader.hasNext()) {
365
			XMLEvent next = readNoWhitespace(reader);
366
			if (isMyEndingElement(next, parentEvent)) {
367
				nameMap.put(classValue, text);
368
				return;
369
			} else if (isStartingElement(next, ANNOTATION)) {
370
				handleNotYetImplementedElement(next); // TODO test handleSimpleAnnotation
371
	         } else if (isStartingElement(next, FOOTNOTE_REF)) {
372
	                handleNotYetImplementedElement(next);
373
	         } else if (next.isCharacters()) {
374
				text += next.asCharacters().getData();
375
			} else {
376
				handleUnexpectedElement(next);
377
			}
378
		}
379
		throw new IllegalStateException("name has no closing tag");
380
	}
381

    
382
	private void fillName(MarkupImportState state, Map<String, String> nameMap,
383
			INonViralName name, TaxonRelationship misappliedRel, XMLEvent event) {
384

    
385
		// Ranks: family, subfamily, tribus, genus, subgenus, section,
386
		// subsection, species, subspecies, variety, subvariety, forma
387
		// infrank, paraut, author, infrparaut, infraut, status, notes
388

    
389
		String infrank = getAndRemoveMapKey(nameMap, INFRANK);
390
		String authorStr = getAndRemoveMapKey(nameMap, AUTHOR);
391
		String paraut = getAndRemoveMapKey(nameMap, PARAUT);
392

    
393
		String infrParAut = getAndRemoveMapKey(nameMap, INFRPARAUT);
394
		String infrAut = getAndRemoveMapKey(nameMap, INFRAUT);
395

    
396
		String statusStr = getAndRemoveMapKey(nameMap, STATUS);
397
		String notes = getAndRemoveMapKey(nameMap, NOTES);
398

    
399
		if (misappliedRel != null && authorStr != null && authorStr.startsWith("auct.")){
400
		    misappliedRel.getFromTaxon().setAppendedPhrase(authorStr);
401
		    authorStr = null;
402
		}
403

    
404
		if (!name.isProtectedTitleCache()) { // otherwise fullName
405

    
406
			makeRankDecision(state, nameMap, name, event, infrank);
407

    
408
			// test consistency of rank and authors
409
			testRankAuthorConsistency(name, event, authorStr, paraut,infrParAut, infrAut);
410

    
411
			// authors
412
			makeNomenclaturalAuthors(state, event, name, authorStr, paraut, infrParAut, infrAut);
413
		}
414

    
415
		// status
416
		// TODO handle pro parte, pro syn. etc.
417
		if (isNotBlank(statusStr)) {
418
			String proPartePattern = "(pro parte|p.p.)";
419
			if (statusStr.matches(proPartePattern)) {
420
				state.setProParte(true);
421
			}
422
			try {
423
				// TODO handle trim earlier
424
				statusStr = statusStr.trim();
425
				NomenclaturalStatusType nomStatusType = NomenclaturalStatusType
426
				        .getNomenclaturalStatusTypeByAbbreviation(statusStr, name);
427
				name.addStatus(NomenclaturalStatus.NewInstance(nomStatusType));
428
			} catch (UnknownCdmTypeException e) {
429
				String message = "Status '%s' could not be recognized";
430
				message = String.format(message, statusStr);
431
				fireWarningEvent(message, event, 4);
432
			}
433
		}
434

    
435
		// notes
436
		if (StringUtils.isNotBlank(notes)) {
437
			handleNotYetImplementedAttributeValue(event, CLASS, NOTES);
438
		}
439

    
440
		return;
441
	}
442

    
443
	/**
444
     * @param statusStr
445
     * @return
446
     */
447
    private String normalizeStatus(String statusStr) {
448
        if (statusStr == null){
449
            return null;
450
        }else if (statusStr.equals("nomen")){
451
            statusStr = "nom. nud.";
452
        }
453
        return statusStr.trim();
454
    }
455

    
456
    /**
457
	 * @param state
458
	 * @param nameMap
459
	 * @param name
460
	 * @param event
461
	 * @param infrankStr
462
	 */
463
	private void makeRankDecision(MarkupImportState state,
464
			Map<String, String> nameMap, INonViralName name, XMLEvent event,
465
			String infrankStr) {
466
		// TODO ranks
467
		for (String key : nameMap.keySet()) {
468
			Rank rank = makeRank(state, key, false);
469
			if (rank == null) {
470
				handleNotYetImplementedAttributeValue(event, CLASS, key);
471
			} else {
472
				if (name.getRank() == null || rank.isLower(name.getRank())) {
473
					name.setRank(rank);
474
				}
475
				String value = nameMap.get(key);
476
				if (rank.isSupraGeneric() || rank.isGenus()) {
477
					if ((key.equalsIgnoreCase(GENUS_ABBREVIATION)
478
							&& isNotBlank(state.getLatestGenusEpithet()) || isGenusAbbrev(
479
								value, state.getLatestGenusEpithet()))) {
480
						value = state.getLatestGenusEpithet();
481
					}
482
					name.setGenusOrUninomial(toFirstCapital(value));
483
				} else if (rank.isInfraGeneric()) {
484
					name.setInfraGenericEpithet(toFirstCapital(value));
485
				} else if (rank.isSpecies()) {
486
					if (state.getConfig().isAllowCapitalSpeciesEpithet()
487
							&& isFirstCapitalWord(value)) { // capital letters
488
															// are allowed for
489
															// species epithet
490
															// in case of person
491
															// names (e.g.
492
															// Manilkara
493
															// Welwitschii Engl.
494
						name.setSpecificEpithet(value);
495
					} else {
496
						name.setSpecificEpithet(value.toLowerCase());
497
					}
498
				} else if (rank.isInfraSpecific()) {
499
					name.setInfraSpecificEpithet(value.toLowerCase());
500
				} else {
501
					String message = "Invalid rank '%s'. Can't decide which epithet to fill with '%s'";
502
					message = String.format(message, rank.getTitleCache(),
503
							value);
504
					fireWarningEvent(message, event, 4);
505
				}
506
			}
507

    
508
		}
509
		// handle given infrank marker
510
		if (StringUtils.isNotBlank(infrankStr)) {
511
			Rank infRank = makeRank(state, infrankStr, true);
512

    
513
			if (infRank == null) {
514
				String message = "Infrank '%s' rank not recognized";
515
				message = String.format(message, infrankStr);
516
				fireWarningEvent(message, event, 4);
517
			} else {
518
				if (name.getRank() == null) {
519
					name.setRank(infRank);
520
				} else if (infRank.isLower(name.getRank())) {
521
					String message = "InfRank '%s' is lower than existing rank ";
522
					message = String.format(message, infrankStr);
523
					fireWarningEvent(message, event, 2);
524
					name.setRank(infRank);
525
				} else if (infRank.equals(name.getRank())) {
526
					// nothing
527
				} else {
528
					String message = "InfRank '%s' is higher than existing rank ";
529
					message = String.format(message, infrankStr);
530
					fireWarningEvent(message, event, 2);
531
				}
532
			}
533
		}
534
	}
535

    
536
	/**
537
	 * @param state
538
	 * @param name
539
	 * @param event
540
	 * @param authorStr
541
	 * @param paraut
542
	 * @param infrParAut
543
	 * @param infrAut
544
	 */
545
	private void makeNomenclaturalAuthors(MarkupImportState state, XMLEvent event, INonViralName name,
546
			String authorStr, String paraut, String infrParAut, String infrAut) {
547
		if (name.getRank() != null && name.getRank().isInfraSpecific()) {
548
			if (StringUtils.isNotBlank(infrAut)) {
549
				TeamOrPersonBase<?>[] authorAndEx = authorAndEx(state, infrAut, event);
550
				name.setCombinationAuthorship(authorAndEx[0]);
551
				name.setExCombinationAuthorship(authorAndEx[1]);
552
			}
553
			if (StringUtils.isNotBlank(infrParAut)) {
554
				TeamOrPersonBase<?>[] authorAndEx = authorAndEx(state, infrParAut,event);
555
				name.setBasionymAuthorship(authorAndEx[0]);
556
				name.setExBasionymAuthorship(authorAndEx[1]);
557
			}
558
		} else {
559
			if (name.getRank() == null) {
560
				String message = "No rank defined. Check correct usage of authors!";
561
				fireWarningEvent(message, event, 4);
562
				if (isNotBlank(infrParAut) || isNotBlank(infrAut)) {
563
					authorStr = infrAut;
564
					paraut = infrParAut;
565
				}
566
			}
567
			if (StringUtils.isNotBlank(authorStr)) {
568
				TeamOrPersonBase<?>[] authorAndEx = authorAndEx(state, authorStr,	event);
569
				name.setCombinationAuthorship(authorAndEx[0]);
570
				name.setExCombinationAuthorship(authorAndEx[1]);
571
			}
572
			if (StringUtils.isNotBlank(paraut)) {
573
				TeamOrPersonBase<?>[] authorAndEx = authorAndEx(state, paraut, event);
574
				name.setBasionymAuthorship(authorAndEx[0]);
575
				name.setExBasionymAuthorship(authorAndEx[1]);
576
			}
577
		}
578

    
579
		//remember author for following citations
580
		state.setLatestAuthorInHomotype(name.getCombinationAuthorship());
581
	}
582

    
583
	private TeamOrPersonBase<?>[] authorAndEx(MarkupImportState state, String authorAndEx, XMLEvent xmlEvent) {
584
		authorAndEx = authorAndEx.trim();
585
		TeamOrPersonBase<?>[] result = new TeamOrPersonBase[2];
586

    
587
		String[] split = authorAndEx.split("\\sex\\s");
588
		if (split.length > 2) {
589
			String message = "There is more then 1 ' ex ' in author string. Can't separate author and ex-author";
590
			fireWarningEvent(message, xmlEvent, 4);
591
			result[0] = createAuthor(state, authorAndEx);
592
		} else if (split.length == 2) {
593
			result[0] = createAuthor(state, split[1]);
594
			result[1] = createAuthor(state, split[0]);
595
		} else {
596
			result[0] = createAuthor(state, split[0]);
597
		}
598
		return result;
599
	}
600

    
601
	/**
602
	 * Returns the (empty) name with the correct homotypical group depending on
603
	 * the taxon status and in case of synonym adds it to the taxon.
604
	 * Throws NPE if no currentTaxon is set in state.
605
	 *
606
	 * @param state
607
	 * @param homotypicalGroup
608
	 * @param isSynonym
609
	 * @return
610
	 */
611
	private INonViralName createName(MarkupImportState state,
612
			HomotypicalGroup homotypicalGroup, boolean isSynonym) {
613
		INonViralName name;
614
		Taxon taxon = state.getCurrentTaxon();
615
		if (isSynonym) {
616
			Rank defaultRank = Rank.SPECIES(); // can be any
617
			name = createNameByCode(state, defaultRank);
618
			if (homotypicalGroup != null) {
619
				name.setHomotypicalGroup(homotypicalGroup);
620
			}
621
			SynonymType synonymType = SynonymType.HETEROTYPIC_SYNONYM_OF();
622
			if (taxon.getHomotypicGroup().equals(homotypicalGroup)) {
623
				synonymType = SynonymType.HOMOTYPIC_SYNONYM_OF();
624
			}
625
			taxon.addSynonymName(TaxonName.castAndDeproxy(name), synonymType);
626
		} else {
627
			name = taxon.getName();
628
		}
629
		return name;
630
	}
631

    
632
	private void handleCitation(MarkupImportState state, XMLEventReader reader,
633
			XMLEvent parentEvent, INonViralName nvn, TaxonRelationship misappliedRel) throws XMLStreamException {
634
		String classValue = getClassOnlyAttribute(parentEvent);
635

    
636
		TaxonName name = TaxonName.castAndDeproxy(nvn);
637
		state.setCitation(true);
638
		boolean hasRefPart = false;
639
		Map<String, String> refMap = new HashMap<>();
640
		while (reader.hasNext()) {
641
			XMLEvent next = readNoWhitespace(reader);
642
			if (isMyEndingElement(next, parentEvent)) {
643
				checkMandatoryElement(hasRefPart, parentEvent.asStartElement(), REF_PART);
644
				Reference reference = createReference(state, refMap, next);
645
				String microReference = refMap.get(DETAILS);
646
				doCitation(state, name, classValue, misappliedRel,
647
				        reference, microReference, parentEvent);
648
				state.setCitation(false);
649
				return;
650
			} else if (isStartingElement(next, REF_PART)) {
651
				handleRefPart(state, reader, next, refMap);
652
				hasRefPart = true;
653
			} else {
654
				handleUnexpectedElement(next);
655
			}
656
		}
657
		throw new IllegalStateException("Citation has no closing tag");
658

    
659
	}
660

    
661

    
662
	private void doCitation(MarkupImportState state, TaxonName name,
663
			String classValue, TaxonRelationship misappliedRel,
664
			Reference reference, String microCitation,
665
			XMLEvent parentEvent) {
666
	    reference = state.getDeduplicationHelper(docImport).getExistingReference(state, reference);
667
	    if (misappliedRel != null){
668
	        if (!PUBLICATION.equalsIgnoreCase(classValue)){
669
                fireWarningEvent("'Usage' not handled correctly for misidentifications", parentEvent, 4);
670
            }else{
671
                Taxon misappliedTaxon = misappliedRel.getFromTaxon();
672
                misappliedTaxon.setSec(reference);
673
                misappliedTaxon.setSecMicroReference(microCitation);
674
                misappliedRel.setCitation(state.getConfig().getSourceReference());
675
            }
676
	    }else if (PUBLICATION.equalsIgnoreCase(classValue)) {
677
			name.setNomenclaturalReference(reference);
678
			name.setNomenclaturalMicroReference(microCitation);
679
		} else if (USAGE.equalsIgnoreCase(classValue)) {
680
			Taxon taxon = state.getCurrentTaxon();
681
			TaxonDescription td = getDefaultTaxonDescription(taxon, false, true, state.getConfig().getSourceReference());
682
			TextData citation = TextData.NewInstance(Feature.CITATION());
683
			// TODO name used in source
684
			citation.addSource(OriginalSourceType.PrimaryTaxonomicSource, null, null, reference, microCitation, name, null);
685
			td.addElement(citation);
686
		} else if (TYPE.equalsIgnoreCase(classValue)) {
687
			handleNotYetImplementedAttributeValue(parentEvent, CLASS, classValue);
688
		} else {
689
			// TODO Not yet implemented
690
			handleNotYetImplementedAttributeValue(parentEvent, CLASS, classValue);
691
		}
692
	}
693

    
694
	/**
695
	 * Tests if the names rank is consistent with the given author strings.
696
	 * NOTE: Tags for authors are differ depending on the rank.
697
	 *
698
	 * @param name
699
	 * @param event
700
	 * @param authorStr
701
	 * @param paraut
702
	 * @param infrParAut
703
	 * @param infrAut
704
	 */
705
	private void testRankAuthorConsistency(INonViralName name, XMLEvent event,
706
			String authorStr, String paraut, String infrParAut, String infrAut) {
707
		if (name.getRank() == null) {
708
			return;
709
		}
710
		if (name.getRank().isInfraSpecific()) {
711
			if (StringUtils.isBlank(infrParAut)
712
					&& StringUtils.isBlank(infrAut) // was isNotBlank before
713
													// 29.5.2012
714
					&& (StringUtils.isNotBlank(paraut) || StringUtils
715
							.isNotBlank(authorStr)) && !name.isAutonym()) {
716
				String message = "Rank is infraspecicific but has only specific or higher author(s)";
717
				fireWarningEvent(message, event, 4);
718
			}
719
		} else {
720
			// is not infraspecific
721
			if (StringUtils.isNotBlank(infrParAut)
722
					|| StringUtils.isNotBlank(infrAut)) {
723
				String message = "Rank is not infraspecicific but name has infra author(s)";
724
				fireWarningEvent(message, event, 4);
725
			}
726
		}
727
	}
728

    
729
	private Reference createReference(MarkupImportState state,
730
			Map<String, String> refMap, XMLEvent parentEvent) {
731
		// TODO
732
		Reference reference;
733

    
734
		String type = getAndRemoveMapKey(refMap, PUBTYPE);
735
		String authorStr = getAndRemoveMapKey(refMap, AUTHOR);
736
		String titleStr = getAndRemoveMapKey(refMap, PUBTITLE);
737
		String titleCache = getAndRemoveMapKey(refMap, PUBFULLNAME);
738
		String volume = getAndRemoveMapKey(refMap, VOLUME);
739
		String edition = getAndRemoveMapKey(refMap, EDITION);
740
		String editors = getAndRemoveMapKey(refMap, EDITORS);
741
		String year = getAndRemoveMapKey(refMap, YEAR);
742
		String pubName = getAndRemoveMapKey(refMap, PUBNAME);
743
		String pages = getAndRemoveMapKey(refMap, PAGES);
744
		String publocation = getAndRemoveMapKey(refMap, PUBLOCATION);
745
		String publisher = getAndRemoveMapKey(refMap, PUBLISHER);
746
		String appendix = getAndRemoveMapKey(refMap, APPENDIX);
747
		String issue = getAndRemoveMapKey(refMap, ISSUE);
748
        String nameStatus = getAndRemoveMapKey(refMap, NAME_STATUS);
749

    
750
		if (state.isCitation()) {
751
			reference = handleCitationSpecific(state, type, authorStr,
752
					titleStr, titleCache, volume, issue, edition, editors, pubName, pages, appendix, refMap, parentEvent);
753

    
754
		} else { // no citation
755
			reference = handleNonCitationSpecific(state, type, authorStr, titleStr,
756
					titleCache, volume, issue, edition, editors, pubName, appendix, pages, parentEvent);
757
		}
758

    
759
		//year
760
		TimePeriod timeperiod = TimePeriodParser.parseString(year);
761
		if (reference.getType().equals(ReferenceType.BookSection)){
762
			reference.getInBook().setDatePublished(timeperiod);
763
		}
764
		reference.setDatePublished(timeperiod);
765

    
766
		//Quickfix for these 2 attributes used in feature.references
767
		Reference inRef = reference.getInReference() == null ? reference : reference.getInReference();
768
		//publocation
769
		if (isNotBlank(publisher)){
770
			inRef.setPublisher(publisher);
771
		}
772

    
773
		//publisher
774
		if (isNotBlank(publocation)){
775
			inRef.setPlacePublished(publocation);
776
		}
777

    
778
		if (isNotBlank(nameStatus)){
779
		    state.setNameStatus(nameStatus);
780
		}
781

    
782
		// TODO
783
		String[] unhandledList = new String[] { ALTERNATEPUBTITLE, NOTES, STATUS };
784
		for (String unhandled : unhandledList) {
785
			String value = getAndRemoveMapKey(refMap, unhandled);
786
			if (isNotBlank(value)) {
787
				this.handleNotYetImplementedAttributeValue(parentEvent, CLASS, unhandled);
788
			}
789
		}
790

    
791
		for (String key : refMap.keySet()) {
792
			if (!DETAILS.equalsIgnoreCase(key)) {
793
				this.fireUnexpectedAttributeValue(parentEvent, CLASS, key);
794
			}
795
		}
796

    
797
		return reference;
798
	}
799

    
800

    
801
	/**
802
	 * Handles references used in the citation tag
803
	 * @param appendix
804
	 * @see #handleNonCitationSpecific(String, String, String, String, String, String, String, String)
805
	 */
806
	private Reference handleCitationSpecific(MarkupImportState state,
807
			String type, String authorStr, String titleStr, String titleCache,
808
			String volume, String issue, String edition, String editors, String pubName,
809
			String pages, String appendix, Map<String, String> refMap, XMLEvent parentEvent) {
810

    
811
		if (titleStr != null){
812
			String message = "Currently it is not expected that a titleStr exists in a citation";
813
			fireWarningEvent(message, parentEvent, 4);
814
		}
815
		if (isBlank(volume) && isNotBlank(issue)){
816
		    String message = "Issue ('"+issue+"') exists but no volume";
817
            fireWarningEvent(message, parentEvent, 4);
818
            volume = issue;
819
		}else if (isNotBlank(issue)){
820
            volume = volume + "("+ issue + ")";
821
        }
822

    
823

    
824
		RefType refType = defineRefTypeForCitation(type, volume, editors, authorStr, pubName, parentEvent);
825
		Reference reference;
826

    
827
		if (isNotBlank(appendix)){
828
		    pubName = pubName == null ?  appendix : (pubName + " " + appendix).replaceAll("  ", " ");
829
		}
830

    
831
		if (refType == RefType.Article) {
832
			IArticle article = ReferenceFactory.newArticle();
833
			if (pubName != null) {
834
				IJournal journal = ReferenceFactory.newJournal();
835
				journal.setTitle(pubName);
836
				article.setInJournal(journal);
837
				article.setVolume(volume);
838
				if (isNotBlank(edition)){
839
					String message = "Article must not have an edition.";
840
					fireWarningEvent(message, parentEvent, 4);
841
				}
842
			}
843
			reference = (Reference) article;
844
		} else if (refType == RefType.BookSection) {
845
			//Book Section
846
			reference = ReferenceFactory.newBookSection();
847
			IBook  book = ReferenceFactory.newBook();
848
			reference.setInBook(book);
849
			book.setTitle(pubName);
850
			book.setVolume(volume);
851
			book.setEdition(edition);
852

    
853
			if (state.getConfig().isUseEditorAsInAuthorWhereNeeded()){
854
				TeamOrPersonBase<?> inAuthor = createAuthor(state, editors);
855
				book.setAuthorship(inAuthor);
856
				editors = null;
857
			}
858
		} else if (refType == RefType.Book){
859
			//Book
860
			reference = ReferenceFactory.newBook();
861
			reference.setTitle(pubName);
862
			reference.setVolume(volume);
863
			reference.setEdition(edition);
864
		}else if (refType == RefType.Generic){
865
			//Generic - undefinable
866
//			String message = "Can't define the type of the reference. Use generic instead";
867
//			fireWarningEvent(message, parentEvent, 4);
868
			reference = ReferenceFactory.newGeneric();
869
			reference.setTitle(pubName);
870
			reference.setEdition(edition);
871

    
872
			//volume indicates an in-reference
873
			if (isNotBlank(volume)){
874
				Reference partOf = ReferenceFactory.newGeneric();
875
				partOf.setVolume(volume);
876
				partOf.setInReference(reference);
877
				reference = partOf;
878
			}
879
		}else if (refType == RefType.LatestUsed){
880
			Reference latestReference = state.getLatestReferenceInHomotype();
881
			if (latestReference == null){
882
				String message = "No former reference available for incomplete citation";
883
				fireWarningEvent(message, parentEvent, 6);
884
				reference = ReferenceFactory.newGeneric();
885
			}else{
886
				if (latestReference.getInReference() != null){
887
					reference = (Reference)latestReference.clone();
888
				}else{
889
					String message = "Latest reference is not an in-reference. This is not yet handled.";
890
					fireWarningEvent(message, parentEvent, 6);
891
					reference = ReferenceFactory.newGeneric();
892
				}
893
			}
894
			reference.setVolume(volume);
895
			if (isNotBlank(edition)){
896
				String message = "Edition not yet handled for incomplete citations";
897
				fireWarningEvent(message, parentEvent, 4);
898
			}
899

    
900
		}else{
901
			String message = "Unhandled reference type: %s" ;
902
			fireWarningEvent(String.format(message, refType.toString()), parentEvent, 8);
903
			reference = ReferenceFactory.newGeneric();
904
		}
905

    
906
		//author
907
		TeamOrPersonBase<?> author;
908
		if (isBlank(authorStr)){
909
			if (refType != RefType.LatestUsed){
910
				author = state.getLatestAuthorInHomotype();
911
				reference.setAuthorship(author);
912
			}
913
		}else{
914
			author = createAuthor(state, authorStr);
915
			state.setLatestAuthorInHomotype(author);
916
			reference.setAuthorship(author);
917
		}
918

    
919

    
920
		//title, titleCache
921
		handleTitlesInCitation(titleStr, titleCache, parentEvent, reference);
922

    
923
		//editors
924
		handleEditorsInCitation(edition, editors, reference, parentEvent);
925

    
926
		//pages
927
		handlePages(state, refMap, parentEvent, reference, pages);
928

    
929
//		state.getDeduplicationHelper(docImport).getExistingReference(state, reference);
930

    
931
		//remember reference for following citation
932
		state.setLatestReferenceInHomotype(reference);
933

    
934
		return reference;
935
	}
936

    
937
	private void handleEditorsInCitation(String edition, String editors, Reference reference, XMLEvent parentEvent) {
938
		//editor
939
		reference.setEditor(editors);
940
		if ( editors != null){
941
			String message = "Citation reference has an editor. This is unusual for a citation reference (appears regularly in <reference> references";
942
			fireWarningEvent(message, parentEvent, 4);
943
		}
944
	}
945

    
946
	private void handleTitlesInCitation(String titleStr, String titleCache,
947
			XMLEvent parentEvent, Reference reference) {
948
		if (isNotBlank(titleStr)){
949
			reference.setTitle(titleStr);
950
		}
951
		//titleCache
952
		if (StringUtils.isNotBlank(titleCache)) {
953
			reference.setTitleCache(titleCache, true);
954
		}
955
		if (titleStr != null || titleCache != null){
956
			String message = "Citation reference has a title or a full title. Both is unusual for a citation reference (appears regularly in <reference> references";
957
			fireWarningEvent(message, parentEvent, 4);
958
		}
959
	}
960

    
961
	private enum RefType{
962
		Article,
963
		BookSection,
964
		Book,
965
		Generic,
966
		LatestUsed
967
	}
968

    
969
	private RefType defineRefTypeForCitation(String type, String volume, String editors,
970
			String authorStr, String pubName, XMLEvent parentEvent) {
971
		if ("journal".equalsIgnoreCase(type)){
972
			return RefType.Article;
973
		}else {
974
			if (editors == null){
975
				//no editors
976
				if (pubName == null){
977
					//looks like we need to use reference info from former citations here
978
					return RefType.LatestUsed;
979
				}else if (volume == null){
980
					return RefType.Book;  //Book must not have in-authors
981
				}else if (IJournal.guessIsJournalName(pubName)){
982
				    return RefType.Article;
983
				}else{
984
					return RefType.Generic;
985
				}
986

    
987
			}else{
988
				//editors
989
				if (pubName != null){
990
					return RefType.BookSection;
991
				}else{
992
					String message = "Unexpected state: Citation has editors but no pubName";
993
					fireWarningEvent(message, parentEvent, 4);
994
					return RefType.Generic;
995
				}
996
			}
997
		}
998
	}
999

    
1000

    
1001
	private void handlePages(MarkupImportState state,
1002
			Map<String, String> refMap, XMLEvent parentEvent,
1003
			Reference reference, String pages) {
1004
		// TODO check if this is handled correctly in FM markup
1005
		boolean switchPages = state.getConfig().isHandlePagesAsDetailWhereNeeded();
1006
		if (switchPages){
1007
			if (pages != null ){
1008
				String detail = refMap.get(DETAILS);
1009
				if (isBlank(detail)){
1010
					if (pages.contains("-")){
1011
						String message = "There is a pages tag with '-'. Unclear if this really means pages";
1012
						fireWarningEvent(message, parentEvent, 8);
1013
						reference.setPages(pages);
1014
					}else{
1015
						//handle pages as detail, this is at least true for Flora Malesiana
1016
						refMap.put(DETAILS, pages);
1017
					}
1018
				}else{
1019
					if (! pages.contains("-")){
1020
						String message = "There are pages and detail available where pages may also hold details information.";
1021
						fireWarningEvent(message, parentEvent, 8);
1022
					}
1023
					reference.setPages(pages);
1024
				}
1025
			}
1026
		}
1027
	}
1028

    
1029
	public Reference handleReference(MarkupImportState state,
1030
			XMLEventReader reader, XMLEvent parentEvent)
1031
			throws XMLStreamException {
1032
		checkNoAttributes(parentEvent);
1033

    
1034
		boolean hasRefPart = false;
1035
		Map<String, String> refMap = new HashMap<String, String>();
1036
		while (reader.hasNext()) {
1037
			XMLEvent next = readNoWhitespace(reader);
1038
			if (isMyEndingElement(next, parentEvent)) {
1039
				checkMandatoryElement(hasRefPart, parentEvent.asStartElement(), REF_PART);
1040
				Reference reference = createReference(state, refMap, next);
1041
				return reference;
1042
			} else if (isStartingElement(next, REF_PART)) {
1043
				handleRefPart(state, reader, next, refMap);
1044
				hasRefPart = true;
1045
			} else {
1046
				handleUnexpectedElement(next);
1047
			}
1048
		}
1049
		// TODO handle missing end element
1050
		throw new IllegalStateException("<Reference> has no closing tag");
1051
	}
1052

    
1053
}
(15-15/19)