Project

General

Profile

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

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

    
12
import java.util.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 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, ANNOTATION)) {
254
				handleNotYetImplementedElement(next);
255
			} else {
256
				handleUnexpectedElement(next);
257
			}
258
		}
259
		// TODO handle missing end element
260
		throw new IllegalStateException("Nom has no closing tag");
261
	}
262

    
263
	/**
264
	 * Handles appearance of text within <nom> tags.
265
	 * Usually this is not expected except for some information that is already handled
266
	 * elsewhere, e.g. the string Nametype is holding information that is available already
267
	 * via the surrounding nametype tag. Therefore this information can be neglected.
268
	 * This method is open for upcoming cases which need to be handled. 
269
	 * @param state
270
	 * @param event
271
	 * @param text
272
	 * @param isNameType
273
	 */
274
	private void handleNomText(MarkupImportState state, XMLEvent event, String text, boolean isNameType) {
275
		if (isBlank(text)){
276
			return;
277
		}
278
		text = text.trim();
279
		//neglect known redundant strings
280
		if (isNameType && (text.matches("(?i)^Esp[\u00E8\u00C8]ce[·\\-\\s]type\\:$") 
281
							|| charIsSimpleType(text) )){
282
			return;
283
		}//neglect meaningless punctuation
284
		else if (isPunctuation(text)){
285
			return;	
286
		}//neglect mea
287
		else if (isPunctuation(text)){
288
			return;	
289
		}else{
290
			String message = "Unhandled text in <nom> tag: \"%s\"";
291
			fireWarningEvent(String.format(message, text), event, 4);
292
		}
293
	}
294
	
295
	/**
296
	 * @param state
297
	 * @param reader
298
	 * @param next
299
	 * @throws XMLStreamException
300
	 */
301
	private void handleNomNum(MarkupImportState state, XMLEventReader reader,
302
			XMLEvent next) throws XMLStreamException {
303
		String num = getCData(state, reader, next);
304
		num = num.replace(".", "");
305
		num = num.replace(")", "");
306
		if (StringUtils.isNotBlank(num)) {
307
			if (state.getCurrentTaxonNum() != null
308
					&& !state.getCurrentTaxonNum().equals(num)) {
309
				String message = "Taxontitle num and homotypes/nom/num differ ( %s <-> %s ). I use the later one.";
310
				message = String.format(message,
311
						state.getCurrentTaxonNum(), num);
312
				fireWarningEvent(message, next, 4);
313
			}
314
			state.setCurrentTaxonNum(num);
315
		}
316
	}
317

    
318

    
319

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

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

    
342
	private void fillName(MarkupImportState state, Map<String, String> nameMap,
343
			NonViralName<?> name, XMLEvent event) {
344

    
345
		// Ranks: family, subfamily, tribus, genus, subgenus, section,
346
		// subsection, species, subspecies, variety, subvariety, forma
347
		// infrank, paraut, author, infrparaut, infraut, status, notes
348

    
349
		String infrank = getAndRemoveMapKey(nameMap, INFRANK);
350
		String authorStr = getAndRemoveMapKey(nameMap, AUTHOR);
351
		String paraut = getAndRemoveMapKey(nameMap, PARAUT);
352

    
353
		String infrParAut = getAndRemoveMapKey(nameMap, INFRPARAUT);
354
		String infrAut = getAndRemoveMapKey(nameMap, INFRAUT);
355

    
356
		String statusStr = getAndRemoveMapKey(nameMap, STATUS);
357
		String notes = getAndRemoveMapKey(nameMap, NOTES);
358

    
359
		if (!name.isProtectedTitleCache()) { // otherwise fullName
360

    
361
			makeRankDecision(state, nameMap, name, event, infrank);
362

    
363
			// test consistency of rank and authors
364
			testRankAuthorConsistency(name, event, authorStr, paraut,infrParAut, infrAut);
365

    
366
			// authors
367
			makeNomenclaturalAuthors(state, event, name, authorStr, paraut, infrParAut, infrAut);
368
		}
369

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

    
389
		// notes
390
		if (StringUtils.isNotBlank(notes)) {
391
			handleNotYetImplementedAttributeValue(event, CLASS, NOTES);
392
		}
393

    
394
		return;
395
	}
396

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

    
449
		}
450
		// handle given infrank marker
451
		if (StringUtils.isNotBlank(infrankStr)) {
452
			Rank infRank = makeRank(state, infrankStr, true);
453

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

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

    
524
	private TeamOrPersonBase<?>[] authorAndEx(String authorAndEx, XMLEvent xmlEvent) {
525
		authorAndEx = authorAndEx.trim();
526
		TeamOrPersonBase<?>[] result = new TeamOrPersonBase[2];
527

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

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

    
572
	private void handleCitation(MarkupImportState state, XMLEventReader reader,
573
			XMLEvent parentEvent, NonViralName<?> name, Map<String, String> nameMap) throws XMLStreamException {
574
		String classValue = getClassOnlyAttribute(parentEvent);
575

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

    
597
	}
598

    
599
	private void handleRefPart(MarkupImportState state, XMLEventReader reader,
600
			XMLEvent parentEvent, Map<String, String> refMap)
601
			throws XMLStreamException {
602
		String classValue = getClassOnlyAttribute(parentEvent);
603

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

    
629
	}
630

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

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

    
687
	private Reference<?> createReference(MarkupImportState state,
688
			Map<String, String> refMap, XMLEvent parentEvent) {
689
		// TODO
690
		Reference<?> reference;
691

    
692
		String type = getAndRemoveMapKey(refMap, PUBTYPE);
693
		String authorStr = getAndRemoveMapKey(refMap, AUTHOR);
694
		String titleStr = getAndRemoveMapKey(refMap, PUBTITLE);
695
		String titleCache = getAndRemoveMapKey(refMap, PUBFULLNAME);
696
		String volume = getAndRemoveMapKey(refMap, VOLUME);
697
		String edition = getAndRemoveMapKey(refMap, EDITION);
698
		String editors = getAndRemoveMapKey(refMap, EDITORS);
699
		String year = getAndRemoveMapKey(refMap, YEAR);
700
		String pubName = getAndRemoveMapKey(refMap, PUBNAME);
701
		String pages = getAndRemoveMapKey(refMap, PAGES);
702
		String publocation = getAndRemoveMapKey(refMap, PUBLOCATION);
703
		String publisher = getAndRemoveMapKey(refMap, PUBLISHER);
704

    
705
		if (state.isCitation()) {
706
			reference = handleCitationSpecific(state, type, authorStr,
707
					titleStr, titleCache, volume, edition, editors, pubName, pages, refMap, parentEvent);
708

    
709
		} else { // no citation
710
			reference = handleNonCitationSpecific(type, authorStr, titleStr,
711
					titleCache, volume, edition, editors, pubName);
712
		}
713

    
714
		//year
715
		TimePeriod timeperiod = TimePeriodParser.parseString(year);
716
		if (reference.getType().equals(ReferenceType.BookSection)){
717
			reference.getInBook().setDatePublished(timeperiod);
718
		}
719
		reference.setDatePublished(timeperiod);
720
		
721
		//Quickfix for these 2 attributes used in feature.references
722
		Reference<?> inRef = reference.getInReference() == null ? reference : reference.getInReference();
723
		//publocation
724
		if (StringUtils.isNotEmpty(publisher)){
725
			inRef.setPublisher(publisher);
726
		}
727
		
728
		//publisher
729
		if (StringUtils.isNotEmpty(publocation)){
730
			inRef.setPlacePublished(publocation);
731
		}
732
		
733
		// TODO
734
		String[] unhandledList = new String[] { ALTERNATEPUBTITLE, ISSUE, NOTES, STATUS };
735
		for (String unhandled : unhandledList) {
736
			String value = getAndRemoveMapKey(refMap, unhandled);
737
			if (isNotBlank(value)) {
738
				this.handleNotYetImplementedAttributeValue(parentEvent, CLASS, unhandled);
739
			}
740
		}
741

    
742
		for (String key : refMap.keySet()) {
743
			if (!DETAILS.equalsIgnoreCase(key)) {
744
				this.fireUnexpectedAttributeValue(parentEvent, CLASS, key);
745
			}
746
		}
747

    
748
		return reference;
749
	}
750

    
751
	
752
	/**
753
	 * Handles references used in the citation tag
754
	 * @see #handleNonCitationSpecific(String, String, String, String, String, String, String, String)
755
	 */
756
	private Reference<?> handleCitationSpecific(MarkupImportState state,
757
			String type, String authorStr, String titleStr, String titleCache,
758
			String volume, String edition, String editors, String pubName, String pages, Map<String, String> refMap, XMLEvent parentEvent) {
759
		
760
		if (titleStr != null){
761
			String message = "Currently it is not expected that a titleStr exists in a citation";
762
			fireWarningEvent(message, parentEvent, 4);
763
		}
764

    
765
		RefType refType = defineRefTypeForCitation(type, volume, editors, authorStr, pubName, parentEvent);
766
		Reference<?> reference;
767
		if (refType == RefType.Article) {
768
			IArticle article = ReferenceFactory.newArticle();
769
			if (pubName != null) {
770
				IJournal journal = ReferenceFactory.newJournal();
771
				journal.setTitle(pubName);
772
				article.setInJournal(journal);
773
				article.setVolume(volume);
774
				if (isNotBlank(edition)){
775
					String message = "Article must not have an edition.";
776
					fireWarningEvent(message, parentEvent, 4);
777
				}
778
			}
779
			reference = (Reference<?>) article;
780
		} else if (refType == RefType.BookSection) {
781
			//Book Section
782
			reference = ReferenceFactory.newBookSection();
783
			IBook  book = ReferenceFactory.newBook();
784
			reference.setInBook(book);
785
			book.setTitle(pubName);
786
			book.setVolume(volume);
787
			book.setEdition(edition);
788
			
789
			if (state.getConfig().isUseEditorAsInAuthorWhereNeeded()){
790
				TeamOrPersonBase<?> inAuthor = createAuthor(editors);
791
				book.setAuthorship(inAuthor);
792
				editors = null;
793
			}
794
		} else if (refType == RefType.Book){
795
			//Book
796
			reference = ReferenceFactory.newBook();
797
			reference.setTitle(pubName);
798
			reference.setVolume(volume);
799
			reference.setEdition(edition);
800
		}else if (refType == RefType.Generic){
801
			//Generic - undefinable
802
//			String message = "Can't define the type of the reference. Use generic instead";
803
//			fireWarningEvent(message, parentEvent, 4);
804
			reference = ReferenceFactory.newGeneric();
805
			reference.setTitle(pubName);
806
			reference.setEdition(edition);
807
			
808
			//volume indicates an in-reference
809
			if (isNotBlank(volume)){
810
				Reference<?> partOf = ReferenceFactory.newGeneric();
811
				partOf.setVolume(volume);
812
				partOf.setInReference(reference);
813
				reference = partOf;
814
			}
815
		}else if (refType == RefType.LatestUsed){
816
			Reference<?> latestReference = state.getLatestReferenceInHomotype();
817
			if (latestReference == null){
818
				String message = "No former reference available for incomplete citation";
819
				fireWarningEvent(message, parentEvent, 6);
820
				reference = ReferenceFactory.newGeneric();
821
			}else{
822
				if (latestReference.getInReference() != null){
823
					reference = (Reference<?>)latestReference.clone();
824
				}else{
825
					String message = "Latest reference is not an in-reference. This is not yet handled.";
826
					fireWarningEvent(message, parentEvent, 6);
827
					reference = ReferenceFactory.newGeneric();
828
				}
829
			}
830
			reference.setVolume(volume);
831
			if (isNotBlank(edition)){
832
				String message = "Edition not yet handled for incomplete citations";
833
				fireWarningEvent(message, parentEvent, 4);
834
			}
835
			
836
		}else{
837
			String message = "Unhandled reference type: %s" ;
838
			fireWarningEvent(String.format(message, refType.toString()), parentEvent, 8);
839
			reference = ReferenceFactory.newGeneric();
840
		}
841
		
842
		//author
843
		TeamOrPersonBase<?> author;
844
		if (isBlank(authorStr)){
845
			if (refType != RefType.LatestUsed){
846
				author = state.getLatestAuthorInHomotype();
847
				reference.setAuthorship(author);
848
			}
849
		}else{
850
			author = createAuthor(authorStr);
851
			state.setLatestAuthorInHomotype(author);
852
			reference.setAuthorship(author);
853
		}
854
		
855

    
856
		//title, titleCache
857
		handleTitlesInCitation(titleStr, titleCache, parentEvent, reference);
858

    
859
		//editors
860
		handleEditorsInCitation(edition, editors, reference, parentEvent);
861
		
862
		//pages
863
		handlePages(state, refMap, parentEvent, reference, pages);
864
		
865
		//remember reference for following citation
866
		state.setLatestReferenceInHomotype(reference);
867
		
868
		return reference;
869
	}
870

    
871
	private void handleEditorsInCitation(String edition, String editors, Reference<?> reference, XMLEvent parentEvent) {
872
		//editor
873
		reference.setEditor(editors);
874
		if ( editors != null){
875
			String message = "Citation reference has an editor. This is unusual for a citation reference (appears regularly in <reference> references";
876
			fireWarningEvent(message, parentEvent, 4);
877
		}
878
	}
879

    
880
	private void handleTitlesInCitation(String titleStr, String titleCache,
881
			XMLEvent parentEvent, Reference<?> reference) {
882
		if (isNotBlank(titleStr)){
883
			reference.setTitle(titleStr);
884
		}
885
		//titleCache
886
		if (StringUtils.isNotBlank(titleCache)) {
887
			reference.setTitleCache(titleCache, true);
888
		}
889
		if (titleStr != null || titleCache != null){
890
			String message = "Citation reference has a title or a full title. Both is unusual for a citation reference (appears regularly in <reference> references";
891
			fireWarningEvent(message, parentEvent, 4);
892
		}
893
	}
894

    
895
	private enum RefType{
896
		Article,
897
		BookSection,
898
		Book,
899
		Generic,
900
		LatestUsed
901
	}
902
	
903
	private RefType defineRefTypeForCitation(String type, String volume, String editors, 
904
			String authorStr, String pubName, XMLEvent parentEvent) {
905
		if ("journal".equalsIgnoreCase(type)){
906
			return RefType.Article;
907
		}else {
908
			if (editors == null){
909
				//no editors
910
				if (pubName == null){
911
					//looks like we need to use reference info from former citations here
912
					return RefType.LatestUsed;
913
				}else if (volume == null){
914
					return RefType.Book;  //Book must not have in-authors
915
				}else{
916
					return RefType.Generic;
917
				}
918

    
919
			}else{
920
				//editors
921
				if (pubName != null){
922
					return RefType.BookSection;
923
				}else{
924
					String message = "Unexpected state: Citation has editors but no pubName";
925
					fireWarningEvent(message, parentEvent, 4);
926
					return RefType.Generic;
927
				}
928
			}
929
		}
930
	}
931

    
932

    
933
	private boolean isArticle(String type, String volume, String editors) {
934
		if ("journal".equalsIgnoreCase(type)){
935
			return true;
936
		}else if (volume != null && editors == null){
937
			return true;
938
		}else{
939
			return false;
940
		}
941
	}
942

    
943
	/**
944
	 * in work
945
	 * @return
946
	 */
947
	private Reference<?> handleNonCitationSpecific(String type, String authorStr,
948
			String titleStr, String titleCache, String volume, String edition,
949
			String editors, String pubName) {
950
		Reference<?> reference;
951
		if (isArticle(type, volume, editors)) {
952
			IArticle article = ReferenceFactory.newArticle();
953
			if (pubName != null) {
954
				IJournal journal = ReferenceFactory.newJournal();
955
				journal.setTitle(pubName);
956
				article.setInJournal(journal);
957
			}
958
			reference = (Reference<?>) article;
959

    
960
		} else {
961
			Reference<?> bookOrPartOf = ReferenceFactory.newGeneric();
962
			reference = bookOrPartOf;
963
		}
964

    
965
		// TODO type
966
		TeamOrPersonBase<?> author = createAuthor(authorStr);
967
		reference.setAuthorship(author);
968

    
969
		//title
970
		reference.setTitle(titleStr);
971
		if (StringUtils.isNotBlank(titleCache)) {
972
			reference.setTitleCache(titleCache, true);
973
		}
974
		
975
		//edition
976
		reference.setEdition(edition);
977
		reference.setEditor(editors);
978

    
979
		//pubName
980
		if (pubName != null) {
981
			Reference<?> inReference;
982
			if (reference.getType().equals(ReferenceType.Article)) {
983
				inReference = ReferenceFactory.newJournal();
984
			} else {
985
				inReference = ReferenceFactory.newGeneric();
986
			}
987
			inReference.setTitle(pubName);
988
			reference.setInReference(inReference);
989
		}
990
		
991
		//volume
992
		reference.setVolume(volume);
993
		return reference;
994
	}
995

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

    
1024
	public Reference<?> handleReference(MarkupImportState state,
1025
			XMLEventReader reader, XMLEvent parentEvent)
1026
			throws XMLStreamException {
1027
		checkNoAttributes(parentEvent);
1028

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

    
1048
}
(15-15/19)