Project

General

Profile

Download (35.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.CdmBase;
26
import eu.etaxonomy.cdm.model.common.OriginalSourceType;
27
import eu.etaxonomy.cdm.model.common.TimePeriod;
28
import eu.etaxonomy.cdm.model.description.Feature;
29
import eu.etaxonomy.cdm.model.description.TaxonDescription;
30
import eu.etaxonomy.cdm.model.description.TextData;
31
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
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.NonViralName;
36
import eu.etaxonomy.cdm.model.name.Rank;
37
import eu.etaxonomy.cdm.model.name.TaxonNameBase;
38
import eu.etaxonomy.cdm.model.reference.IArticle;
39
import eu.etaxonomy.cdm.model.reference.IBook;
40
import eu.etaxonomy.cdm.model.reference.IJournal;
41
import eu.etaxonomy.cdm.model.reference.Reference;
42
import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
43
import eu.etaxonomy.cdm.model.reference.ReferenceType;
44
import eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType;
45
import eu.etaxonomy.cdm.model.taxon.Taxon;
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
 * @created 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
				NonViralName<?> 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
					NonViralName<?> speciesName = handleNom(state, reader,
166
							next, null);
167
					for (TaxonNameBase<?, ?> 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 NonViralName<?> handleNom(MarkupImportState state, XMLEventReader reader,
199
			XMLEvent parentEvent, HomotypicalGroup homotypicalGroup) throws XMLStreamException {
200
		boolean isSynonym = false;
201
		boolean isNameType = state.isNameType();
202
		// attributes
203
		String classValue = getClassOnlyAttribute(parentEvent);
204
		NonViralName<?> name;
205
		if (!isNameType && ACCEPTED.equalsIgnoreCase(classValue)) {
206
			isSynonym = false;
207
			name = createName(state, homotypicalGroup, isSynonym);
208
		} else if (!isNameType && SYNONYM.equalsIgnoreCase(classValue)) {
209
			isSynonym = true;
210
			name = createName(state, homotypicalGroup, isSynonym);
211
		} else if (isNameType && NAME_TYPE.equalsIgnoreCase(classValue)) {
212
			// TODO do we need to define the rank here?
213
			name = createNameByCode(state, null);
214
		} else {
215
			fireUnexpectedAttributeValue(parentEvent, CLASS, classValue);
216
			name = createNameByCode(state, null);
217
		}
218

    
219
		Map<String, String> nameMap = new HashMap<String, String>();
220
		String text = "";
221

    
222
		boolean nameFilled = false;
223
		while (reader.hasNext()) {
224
			XMLEvent next = readNoWhitespace(reader);
225
			if (isMyEndingElement(next, parentEvent)) {
226
				// fill the name with all data gathered, if not yet done before
227
				if (nameFilled == false){
228
					fillName(state, nameMap, name, next);
229
				}
230
				handleNomText(state, parentEvent, text, isNameType);
231
				return name;
232
			} else if (isEndingElement(next, ANNOTATION)) {
233
				// NOT YET IMPLEMENTED //TODO test
234
				// handleSimpleAnnotation
235
				popUnimplemented(next.asEndElement());
236
			}else if (isStartingElement(next, FULL_NAME)) {
237
				handleFullName(state, reader, name, next);
238
			} else if (isStartingElement(next, NUM)) {
239
				handleNomNum(state, reader, next);
240
			} else if (isStartingElement(next, NAME)) {
241
				handleName(state, reader, next, nameMap);
242
			} else if (isStartingElement(next, CITATION)) {
243
				//we need to fill the name here to have nomenclatural author available for the following citations
244
				fillName(state, nameMap, name, next);
245
				nameFilled = true;
246
				handleCitation(state, reader, next, name, nameMap);
247
			} else if (next.isCharacters()) {
248
				text += next.asCharacters().getData();
249
			} else if (isStartingElement(next, HOMONYM)) {
250
				handleNotYetImplementedElement(next);
251
			} else if (isStartingElement(next, NOTES)) {
252
				handleNotYetImplementedElement(next);
253
			} else if (isStartingElement(next, NOMENCLATURAL_NOTES)) {
254
                handleNotYetImplementedElement(next);
255
            } else if (isStartingElement(next, ANNOTATION)) {
256
				handleNotYetImplementedElement(next);
257
			} else {
258
				handleUnexpectedElement(next);
259
			}
260
		}
261
		// TODO handle missing end element
262
		throw new IllegalStateException("Nom has no closing tag");
263
	}
264

    
265
	/**
266
	 * Handles appearance of text within <nom> tags.
267
	 * Usually this is not expected except for some information that is already handled
268
	 * elsewhere, e.g. the string Nametype is holding information that is available already
269
	 * via the surrounding nametype tag. Therefore this information can be neglected.
270
	 * This method is open for upcoming cases which need to be handled.
271
	 * @param state
272
	 * @param event
273
	 * @param text
274
	 * @param isNameType
275
	 */
276
	private void handleNomText(MarkupImportState state, XMLEvent event, String text, boolean isNameType) {
277
		if (isBlank(text)){
278
			return;
279
		}
280
		text = text.trim();
281
		//neglect known redundant strings
282
		if (isNameType && (text.matches("(?i)^Esp[\u00E8\u00C8]ce[·\\-\\s]type\\:$")
283
							|| charIsSimpleType(text) )){
284
			return;
285
		}//neglect meaningless punctuation
286
		else if (isPunctuation(text)){
287
			return;
288
		}//neglect mea
289
		else if (isPunctuation(text)){
290
			return;
291
		}else{
292
			String message = "Unhandled text in <nom> tag: \"%s\"";
293
			fireWarningEvent(String.format(message, text), event, 4);
294
		}
295
	}
296

    
297
	/**
298
	 * @param state
299
	 * @param reader
300
	 * @param next
301
	 * @throws XMLStreamException
302
	 */
303
	private void handleNomNum(MarkupImportState state, XMLEventReader reader,
304
			XMLEvent next) throws XMLStreamException {
305
		String num = getCData(state, reader, next);
306
		num = num.replace(".", "");
307
		num = num.replace(")", "");
308
		if (StringUtils.isNotBlank(num)) {
309
			if (state.getCurrentTaxonNum() != null
310
					&& !state.getCurrentTaxonNum().equals(num)) {
311
				String message = "Taxontitle num and homotypes/nom/num differ ( %s <-> %s ). I use the later one.";
312
				message = String.format(message,
313
						state.getCurrentTaxonNum(), num);
314
				fireWarningEvent(message, next, 4);
315
			}
316
			state.setCurrentTaxonNum(num);
317
		}
318
	}
319

    
320

    
321

    
322
	private void handleName(MarkupImportState state, XMLEventReader reader,
323
			XMLEvent parentEvent, Map<String, String> nameMap)
324
			throws XMLStreamException {
325
		String classValue = getClassOnlyAttribute(parentEvent);
326

    
327
		String text = "";
328
		while (reader.hasNext()) {
329
			XMLEvent next = readNoWhitespace(reader);
330
			if (isMyEndingElement(next, parentEvent)) {
331
				nameMap.put(classValue, text);
332
				return;
333
			} else if (isStartingElement(next, ANNOTATION)) {
334
				handleNotYetImplementedElement(next); // TODO test handleSimpleAnnotation
335
	         } else if (isStartingElement(next, FOOTNOTE_REF)) {
336
	                handleNotYetImplementedElement(next);
337
	         } else if (next.isCharacters()) {
338
				text += next.asCharacters().getData();
339
			} else {
340
				handleUnexpectedElement(next);
341
			}
342
		}
343
		throw new IllegalStateException("name has no closing tag");
344
	}
345

    
346
	private void fillName(MarkupImportState state, Map<String, String> nameMap,
347
			NonViralName<?> name, XMLEvent event) {
348

    
349
		// Ranks: family, subfamily, tribus, genus, subgenus, section,
350
		// subsection, species, subspecies, variety, subvariety, forma
351
		// infrank, paraut, author, infrparaut, infraut, status, notes
352

    
353
		String infrank = getAndRemoveMapKey(nameMap, INFRANK);
354
		String authorStr = getAndRemoveMapKey(nameMap, AUTHOR);
355
		String paraut = getAndRemoveMapKey(nameMap, PARAUT);
356

    
357
		String infrParAut = getAndRemoveMapKey(nameMap, INFRPARAUT);
358
		String infrAut = getAndRemoveMapKey(nameMap, INFRAUT);
359

    
360
		String statusStr = getAndRemoveMapKey(nameMap, STATUS);
361
		String notes = getAndRemoveMapKey(nameMap, NOTES);
362

    
363
		if (!name.isProtectedTitleCache()) { // otherwise fullName
364

    
365
			makeRankDecision(state, nameMap, name, event, infrank);
366

    
367
			// test consistency of rank and authors
368
			testRankAuthorConsistency(name, event, authorStr, paraut,infrParAut, infrAut);
369

    
370
			// authors
371
			makeNomenclaturalAuthors(state, event, name, authorStr, paraut, infrParAut, infrAut);
372
		}
373

    
374
		// status
375
		// TODO handle pro parte, pro syn. etc.
376
		if (StringUtils.isNotBlank(statusStr)) {
377
			String proPartePattern = "(pro parte|p.p.)";
378
			if (statusStr.matches(proPartePattern)) {
379
				state.setProParte(true);
380
			}
381
			try {
382
				// TODO handle trim earlier
383
				statusStr = statusStr.trim();
384
				NomenclaturalStatusType nomStatusType = NomenclaturalStatusType.getNomenclaturalStatusTypeByAbbreviation(statusStr, name);
385
				name.addStatus(NomenclaturalStatus.NewInstance(nomStatusType));
386
			} catch (UnknownCdmTypeException e) {
387
				String message = "Status '%s' could not be recognized";
388
				message = String.format(message, statusStr);
389
				fireWarningEvent(message, event, 4);
390
			}
391
		}
392

    
393
		// notes
394
		if (StringUtils.isNotBlank(notes)) {
395
			handleNotYetImplementedAttributeValue(event, CLASS, NOTES);
396
		}
397

    
398
		return;
399
	}
400

    
401
	/**
402
	 * @param state
403
	 * @param nameMap
404
	 * @param name
405
	 * @param event
406
	 * @param infrankStr
407
	 */
408
	private void makeRankDecision(MarkupImportState state,
409
			Map<String, String> nameMap, NonViralName<?> name, XMLEvent event,
410
			String infrankStr) {
411
		// TODO ranks
412
		for (String key : nameMap.keySet()) {
413
			Rank rank = makeRank(state, key, false);
414
			if (rank == null) {
415
				handleNotYetImplementedAttributeValue(event, CLASS, key);
416
			} else {
417
				if (name.getRank() == null || rank.isLower(name.getRank())) {
418
					name.setRank(rank);
419
				}
420
				String value = nameMap.get(key);
421
				if (rank.isSupraGeneric() || rank.isGenus()) {
422
					if ((key.equalsIgnoreCase(GENUS_ABBREVIATION)
423
							&& isNotBlank(state.getLatestGenusEpithet()) || isGenusAbbrev(
424
								value, state.getLatestGenusEpithet()))) {
425
						value = state.getLatestGenusEpithet();
426
					}
427
					name.setGenusOrUninomial(toFirstCapital(value));
428
				} else if (rank.isInfraGeneric()) {
429
					name.setInfraGenericEpithet(toFirstCapital(value));
430
				} else if (rank.isSpecies()) {
431
					if (state.getConfig().isAllowCapitalSpeciesEpithet()
432
							&& isFirstCapitalWord(value)) { // capital letters
433
															// are allowed for
434
															// species epithet
435
															// in case of person
436
															// names (e.g.
437
															// Manilkara
438
															// Welwitschii Engl.
439
						name.setSpecificEpithet(value);
440
					} else {
441
						name.setSpecificEpithet(value.toLowerCase());
442
					}
443
				} else if (rank.isInfraSpecific()) {
444
					name.setInfraSpecificEpithet(value.toLowerCase());
445
				} else {
446
					String message = "Invalid rank '%s'. Can't decide which epithet to fill with '%s'";
447
					message = String.format(message, rank.getTitleCache(),
448
							value);
449
					fireWarningEvent(message, event, 4);
450
				}
451
			}
452

    
453
		}
454
		// handle given infrank marker
455
		if (StringUtils.isNotBlank(infrankStr)) {
456
			Rank infRank = makeRank(state, infrankStr, true);
457

    
458
			if (infRank == null) {
459
				String message = "Infrank '%s' rank not recognized";
460
				message = String.format(message, infrankStr);
461
				fireWarningEvent(message, event, 4);
462
			} else {
463
				if (name.getRank() == null) {
464
					name.setRank(infRank);
465
				} else if (infRank.isLower(name.getRank())) {
466
					String message = "InfRank '%s' is lower than existing rank ";
467
					message = String.format(message, infrankStr);
468
					fireWarningEvent(message, event, 2);
469
					name.setRank(infRank);
470
				} else if (infRank.equals(name.getRank())) {
471
					// nothing
472
				} else {
473
					String message = "InfRank '%s' is higher than existing rank ";
474
					message = String.format(message, infrankStr);
475
					fireWarningEvent(message, event, 2);
476
				}
477
			}
478
		}
479
	}
480

    
481
	/**
482
	 * @param state
483
	 * @param name
484
	 * @param event
485
	 * @param authorStr
486
	 * @param paraut
487
	 * @param infrParAut
488
	 * @param infrAut
489
	 */
490
	private void makeNomenclaturalAuthors(MarkupImportState state, XMLEvent event, NonViralName<?> name,
491
			String authorStr, String paraut, String infrParAut, String infrAut) {
492
		if (name.getRank() != null && name.getRank().isInfraSpecific()) {
493
			if (StringUtils.isNotBlank(infrAut)) {
494
				TeamOrPersonBase<?>[] authorAndEx = authorAndEx(infrAut, event);
495
				name.setCombinationAuthorship(authorAndEx[0]);
496
				name.setExCombinationAuthorship(authorAndEx[1]);
497
			}
498
			if (StringUtils.isNotBlank(infrParAut)) {
499
				TeamOrPersonBase<?>[] authorAndEx = authorAndEx(infrParAut,event);
500
				name.setBasionymAuthorship(authorAndEx[0]);
501
				name.setExBasionymAuthorship(authorAndEx[1]);
502
			}
503
		} else {
504
			if (name.getRank() == null) {
505
				String message = "No rank defined. Check correct usage of authors!";
506
				fireWarningEvent(message, event, 4);
507
				if (isNotBlank(infrParAut) || isNotBlank(infrAut)) {
508
					authorStr = infrAut;
509
					paraut = infrParAut;
510
				}
511
			}
512
			if (StringUtils.isNotBlank(authorStr)) {
513
				TeamOrPersonBase<?>[] authorAndEx = authorAndEx(authorStr,	event);
514
				name.setCombinationAuthorship(authorAndEx[0]);
515
				name.setExCombinationAuthorship(authorAndEx[1]);
516
			}
517
			if (StringUtils.isNotBlank(paraut)) {
518
				TeamOrPersonBase<?>[] authorAndEx = authorAndEx(paraut, event);
519
				name.setBasionymAuthorship(authorAndEx[0]);
520
				name.setExBasionymAuthorship(authorAndEx[1]);
521
			}
522
		}
523

    
524
		//remember author for following citations
525
		state.setLatestAuthorInHomotype(name.getCombinationAuthorship());
526
	}
527

    
528
	private TeamOrPersonBase<?>[] authorAndEx(String authorAndEx, XMLEvent xmlEvent) {
529
		authorAndEx = authorAndEx.trim();
530
		TeamOrPersonBase<?>[] result = new TeamOrPersonBase[2];
531

    
532
		String[] split = authorAndEx.split("\\sex\\s");
533
		if (split.length > 2) {
534
			String message = "There is more then 1 ' ex ' in author string. Can't separate author and ex-author";
535
			fireWarningEvent(message, xmlEvent, 4);
536
			result[0] = createAuthor(authorAndEx);
537
		} else if (split.length == 2) {
538
			result[0] = createAuthor(split[1]);
539
			result[1] = createAuthor(split[0]);
540
		} else {
541
			result[0] = createAuthor(split[0]);
542
		}
543
		return result;
544
	}
545

    
546
	/**
547
	 * Returns the (empty) name with the correct homotypical group depending on
548
	 * the taxon status. Throws NPE if no currentTaxon is set in state.
549
	 *
550
	 * @param state
551
	 * @param homotypicalGroup
552
	 * @param isSynonym
553
	 * @return
554
	 */
555
	private NonViralName<?> createName(MarkupImportState state,
556
			HomotypicalGroup homotypicalGroup, boolean isSynonym) {
557
		NonViralName<?> name;
558
		Taxon taxon = state.getCurrentTaxon();
559
		if (isSynonym) {
560
			Rank defaultRank = Rank.SPECIES(); // can be any
561
			name = createNameByCode(state, defaultRank);
562
			if (homotypicalGroup != null) {
563
				name.setHomotypicalGroup(homotypicalGroup);
564
			}
565
			SynonymRelationshipType synonymType = SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
566
			if (taxon.getHomotypicGroup().equals(homotypicalGroup)) {
567
				synonymType = SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF();
568
			}
569
			taxon.addSynonymName(name, synonymType);
570
		} else {
571
			name = CdmBase.deproxy(taxon.getName(), NonViralName.class);
572
		}
573
		return name;
574
	}
575

    
576
	private void handleCitation(MarkupImportState state, XMLEventReader reader,
577
			XMLEvent parentEvent, NonViralName<?> name, Map<String, String> nameMap) throws XMLStreamException {
578
		String classValue = getClassOnlyAttribute(parentEvent);
579

    
580
		state.setCitation(true);
581
		boolean hasRefPart = false;
582
		Map<String, String> refMap = new HashMap<String, String>();
583
		while (reader.hasNext()) {
584
			XMLEvent next = readNoWhitespace(reader);
585
			if (isMyEndingElement(next, parentEvent)) {
586
				checkMandatoryElement(hasRefPart, parentEvent.asStartElement(), REF_PART);
587
				Reference reference = createReference(state, refMap, next);
588
				String microReference = refMap.get(DETAILS);
589
				doCitation(state, name, classValue, reference, microReference, parentEvent);
590
				state.setCitation(false);
591
				return;
592
			} else if (isStartingElement(next, REF_PART)) {
593
				handleRefPart(state, reader, next, refMap);
594
				hasRefPart = true;
595
			} else {
596
				handleUnexpectedElement(next);
597
			}
598
		}
599
		throw new IllegalStateException("Citation has no closing tag");
600

    
601
	}
602

    
603
	private void handleRefPart(MarkupImportState state, XMLEventReader reader,
604
			XMLEvent parentEvent, Map<String, String> refMap)
605
			throws XMLStreamException {
606
		String classValue = getClassOnlyAttribute(parentEvent);
607

    
608
		String text = "";
609
		while (reader.hasNext()) {
610
			XMLEvent next = readNoWhitespace(reader);
611
			if (isMyEndingElement(next, parentEvent)) {
612
				refMap.put(classValue, text);
613
				return;
614
			} else if (next.isStartElement()) {
615
				if (isStartingElement(next, ANNOTATION)) {
616
					handleNotYetImplementedElement(next); // TODO test
617
															// handleSimpleAnnotation
618
				} else if (isStartingElement(next, ITALICS)) {
619
					handleNotYetImplementedElement(next);
620
				} else if (isStartingElement(next, BOLD)) {
621
					handleNotYetImplementedElement(next);
622
				} else {
623
					handleUnexpectedStartElement(next.asStartElement());
624
				}
625
			} else if (next.isCharacters()) {
626
				text += next.asCharacters().getData();
627
			} else {
628
				handleUnexpectedEndElement(next.asEndElement());
629
			}
630
		}
631
		throw new IllegalStateException("RefPart has no closing tag");
632

    
633
	}
634

    
635
	private void doCitation(MarkupImportState state, NonViralName<?> name,
636
			String classValue, Reference reference, String microCitation,
637
			XMLEvent parentEvent) {
638
		if (PUBLICATION.equalsIgnoreCase(classValue)) {
639
			name.setNomenclaturalReference(reference);
640
			name.setNomenclaturalMicroReference(microCitation);
641
		} else if (USAGE.equalsIgnoreCase(classValue)) {
642
			Taxon taxon = state.getCurrentTaxon();
643
			TaxonDescription td = getTaxonDescription(taxon, state.getConfig().getSourceReference(), false, true);
644
			TextData citation = TextData.NewInstance(Feature.CITATION());
645
			// TODO name used in source
646
			citation.addSource(OriginalSourceType.PrimaryTaxonomicSource, null, null, reference, microCitation);
647
			td.addElement(citation);
648
		} else if (TYPE.equalsIgnoreCase(classValue)) {
649
			handleNotYetImplementedAttributeValue(parentEvent, CLASS, classValue);
650
		} else {
651
			// TODO Not yet implemented
652
			handleNotYetImplementedAttributeValue(parentEvent, CLASS, classValue);
653
		}
654
	}
655

    
656
	/**
657
	 * Tests if the names rank is consistent with the given author strings.
658
	 * NOTE: Tags for authors are differ depending on the rank.
659
	 *
660
	 * @param name
661
	 * @param event
662
	 * @param authorStr
663
	 * @param paraut
664
	 * @param infrParAut
665
	 * @param infrAut
666
	 */
667
	private void testRankAuthorConsistency(NonViralName<?> name, XMLEvent event,
668
			String authorStr, String paraut, String infrParAut, String infrAut) {
669
		if (name.getRank() == null) {
670
			return;
671
		}
672
		if (name.getRank().isInfraSpecific()) {
673
			if (StringUtils.isBlank(infrParAut)
674
					&& StringUtils.isBlank(infrAut) // was isNotBlank before
675
													// 29.5.2012
676
					&& (StringUtils.isNotBlank(paraut) || StringUtils
677
							.isNotBlank(authorStr)) && !name.isAutonym()) {
678
				String message = "Rank is infraspecicific but has only specific or higher author(s)";
679
				fireWarningEvent(message, event, 4);
680
			}
681
		} else {
682
			// is not infraspecific
683
			if (StringUtils.isNotBlank(infrParAut)
684
					|| StringUtils.isNotBlank(infrAut)) {
685
				String message = "Rank is not infraspecicific but name has infra author(s)";
686
				fireWarningEvent(message, event, 4);
687
			}
688
		}
689
	}
690

    
691
	private Reference createReference(MarkupImportState state,
692
			Map<String, String> refMap, XMLEvent parentEvent) {
693
		// TODO
694
		Reference reference;
695

    
696
		String type = getAndRemoveMapKey(refMap, PUBTYPE);
697
		String authorStr = getAndRemoveMapKey(refMap, AUTHOR);
698
		String titleStr = getAndRemoveMapKey(refMap, PUBTITLE);
699
		String titleCache = getAndRemoveMapKey(refMap, PUBFULLNAME);
700
		String volume = getAndRemoveMapKey(refMap, VOLUME);
701
		String edition = getAndRemoveMapKey(refMap, EDITION);
702
		String editors = getAndRemoveMapKey(refMap, EDITORS);
703
		String year = getAndRemoveMapKey(refMap, YEAR);
704
		String pubName = getAndRemoveMapKey(refMap, PUBNAME);
705
		String pages = getAndRemoveMapKey(refMap, PAGES);
706
		String publocation = getAndRemoveMapKey(refMap, PUBLOCATION);
707
		String publisher = getAndRemoveMapKey(refMap, PUBLISHER);
708
		String appendix = getAndRemoveMapKey(refMap, APPENDIX);
709

    
710
		if (state.isCitation()) {
711
			reference = handleCitationSpecific(state, type, authorStr,
712
					titleStr, titleCache, volume, edition, editors, pubName, pages, appendix, refMap, parentEvent);
713

    
714
		} else { // no citation
715
			reference = handleNonCitationSpecific(type, authorStr, titleStr,
716
					titleCache, volume, edition, editors, pubName, appendix);
717
		}
718

    
719
		//year
720
		TimePeriod timeperiod = TimePeriodParser.parseString(year);
721
		if (reference.getType().equals(ReferenceType.BookSection)){
722
			reference.getInBook().setDatePublished(timeperiod);
723
		}
724
		reference.setDatePublished(timeperiod);
725

    
726
		//Quickfix for these 2 attributes used in feature.references
727
		Reference inRef = reference.getInReference() == null ? reference : reference.getInReference();
728
		//publocation
729
		if (StringUtils.isNotEmpty(publisher)){
730
			inRef.setPublisher(publisher);
731
		}
732

    
733
		//publisher
734
		if (StringUtils.isNotEmpty(publocation)){
735
			inRef.setPlacePublished(publocation);
736
		}
737

    
738
		// TODO
739
		String[] unhandledList = new String[] { ALTERNATEPUBTITLE, ISSUE, NOTES, STATUS };
740
		for (String unhandled : unhandledList) {
741
			String value = getAndRemoveMapKey(refMap, unhandled);
742
			if (isNotBlank(value)) {
743
				this.handleNotYetImplementedAttributeValue(parentEvent, CLASS, unhandled);
744
			}
745
		}
746

    
747
		for (String key : refMap.keySet()) {
748
			if (!DETAILS.equalsIgnoreCase(key)) {
749
				this.fireUnexpectedAttributeValue(parentEvent, CLASS, key);
750
			}
751
		}
752

    
753
		return reference;
754
	}
755

    
756

    
757
	/**
758
	 * Handles references used in the citation tag
759
	 * @param appendix
760
	 * @see #handleNonCitationSpecific(String, String, String, String, String, String, String, String)
761
	 */
762
	private Reference handleCitationSpecific(MarkupImportState state,
763
			String type, String authorStr, String titleStr, String titleCache,
764
			String volume, String edition, String editors, String pubName,
765
			String pages, String appendix, Map<String, String> refMap, XMLEvent parentEvent) {
766

    
767
		if (titleStr != null){
768
			String message = "Currently it is not expected that a titleStr exists in a citation";
769
			fireWarningEvent(message, parentEvent, 4);
770
		}
771

    
772
		RefType refType = defineRefTypeForCitation(type, volume, editors, authorStr, pubName, parentEvent);
773
		Reference reference;
774

    
775
		if (isNotBlank(appendix)){
776
		    pubName = pubName == null ?  appendix : (pubName + " " + appendix).replaceAll("  ", " ");
777
		}
778

    
779
		if (refType == RefType.Article) {
780
			IArticle article = ReferenceFactory.newArticle();
781
			if (pubName != null) {
782
				IJournal journal = ReferenceFactory.newJournal();
783
				journal.setTitle(pubName);
784
				article.setInJournal(journal);
785
				article.setVolume(volume);
786
				if (isNotBlank(edition)){
787
					String message = "Article must not have an edition.";
788
					fireWarningEvent(message, parentEvent, 4);
789
				}
790
			}
791
			reference = (Reference) article;
792
		} else if (refType == RefType.BookSection) {
793
			//Book Section
794
			reference = ReferenceFactory.newBookSection();
795
			IBook  book = ReferenceFactory.newBook();
796
			reference.setInBook(book);
797
			book.setTitle(pubName);
798
			book.setVolume(volume);
799
			book.setEdition(edition);
800

    
801
			if (state.getConfig().isUseEditorAsInAuthorWhereNeeded()){
802
				TeamOrPersonBase<?> inAuthor = createAuthor(editors);
803
				book.setAuthorship(inAuthor);
804
				editors = null;
805
			}
806
		} else if (refType == RefType.Book){
807
			//Book
808
			reference = ReferenceFactory.newBook();
809
			reference.setTitle(pubName);
810
			reference.setVolume(volume);
811
			reference.setEdition(edition);
812
		}else if (refType == RefType.Generic){
813
			//Generic - undefinable
814
//			String message = "Can't define the type of the reference. Use generic instead";
815
//			fireWarningEvent(message, parentEvent, 4);
816
			reference = ReferenceFactory.newGeneric();
817
			reference.setTitle(pubName);
818
			reference.setEdition(edition);
819

    
820
			//volume indicates an in-reference
821
			if (isNotBlank(volume)){
822
				Reference partOf = ReferenceFactory.newGeneric();
823
				partOf.setVolume(volume);
824
				partOf.setInReference(reference);
825
				reference = partOf;
826
			}
827
		}else if (refType == RefType.LatestUsed){
828
			Reference latestReference = state.getLatestReferenceInHomotype();
829
			if (latestReference == null){
830
				String message = "No former reference available for incomplete citation";
831
				fireWarningEvent(message, parentEvent, 6);
832
				reference = ReferenceFactory.newGeneric();
833
			}else{
834
				if (latestReference.getInReference() != null){
835
					reference = (Reference)latestReference.clone();
836
				}else{
837
					String message = "Latest reference is not an in-reference. This is not yet handled.";
838
					fireWarningEvent(message, parentEvent, 6);
839
					reference = ReferenceFactory.newGeneric();
840
				}
841
			}
842
			reference.setVolume(volume);
843
			if (isNotBlank(edition)){
844
				String message = "Edition not yet handled for incomplete citations";
845
				fireWarningEvent(message, parentEvent, 4);
846
			}
847

    
848
		}else{
849
			String message = "Unhandled reference type: %s" ;
850
			fireWarningEvent(String.format(message, refType.toString()), parentEvent, 8);
851
			reference = ReferenceFactory.newGeneric();
852
		}
853

    
854
		//author
855
		TeamOrPersonBase<?> author;
856
		if (isBlank(authorStr)){
857
			if (refType != RefType.LatestUsed){
858
				author = state.getLatestAuthorInHomotype();
859
				reference.setAuthorship(author);
860
			}
861
		}else{
862
			author = createAuthor(authorStr);
863
			state.setLatestAuthorInHomotype(author);
864
			reference.setAuthorship(author);
865
		}
866

    
867

    
868
		//title, titleCache
869
		handleTitlesInCitation(titleStr, titleCache, parentEvent, reference);
870

    
871
		//editors
872
		handleEditorsInCitation(edition, editors, reference, parentEvent);
873

    
874
		//pages
875
		handlePages(state, refMap, parentEvent, reference, pages);
876

    
877
		//remember reference for following citation
878
		state.setLatestReferenceInHomotype(reference);
879

    
880
		return reference;
881
	}
882

    
883
	private void handleEditorsInCitation(String edition, String editors, Reference reference, XMLEvent parentEvent) {
884
		//editor
885
		reference.setEditor(editors);
886
		if ( editors != null){
887
			String message = "Citation reference has an editor. This is unusual for a citation reference (appears regularly in <reference> references";
888
			fireWarningEvent(message, parentEvent, 4);
889
		}
890
	}
891

    
892
	private void handleTitlesInCitation(String titleStr, String titleCache,
893
			XMLEvent parentEvent, Reference reference) {
894
		if (isNotBlank(titleStr)){
895
			reference.setTitle(titleStr);
896
		}
897
		//titleCache
898
		if (StringUtils.isNotBlank(titleCache)) {
899
			reference.setTitleCache(titleCache, true);
900
		}
901
		if (titleStr != null || titleCache != null){
902
			String message = "Citation reference has a title or a full title. Both is unusual for a citation reference (appears regularly in <reference> references";
903
			fireWarningEvent(message, parentEvent, 4);
904
		}
905
	}
906

    
907
	private enum RefType{
908
		Article,
909
		BookSection,
910
		Book,
911
		Generic,
912
		LatestUsed
913
	}
914

    
915
	private RefType defineRefTypeForCitation(String type, String volume, String editors,
916
			String authorStr, String pubName, XMLEvent parentEvent) {
917
		if ("journal".equalsIgnoreCase(type)){
918
			return RefType.Article;
919
		}else {
920
			if (editors == null){
921
				//no editors
922
				if (pubName == null){
923
					//looks like we need to use reference info from former citations here
924
					return RefType.LatestUsed;
925
				}else if (volume == null){
926
					return RefType.Book;  //Book must not have in-authors
927
				}else{
928
					return RefType.Generic;
929
				}
930

    
931
			}else{
932
				//editors
933
				if (pubName != null){
934
					return RefType.BookSection;
935
				}else{
936
					String message = "Unexpected state: Citation has editors but no pubName";
937
					fireWarningEvent(message, parentEvent, 4);
938
					return RefType.Generic;
939
				}
940
			}
941
		}
942
	}
943

    
944

    
945
	private boolean isArticle(String type, String volume, String editors) {
946
		if ("journal".equalsIgnoreCase(type)){
947
			return true;
948
		}else if (volume != null && editors == null){
949
			return true;
950
		}else{
951
			return false;
952
		}
953
	}
954

    
955
	/**
956
	 * in work
957
	 * @param appendix
958
	 * @return
959
	 */
960
	private Reference handleNonCitationSpecific(String type, String authorStr,
961
			String titleStr, String titleCache, String volume, String edition,
962
			String editors, String pubName, String appendix) {
963

    
964
	    Reference reference;
965

    
966
	    if (isNotBlank(appendix)){
967
	        pubName = pubName == null ?  appendix : (pubName + " " + appendix).replaceAll("  ", " ");
968
	    }
969

    
970
	    if (isArticle(type, volume, editors)) {
971
			IArticle article = ReferenceFactory.newArticle();
972
			if (pubName != null) {
973
				IJournal journal = ReferenceFactory.newJournal();
974
				journal.setTitle(pubName);
975
				article.setInJournal(journal);
976
			}
977
			reference = (Reference) article;
978

    
979
		} else {
980
			Reference bookOrPartOf = ReferenceFactory.newGeneric();
981
			reference = bookOrPartOf;
982
		}
983

    
984
		// TODO type
985
		TeamOrPersonBase<?> author = createAuthor(authorStr);
986
		reference.setAuthorship(author);
987

    
988
		//title
989
		reference.setTitle(titleStr);
990
		if (StringUtils.isNotBlank(titleCache)) {
991
			reference.setTitleCache(titleCache, true);
992
		}
993

    
994
		//edition
995
		reference.setEdition(edition);
996
		reference.setEditor(editors);
997

    
998
		//pubName
999
		if (pubName != null) {
1000
			Reference inReference;
1001
			if (reference.getType().equals(ReferenceType.Article)) {
1002
				inReference = ReferenceFactory.newJournal();
1003
			} else {
1004
				inReference = ReferenceFactory.newGeneric();
1005
			}
1006
			inReference.setTitle(pubName);
1007
			reference.setInReference(inReference);
1008
		}
1009

    
1010
		//volume
1011
		reference.setVolume(volume);
1012
		return reference;
1013
	}
1014

    
1015
	private void handlePages(MarkupImportState state,
1016
			Map<String, String> refMap, XMLEvent parentEvent,
1017
			Reference reference, String pages) {
1018
		// TODO check if this is handled correctly in FM markup
1019
		boolean switchPages = state.getConfig().isHandlePagesAsDetailWhereNeeded();
1020
		if (switchPages){
1021
			if (pages != null ){
1022
				String detail = refMap.get(DETAILS);
1023
				if (isBlank(detail)){
1024
					if (pages.contains("-")){
1025
						String message = "There is a pages tag with '-'. Unclear if this really means pages";
1026
						fireWarningEvent(message, parentEvent, 8);
1027
						reference.setPages(pages);
1028
					}else{
1029
						//handle pages as detail, this is at least true for Flora Malesiana
1030
						refMap.put(DETAILS, pages);
1031
					}
1032
				}else{
1033
					if (! pages.contains("-")){
1034
						String message = "There are pages and detail available where pages may also hold details information.";
1035
						fireWarningEvent(message, parentEvent, 8);
1036
					}
1037
					reference.setPages(pages);
1038
				}
1039
			}
1040
		}
1041
	}
1042

    
1043
	public Reference handleReference(MarkupImportState state,
1044
			XMLEventReader reader, XMLEvent parentEvent)
1045
			throws XMLStreamException {
1046
		checkNoAttributes(parentEvent);
1047

    
1048
		boolean hasRefPart = false;
1049
		Map<String, String> refMap = new HashMap<String, String>();
1050
		while (reader.hasNext()) {
1051
			XMLEvent next = readNoWhitespace(reader);
1052
			if (isMyEndingElement(next, parentEvent)) {
1053
				checkMandatoryElement(hasRefPart, parentEvent.asStartElement(), REF_PART);
1054
				Reference reference = createReference(state, refMap, next);
1055
				return reference;
1056
			} else if (isStartingElement(next, REF_PART)) {
1057
				handleRefPart(state, reader, next, refMap);
1058
				hasRefPart = true;
1059
			} else {
1060
				handleUnexpectedElement(next);
1061
			}
1062
		}
1063
		// TODO handle missing end element
1064
		throw new IllegalStateException("<Reference> has no closing tag");
1065
	}
1066

    
1067
}
(15-15/19)