ref #6369 adapt existing occurrences of interface to removed generics
[cdmlib.git] / cdmlib-io / src / main / java / eu / etaxonomy / cdm / io / markup / MarkupNomenclatureImport.java
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.INonViralName;
33 import eu.etaxonomy.cdm.model.name.NameTypeDesignationStatus;
34 import eu.etaxonomy.cdm.model.name.NomenclaturalStatus;
35 import eu.etaxonomy.cdm.model.name.NomenclaturalStatusType;
36 import eu.etaxonomy.cdm.model.name.NonViralName;
37 import eu.etaxonomy.cdm.model.name.Rank;
38 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
39 import eu.etaxonomy.cdm.model.reference.IArticle;
40 import eu.etaxonomy.cdm.model.reference.IBook;
41 import eu.etaxonomy.cdm.model.reference.IJournal;
42 import eu.etaxonomy.cdm.model.reference.Reference;
43 import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
44 import eu.etaxonomy.cdm.model.reference.ReferenceType;
45 import eu.etaxonomy.cdm.model.taxon.SynonymType;
46 import eu.etaxonomy.cdm.model.taxon.Taxon;
47 import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
48 import eu.etaxonomy.cdm.strategy.parser.NameTypeParser;
49 import eu.etaxonomy.cdm.strategy.parser.TimePeriodParser;
50
51 /**
52 * @author a.mueller
53 * @created 30.05.2012
54 *
55 */
56 public class MarkupNomenclatureImport extends MarkupImportBase {
57 @SuppressWarnings("unused")
58 private static final Logger logger = Logger.getLogger(MarkupNomenclatureImport.class);
59
60
61 // private NonViralNameParserImpl parser = new NonViralNameParserImpl();
62
63 private final MarkupSpecimenImport specimenImport;
64
65 public MarkupNomenclatureImport(MarkupDocumentImport docImport, MarkupSpecimenImport specimenImport) {
66 super(docImport);
67 this.specimenImport = specimenImport;
68 }
69
70 public void handleNomenclature(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent)
71 throws XMLStreamException {
72 checkNoAttributes(parentEvent);
73
74 while (reader.hasNext()) {
75 XMLEvent next = readNoWhitespace(reader);
76 if (isMyEndingElement(next, parentEvent)) {
77 return;
78 } else if (isStartingElement(next, HOMOTYPES)) {
79 handleHomotypes(state, reader, next.asStartElement());
80 } else if (isStartingElement(next, NOMENCLATURAL_NOTES)) {
81 handleAmbigousManually(state, reader, next.asStartElement());
82 } else {
83 fireSchemaConflictEventExpectedStartTag(HOMOTYPES, reader);
84 state.setUnsuccessfull();
85 }
86 }
87 return;
88 }
89
90
91 private void handleHomotypes(MarkupImportState state,
92 XMLEventReader reader, StartElement parentEvent)
93 throws XMLStreamException {
94 checkNoAttributes(parentEvent);
95
96 HomotypicalGroup homotypicalGroup = null;
97
98 boolean hasNom = false;
99 while (reader.hasNext()) {
100 XMLEvent next = readNoWhitespace(reader);
101 if (isMyEndingElement(next, parentEvent)) {
102 checkMandatoryElement(hasNom, parentEvent, NOM);
103 state.setLatestAuthorInHomotype(null);
104 return;
105 } else if (isEndingElement(next, NAME_TYPE)) {
106 state.setNameType(false);
107 } else if (isStartingElement(next, NOM)) {
108 INonViralName name = handleNom(state, reader, next, homotypicalGroup);
109 homotypicalGroup = name.getHomotypicalGroup();
110 hasNom = true;
111 } else if (isStartingElement(next, NAME_TYPE)) {
112 state.setNameType(true);
113 handleNameType(state, reader, next, homotypicalGroup);
114 } else if (isStartingElement(next, SPECIMEN_TYPE)) {
115 specimenImport.handleSpecimenType(state, reader, next, homotypicalGroup);
116 } else if (isStartingElement(next, NOTES)) {
117 handleNotYetImplementedElement(next);
118 } else {
119 handleUnexpectedElement(next);
120 }
121 }
122 state.setLatestAuthorInHomotype(null);
123 // TODO handle missing end element
124 throw new IllegalStateException("Homotypes has no closing tag");
125
126 }
127
128 private void handleNameType(MarkupImportState state, XMLEventReader reader,
129 XMLEvent parentEvent, HomotypicalGroup homotypicalGroup)
130 throws XMLStreamException {
131 Map<String, Attribute> attributes = getAttributes(parentEvent);
132 String typeStatus = getAndRemoveAttributeValue(attributes, TYPE_STATUS);
133 checkNoAttributes(attributes, parentEvent);
134
135 NameTypeDesignationStatus status;
136 try {
137 status = NameTypeParser.parseNameTypeStatus(typeStatus);
138 } catch (UnknownCdmTypeException e) {
139 String message = "Type status could not be recognized: %s";
140 message = String.format(message, typeStatus);
141 fireWarningEvent(message, parentEvent, 4);
142 status = null;
143 }
144
145 boolean hasNom = false;
146 while (reader.hasNext()) {
147 XMLEvent next = readNoWhitespace(reader);
148 if (next.isEndElement()) {
149 if (isMyEndingElement(next, parentEvent)) {
150 checkMandatoryElement(hasNom, parentEvent.asStartElement(),
151 NOM);
152 state.setNameType(false);
153 return;
154 } else {
155 if (isEndingElement(next, ACCEPTED_NAME)) {
156 // NOT YET IMPLEMENTED
157 popUnimplemented(next.asEndElement());
158 } else {
159 handleUnexpectedEndElement(next.asEndElement());
160 }
161 }
162 } else if (next.isStartElement()) {
163 if (isStartingElement(next, NOM)) {
164 // TODO should we check if the type is always a species, is
165 // this a rule?
166 NonViralName<?> speciesName = handleNom(state, reader,
167 next, null);
168 for (TaxonNameBase<?, ?> name : homotypicalGroup
169 .getTypifiedNames()) {
170 name.addNameTypeDesignation(speciesName, null, null,
171 null, status, false, false, false, false);
172 }
173 hasNom = true;
174 } else if (isStartingElement(next, ACCEPTED_NAME)) {
175 handleNotYetImplementedElement(next);
176 } else {
177 handleUnexpectedStartElement(next);
178 }
179 } else {
180 handleUnexpectedElement(next);
181 }
182 }
183 // TODO handle missing end element
184 throw new IllegalStateException("Homotypes has no closing tag");
185
186 }
187
188 /**
189 * Creates the name defined by a nom tag. Adds it to the given homotypical
190 * group (if not null).
191 *
192 * @param state
193 * @param reader
194 * @param parentEvent
195 * @param homotypicalGroup
196 * @return
197 * @throws XMLStreamException
198 */
199 private NonViralName<?> handleNom(MarkupImportState state, XMLEventReader reader,
200 XMLEvent parentEvent, HomotypicalGroup homotypicalGroup) throws XMLStreamException {
201 boolean isSynonym = false;
202 boolean isNameType = state.isNameType();
203 // attributes
204 String classValue = getClassOnlyAttribute(parentEvent);
205 NonViralName<?> name;
206 if (!isNameType && ACCEPTED.equalsIgnoreCase(classValue)) {
207 isSynonym = false;
208 name = createName(state, homotypicalGroup, isSynonym);
209 } else if (!isNameType && SYNONYM.equalsIgnoreCase(classValue)) {
210 isSynonym = true;
211 name = createName(state, homotypicalGroup, isSynonym);
212 } else if (isNameType && NAME_TYPE.equalsIgnoreCase(classValue)) {
213 // TODO do we need to define the rank here?
214 name = createNameByCode(state, null);
215 } else {
216 fireUnexpectedAttributeValue(parentEvent, CLASS, classValue);
217 name = createNameByCode(state, null);
218 }
219
220 Map<String, String> nameMap = new HashMap<String, String>();
221 String text = "";
222
223 boolean nameFilled = false;
224 while (reader.hasNext()) {
225 XMLEvent next = readNoWhitespace(reader);
226 if (isMyEndingElement(next, parentEvent)) {
227 // fill the name with all data gathered, if not yet done before
228 if (nameFilled == false){
229 fillName(state, nameMap, name, next);
230 }
231 handleNomText(state, parentEvent, text, isNameType);
232 return name;
233 } else if (isEndingElement(next, ANNOTATION)) {
234 // NOT YET IMPLEMENTED //TODO test
235 // handleSimpleAnnotation
236 popUnimplemented(next.asEndElement());
237 }else if (isStartingElement(next, FULL_NAME)) {
238 handleFullName(state, reader, name, next);
239 } else if (isStartingElement(next, NUM)) {
240 handleNomNum(state, reader, next);
241 } else if (isStartingElement(next, NAME)) {
242 handleName(state, reader, next, nameMap);
243 } else if (isStartingElement(next, CITATION)) {
244 //we need to fill the name here to have nomenclatural author available for the following citations
245 fillName(state, nameMap, name, next);
246 nameFilled = true;
247 handleCitation(state, reader, next, name, nameMap);
248 } else if (next.isCharacters()) {
249 text += next.asCharacters().getData();
250 } else if (isStartingElement(next, HOMONYM)) {
251 handleNotYetImplementedElement(next);
252 } else if (isStartingElement(next, NOTES)) {
253 handleNotYetImplementedElement(next);
254 } else if (isStartingElement(next, NOMENCLATURAL_NOTES)) {
255 handleNotYetImplementedElement(next);
256 } else if (isStartingElement(next, ANNOTATION)) {
257 handleNotYetImplementedElement(next);
258 } else {
259 handleUnexpectedElement(next);
260 }
261 }
262 // TODO handle missing end element
263 throw new IllegalStateException("Nom has no closing tag");
264 }
265
266 /**
267 * Handles appearance of text within <nom> tags.
268 * Usually this is not expected except for some information that is already handled
269 * elsewhere, e.g. the string Nametype is holding information that is available already
270 * via the surrounding nametype tag. Therefore this information can be neglected.
271 * This method is open for upcoming cases which need to be handled.
272 * @param state
273 * @param event
274 * @param text
275 * @param isNameType
276 */
277 private void handleNomText(MarkupImportState state, XMLEvent event, String text, boolean isNameType) {
278 if (isBlank(text)){
279 return;
280 }
281 text = text.trim();
282 //neglect known redundant strings
283 if (isNameType && (text.matches("(?i)^Esp[\u00E8\u00C8]ce[·\\-\\s]type\\:$")
284 || charIsSimpleType(text) )){
285 return;
286 }//neglect meaningless punctuation
287 else if (isPunctuation(text)){
288 return;
289 }//neglect mea
290 else if (isPunctuation(text)){
291 return;
292 }else{
293 String message = "Unhandled text in <nom> tag: \"%s\"";
294 fireWarningEvent(String.format(message, text), event, 4);
295 }
296 }
297
298 /**
299 * @param state
300 * @param reader
301 * @param next
302 * @throws XMLStreamException
303 */
304 private void handleNomNum(MarkupImportState state, XMLEventReader reader,
305 XMLEvent next) throws XMLStreamException {
306 String num = getCData(state, reader, next);
307 num = num.replace(".", "");
308 num = num.replace(")", "");
309 if (StringUtils.isNotBlank(num)) {
310 if (state.getCurrentTaxonNum() != null
311 && !state.getCurrentTaxonNum().equals(num)) {
312 String message = "Taxontitle num and homotypes/nom/num differ ( %s <-> %s ). I use the later one.";
313 message = String.format(message,
314 state.getCurrentTaxonNum(), num);
315 fireWarningEvent(message, next, 4);
316 }
317 state.setCurrentTaxonNum(num);
318 }
319 }
320
321
322
323 private void handleName(MarkupImportState state, XMLEventReader reader,
324 XMLEvent parentEvent, Map<String, String> nameMap)
325 throws XMLStreamException {
326 String classValue = getClassOnlyAttribute(parentEvent);
327
328 String text = "";
329 while (reader.hasNext()) {
330 XMLEvent next = readNoWhitespace(reader);
331 if (isMyEndingElement(next, parentEvent)) {
332 nameMap.put(classValue, text);
333 return;
334 } else if (isStartingElement(next, ANNOTATION)) {
335 handleNotYetImplementedElement(next); // TODO test handleSimpleAnnotation
336 } else if (isStartingElement(next, FOOTNOTE_REF)) {
337 handleNotYetImplementedElement(next);
338 } else if (next.isCharacters()) {
339 text += next.asCharacters().getData();
340 } else {
341 handleUnexpectedElement(next);
342 }
343 }
344 throw new IllegalStateException("name has no closing tag");
345 }
346
347 private void fillName(MarkupImportState state, Map<String, String> nameMap,
348 TaxonNameBase name, XMLEvent event) {
349
350 // Ranks: family, subfamily, tribus, genus, subgenus, section,
351 // subsection, species, subspecies, variety, subvariety, forma
352 // infrank, paraut, author, infrparaut, infraut, status, notes
353
354 String infrank = getAndRemoveMapKey(nameMap, INFRANK);
355 String authorStr = getAndRemoveMapKey(nameMap, AUTHOR);
356 String paraut = getAndRemoveMapKey(nameMap, PARAUT);
357
358 String infrParAut = getAndRemoveMapKey(nameMap, INFRPARAUT);
359 String infrAut = getAndRemoveMapKey(nameMap, INFRAUT);
360
361 String statusStr = getAndRemoveMapKey(nameMap, STATUS);
362 String notes = getAndRemoveMapKey(nameMap, NOTES);
363
364 if (!name.isProtectedTitleCache()) { // otherwise fullName
365
366 makeRankDecision(state, nameMap, name, event, infrank);
367
368 // test consistency of rank and authors
369 testRankAuthorConsistency(name, event, authorStr, paraut,infrParAut, infrAut);
370
371 // authors
372 makeNomenclaturalAuthors(state, event, name, authorStr, paraut, infrParAut, infrAut);
373 }
374
375 // status
376 // TODO handle pro parte, pro syn. etc.
377 if (StringUtils.isNotBlank(statusStr)) {
378 String proPartePattern = "(pro parte|p.p.)";
379 if (statusStr.matches(proPartePattern)) {
380 state.setProParte(true);
381 }
382 try {
383 // TODO handle trim earlier
384 statusStr = statusStr.trim();
385 NomenclaturalStatusType nomStatusType = NomenclaturalStatusType.getNomenclaturalStatusTypeByAbbreviation(statusStr, name);
386 name.addStatus(NomenclaturalStatus.NewInstance(nomStatusType));
387 } catch (UnknownCdmTypeException e) {
388 String message = "Status '%s' could not be recognized";
389 message = String.format(message, statusStr);
390 fireWarningEvent(message, event, 4);
391 }
392 }
393
394 // notes
395 if (StringUtils.isNotBlank(notes)) {
396 handleNotYetImplementedAttributeValue(event, CLASS, NOTES);
397 }
398
399 return;
400 }
401
402 /**
403 * @param state
404 * @param nameMap
405 * @param name
406 * @param event
407 * @param infrankStr
408 */
409 private void makeRankDecision(MarkupImportState state,
410 Map<String, String> nameMap, INonViralName name, XMLEvent event,
411 String infrankStr) {
412 // TODO ranks
413 for (String key : nameMap.keySet()) {
414 Rank rank = makeRank(state, key, false);
415 if (rank == null) {
416 handleNotYetImplementedAttributeValue(event, CLASS, key);
417 } else {
418 if (name.getRank() == null || rank.isLower(name.getRank())) {
419 name.setRank(rank);
420 }
421 String value = nameMap.get(key);
422 if (rank.isSupraGeneric() || rank.isGenus()) {
423 if ((key.equalsIgnoreCase(GENUS_ABBREVIATION)
424 && isNotBlank(state.getLatestGenusEpithet()) || isGenusAbbrev(
425 value, state.getLatestGenusEpithet()))) {
426 value = state.getLatestGenusEpithet();
427 }
428 name.setGenusOrUninomial(toFirstCapital(value));
429 } else if (rank.isInfraGeneric()) {
430 name.setInfraGenericEpithet(toFirstCapital(value));
431 } else if (rank.isSpecies()) {
432 if (state.getConfig().isAllowCapitalSpeciesEpithet()
433 && isFirstCapitalWord(value)) { // capital letters
434 // are allowed for
435 // species epithet
436 // in case of person
437 // names (e.g.
438 // Manilkara
439 // Welwitschii Engl.
440 name.setSpecificEpithet(value);
441 } else {
442 name.setSpecificEpithet(value.toLowerCase());
443 }
444 } else if (rank.isInfraSpecific()) {
445 name.setInfraSpecificEpithet(value.toLowerCase());
446 } else {
447 String message = "Invalid rank '%s'. Can't decide which epithet to fill with '%s'";
448 message = String.format(message, rank.getTitleCache(),
449 value);
450 fireWarningEvent(message, event, 4);
451 }
452 }
453
454 }
455 // handle given infrank marker
456 if (StringUtils.isNotBlank(infrankStr)) {
457 Rank infRank = makeRank(state, infrankStr, true);
458
459 if (infRank == null) {
460 String message = "Infrank '%s' rank not recognized";
461 message = String.format(message, infrankStr);
462 fireWarningEvent(message, event, 4);
463 } else {
464 if (name.getRank() == null) {
465 name.setRank(infRank);
466 } else if (infRank.isLower(name.getRank())) {
467 String message = "InfRank '%s' is lower than existing rank ";
468 message = String.format(message, infrankStr);
469 fireWarningEvent(message, event, 2);
470 name.setRank(infRank);
471 } else if (infRank.equals(name.getRank())) {
472 // nothing
473 } else {
474 String message = "InfRank '%s' is higher than existing rank ";
475 message = String.format(message, infrankStr);
476 fireWarningEvent(message, event, 2);
477 }
478 }
479 }
480 }
481
482 /**
483 * @param state
484 * @param name
485 * @param event
486 * @param authorStr
487 * @param paraut
488 * @param infrParAut
489 * @param infrAut
490 */
491 private void makeNomenclaturalAuthors(MarkupImportState state, XMLEvent event, INonViralName name,
492 String authorStr, String paraut, String infrParAut, String infrAut) {
493 if (name.getRank() != null && name.getRank().isInfraSpecific()) {
494 if (StringUtils.isNotBlank(infrAut)) {
495 TeamOrPersonBase<?>[] authorAndEx = authorAndEx(infrAut, event);
496 name.setCombinationAuthorship(authorAndEx[0]);
497 name.setExCombinationAuthorship(authorAndEx[1]);
498 }
499 if (StringUtils.isNotBlank(infrParAut)) {
500 TeamOrPersonBase<?>[] authorAndEx = authorAndEx(infrParAut,event);
501 name.setBasionymAuthorship(authorAndEx[0]);
502 name.setExBasionymAuthorship(authorAndEx[1]);
503 }
504 } else {
505 if (name.getRank() == null) {
506 String message = "No rank defined. Check correct usage of authors!";
507 fireWarningEvent(message, event, 4);
508 if (isNotBlank(infrParAut) || isNotBlank(infrAut)) {
509 authorStr = infrAut;
510 paraut = infrParAut;
511 }
512 }
513 if (StringUtils.isNotBlank(authorStr)) {
514 TeamOrPersonBase<?>[] authorAndEx = authorAndEx(authorStr, event);
515 name.setCombinationAuthorship(authorAndEx[0]);
516 name.setExCombinationAuthorship(authorAndEx[1]);
517 }
518 if (StringUtils.isNotBlank(paraut)) {
519 TeamOrPersonBase<?>[] authorAndEx = authorAndEx(paraut, event);
520 name.setBasionymAuthorship(authorAndEx[0]);
521 name.setExBasionymAuthorship(authorAndEx[1]);
522 }
523 }
524
525 //remember author for following citations
526 state.setLatestAuthorInHomotype(name.getCombinationAuthorship());
527 }
528
529 private TeamOrPersonBase<?>[] authorAndEx(String authorAndEx, XMLEvent xmlEvent) {
530 authorAndEx = authorAndEx.trim();
531 TeamOrPersonBase<?>[] result = new TeamOrPersonBase[2];
532
533 String[] split = authorAndEx.split("\\sex\\s");
534 if (split.length > 2) {
535 String message = "There is more then 1 ' ex ' in author string. Can't separate author and ex-author";
536 fireWarningEvent(message, xmlEvent, 4);
537 result[0] = createAuthor(authorAndEx);
538 } else if (split.length == 2) {
539 result[0] = createAuthor(split[1]);
540 result[1] = createAuthor(split[0]);
541 } else {
542 result[0] = createAuthor(split[0]);
543 }
544 return result;
545 }
546
547 /**
548 * Returns the (empty) name with the correct homotypical group depending on
549 * the taxon status. Throws NPE if no currentTaxon is set in state.
550 *
551 * @param state
552 * @param homotypicalGroup
553 * @param isSynonym
554 * @return
555 */
556 private NonViralName<?> createName(MarkupImportState state,
557 HomotypicalGroup homotypicalGroup, boolean isSynonym) {
558 NonViralName<?> name;
559 Taxon taxon = state.getCurrentTaxon();
560 if (isSynonym) {
561 Rank defaultRank = Rank.SPECIES(); // can be any
562 name = createNameByCode(state, defaultRank);
563 if (homotypicalGroup != null) {
564 name.setHomotypicalGroup(homotypicalGroup);
565 }
566 SynonymType synonymType = SynonymType.HETEROTYPIC_SYNONYM_OF();
567 if (taxon.getHomotypicGroup().equals(homotypicalGroup)) {
568 synonymType = SynonymType.HOMOTYPIC_SYNONYM_OF();
569 }
570 taxon.addSynonymName(name, synonymType);
571 } else {
572 name = CdmBase.deproxy(taxon.getName(), NonViralName.class);
573 }
574 return name;
575 }
576
577 private void handleCitation(MarkupImportState state, XMLEventReader reader,
578 XMLEvent parentEvent, INonViralName name, Map<String, String> nameMap) throws XMLStreamException {
579 String classValue = getClassOnlyAttribute(parentEvent);
580
581 state.setCitation(true);
582 boolean hasRefPart = false;
583 Map<String, String> refMap = new HashMap<String, String>();
584 while (reader.hasNext()) {
585 XMLEvent next = readNoWhitespace(reader);
586 if (isMyEndingElement(next, parentEvent)) {
587 checkMandatoryElement(hasRefPart, parentEvent.asStartElement(), REF_PART);
588 Reference reference = createReference(state, refMap, next);
589 String microReference = refMap.get(DETAILS);
590 doCitation(state, name, classValue, reference, microReference, parentEvent);
591 state.setCitation(false);
592 return;
593 } else if (isStartingElement(next, REF_PART)) {
594 handleRefPart(state, reader, next, refMap);
595 hasRefPart = true;
596 } else {
597 handleUnexpectedElement(next);
598 }
599 }
600 throw new IllegalStateException("Citation has no closing tag");
601
602 }
603
604 private void handleRefPart(MarkupImportState state, XMLEventReader reader,
605 XMLEvent parentEvent, Map<String, String> refMap)
606 throws XMLStreamException {
607 String classValue = getClassOnlyAttribute(parentEvent);
608
609 String text = "";
610 while (reader.hasNext()) {
611 XMLEvent next = readNoWhitespace(reader);
612 if (isMyEndingElement(next, parentEvent)) {
613 refMap.put(classValue, text);
614 return;
615 } else if (next.isStartElement()) {
616 if (isStartingElement(next, ANNOTATION)) {
617 handleNotYetImplementedElement(next); // TODO test
618 // handleSimpleAnnotation
619 } else if (isStartingElement(next, ITALICS)) {
620 handleNotYetImplementedElement(next);
621 } else if (isStartingElement(next, BOLD)) {
622 handleNotYetImplementedElement(next);
623 } else {
624 handleUnexpectedStartElement(next.asStartElement());
625 }
626 } else if (next.isCharacters()) {
627 text += next.asCharacters().getData();
628 } else {
629 handleUnexpectedEndElement(next.asEndElement());
630 }
631 }
632 throw new IllegalStateException("RefPart has no closing tag");
633
634 }
635
636 private void doCitation(MarkupImportState state, INonViralName name,
637 String classValue, Reference reference, String microCitation,
638 XMLEvent parentEvent) {
639 if (PUBLICATION.equalsIgnoreCase(classValue)) {
640 name.setNomenclaturalReference(reference);
641 name.setNomenclaturalMicroReference(microCitation);
642 } else if (USAGE.equalsIgnoreCase(classValue)) {
643 Taxon taxon = state.getCurrentTaxon();
644 TaxonDescription td = getTaxonDescription(taxon, state.getConfig().getSourceReference(), false, true);
645 TextData citation = TextData.NewInstance(Feature.CITATION());
646 // TODO name used in source
647 citation.addSource(OriginalSourceType.PrimaryTaxonomicSource, null, null, reference, microCitation);
648 td.addElement(citation);
649 } else if (TYPE.equalsIgnoreCase(classValue)) {
650 handleNotYetImplementedAttributeValue(parentEvent, CLASS, classValue);
651 } else {
652 // TODO Not yet implemented
653 handleNotYetImplementedAttributeValue(parentEvent, CLASS, classValue);
654 }
655 }
656
657 /**
658 * Tests if the names rank is consistent with the given author strings.
659 * NOTE: Tags for authors are differ depending on the rank.
660 *
661 * @param name
662 * @param event
663 * @param authorStr
664 * @param paraut
665 * @param infrParAut
666 * @param infrAut
667 */
668 private void testRankAuthorConsistency(INonViralName name, XMLEvent event,
669 String authorStr, String paraut, String infrParAut, String infrAut) {
670 if (name.getRank() == null) {
671 return;
672 }
673 if (name.getRank().isInfraSpecific()) {
674 if (StringUtils.isBlank(infrParAut)
675 && StringUtils.isBlank(infrAut) // was isNotBlank before
676 // 29.5.2012
677 && (StringUtils.isNotBlank(paraut) || StringUtils
678 .isNotBlank(authorStr)) && !name.isAutonym()) {
679 String message = "Rank is infraspecicific but has only specific or higher author(s)";
680 fireWarningEvent(message, event, 4);
681 }
682 } else {
683 // is not infraspecific
684 if (StringUtils.isNotBlank(infrParAut)
685 || StringUtils.isNotBlank(infrAut)) {
686 String message = "Rank is not infraspecicific but name has infra author(s)";
687 fireWarningEvent(message, event, 4);
688 }
689 }
690 }
691
692 private Reference createReference(MarkupImportState state,
693 Map<String, String> refMap, XMLEvent parentEvent) {
694 // TODO
695 Reference reference;
696
697 String type = getAndRemoveMapKey(refMap, PUBTYPE);
698 String authorStr = getAndRemoveMapKey(refMap, AUTHOR);
699 String titleStr = getAndRemoveMapKey(refMap, PUBTITLE);
700 String titleCache = getAndRemoveMapKey(refMap, PUBFULLNAME);
701 String volume = getAndRemoveMapKey(refMap, VOLUME);
702 String edition = getAndRemoveMapKey(refMap, EDITION);
703 String editors = getAndRemoveMapKey(refMap, EDITORS);
704 String year = getAndRemoveMapKey(refMap, YEAR);
705 String pubName = getAndRemoveMapKey(refMap, PUBNAME);
706 String pages = getAndRemoveMapKey(refMap, PAGES);
707 String publocation = getAndRemoveMapKey(refMap, PUBLOCATION);
708 String publisher = getAndRemoveMapKey(refMap, PUBLISHER);
709 String appendix = getAndRemoveMapKey(refMap, APPENDIX);
710
711 if (state.isCitation()) {
712 reference = handleCitationSpecific(state, type, authorStr,
713 titleStr, titleCache, volume, edition, editors, pubName, pages, appendix, refMap, parentEvent);
714
715 } else { // no citation
716 reference = handleNonCitationSpecific(type, authorStr, titleStr,
717 titleCache, volume, edition, editors, pubName, appendix);
718 }
719
720 //year
721 TimePeriod timeperiod = TimePeriodParser.parseString(year);
722 if (reference.getType().equals(ReferenceType.BookSection)){
723 reference.getInBook().setDatePublished(timeperiod);
724 }
725 reference.setDatePublished(timeperiod);
726
727 //Quickfix for these 2 attributes used in feature.references
728 Reference inRef = reference.getInReference() == null ? reference : reference.getInReference();
729 //publocation
730 if (StringUtils.isNotEmpty(publisher)){
731 inRef.setPublisher(publisher);
732 }
733
734 //publisher
735 if (StringUtils.isNotEmpty(publocation)){
736 inRef.setPlacePublished(publocation);
737 }
738
739 // TODO
740 String[] unhandledList = new String[] { ALTERNATEPUBTITLE, ISSUE, NOTES, STATUS };
741 for (String unhandled : unhandledList) {
742 String value = getAndRemoveMapKey(refMap, unhandled);
743 if (isNotBlank(value)) {
744 this.handleNotYetImplementedAttributeValue(parentEvent, CLASS, unhandled);
745 }
746 }
747
748 for (String key : refMap.keySet()) {
749 if (!DETAILS.equalsIgnoreCase(key)) {
750 this.fireUnexpectedAttributeValue(parentEvent, CLASS, key);
751 }
752 }
753
754 return reference;
755 }
756
757
758 /**
759 * Handles references used in the citation tag
760 * @param appendix
761 * @see #handleNonCitationSpecific(String, String, String, String, String, String, String, String)
762 */
763 private Reference handleCitationSpecific(MarkupImportState state,
764 String type, String authorStr, String titleStr, String titleCache,
765 String volume, String edition, String editors, String pubName,
766 String pages, String appendix, Map<String, String> refMap, XMLEvent parentEvent) {
767
768 if (titleStr != null){
769 String message = "Currently it is not expected that a titleStr exists in a citation";
770 fireWarningEvent(message, parentEvent, 4);
771 }
772
773 RefType refType = defineRefTypeForCitation(type, volume, editors, authorStr, pubName, parentEvent);
774 Reference reference;
775
776 if (isNotBlank(appendix)){
777 pubName = pubName == null ? appendix : (pubName + " " + appendix).replaceAll(" ", " ");
778 }
779
780 if (refType == RefType.Article) {
781 IArticle article = ReferenceFactory.newArticle();
782 if (pubName != null) {
783 IJournal journal = ReferenceFactory.newJournal();
784 journal.setTitle(pubName);
785 article.setInJournal(journal);
786 article.setVolume(volume);
787 if (isNotBlank(edition)){
788 String message = "Article must not have an edition.";
789 fireWarningEvent(message, parentEvent, 4);
790 }
791 }
792 reference = (Reference) article;
793 } else if (refType == RefType.BookSection) {
794 //Book Section
795 reference = ReferenceFactory.newBookSection();
796 IBook book = ReferenceFactory.newBook();
797 reference.setInBook(book);
798 book.setTitle(pubName);
799 book.setVolume(volume);
800 book.setEdition(edition);
801
802 if (state.getConfig().isUseEditorAsInAuthorWhereNeeded()){
803 TeamOrPersonBase<?> inAuthor = createAuthor(editors);
804 book.setAuthorship(inAuthor);
805 editors = null;
806 }
807 } else if (refType == RefType.Book){
808 //Book
809 reference = ReferenceFactory.newBook();
810 reference.setTitle(pubName);
811 reference.setVolume(volume);
812 reference.setEdition(edition);
813 }else if (refType == RefType.Generic){
814 //Generic - undefinable
815 // String message = "Can't define the type of the reference. Use generic instead";
816 // fireWarningEvent(message, parentEvent, 4);
817 reference = ReferenceFactory.newGeneric();
818 reference.setTitle(pubName);
819 reference.setEdition(edition);
820
821 //volume indicates an in-reference
822 if (isNotBlank(volume)){
823 Reference partOf = ReferenceFactory.newGeneric();
824 partOf.setVolume(volume);
825 partOf.setInReference(reference);
826 reference = partOf;
827 }
828 }else if (refType == RefType.LatestUsed){
829 Reference latestReference = state.getLatestReferenceInHomotype();
830 if (latestReference == null){
831 String message = "No former reference available for incomplete citation";
832 fireWarningEvent(message, parentEvent, 6);
833 reference = ReferenceFactory.newGeneric();
834 }else{
835 if (latestReference.getInReference() != null){
836 reference = (Reference)latestReference.clone();
837 }else{
838 String message = "Latest reference is not an in-reference. This is not yet handled.";
839 fireWarningEvent(message, parentEvent, 6);
840 reference = ReferenceFactory.newGeneric();
841 }
842 }
843 reference.setVolume(volume);
844 if (isNotBlank(edition)){
845 String message = "Edition not yet handled for incomplete citations";
846 fireWarningEvent(message, parentEvent, 4);
847 }
848
849 }else{
850 String message = "Unhandled reference type: %s" ;
851 fireWarningEvent(String.format(message, refType.toString()), parentEvent, 8);
852 reference = ReferenceFactory.newGeneric();
853 }
854
855 //author
856 TeamOrPersonBase<?> author;
857 if (isBlank(authorStr)){
858 if (refType != RefType.LatestUsed){
859 author = state.getLatestAuthorInHomotype();
860 reference.setAuthorship(author);
861 }
862 }else{
863 author = createAuthor(authorStr);
864 state.setLatestAuthorInHomotype(author);
865 reference.setAuthorship(author);
866 }
867
868
869 //title, titleCache
870 handleTitlesInCitation(titleStr, titleCache, parentEvent, reference);
871
872 //editors
873 handleEditorsInCitation(edition, editors, reference, parentEvent);
874
875 //pages
876 handlePages(state, refMap, parentEvent, reference, pages);
877
878 //remember reference for following citation
879 state.setLatestReferenceInHomotype(reference);
880
881 return reference;
882 }
883
884 private void handleEditorsInCitation(String edition, String editors, Reference reference, XMLEvent parentEvent) {
885 //editor
886 reference.setEditor(editors);
887 if ( editors != null){
888 String message = "Citation reference has an editor. This is unusual for a citation reference (appears regularly in <reference> references";
889 fireWarningEvent(message, parentEvent, 4);
890 }
891 }
892
893 private void handleTitlesInCitation(String titleStr, String titleCache,
894 XMLEvent parentEvent, Reference reference) {
895 if (isNotBlank(titleStr)){
896 reference.setTitle(titleStr);
897 }
898 //titleCache
899 if (StringUtils.isNotBlank(titleCache)) {
900 reference.setTitleCache(titleCache, true);
901 }
902 if (titleStr != null || titleCache != null){
903 String message = "Citation reference has a title or a full title. Both is unusual for a citation reference (appears regularly in <reference> references";
904 fireWarningEvent(message, parentEvent, 4);
905 }
906 }
907
908 private enum RefType{
909 Article,
910 BookSection,
911 Book,
912 Generic,
913 LatestUsed
914 }
915
916 private RefType defineRefTypeForCitation(String type, String volume, String editors,
917 String authorStr, String pubName, XMLEvent parentEvent) {
918 if ("journal".equalsIgnoreCase(type)){
919 return RefType.Article;
920 }else {
921 if (editors == null){
922 //no editors
923 if (pubName == null){
924 //looks like we need to use reference info from former citations here
925 return RefType.LatestUsed;
926 }else if (volume == null){
927 return RefType.Book; //Book must not have in-authors
928 }else{
929 return RefType.Generic;
930 }
931
932 }else{
933 //editors
934 if (pubName != null){
935 return RefType.BookSection;
936 }else{
937 String message = "Unexpected state: Citation has editors but no pubName";
938 fireWarningEvent(message, parentEvent, 4);
939 return RefType.Generic;
940 }
941 }
942 }
943 }
944
945
946 private boolean isArticle(String type, String volume, String editors) {
947 if ("journal".equalsIgnoreCase(type)){
948 return true;
949 }else if (volume != null && editors == null){
950 return true;
951 }else{
952 return false;
953 }
954 }
955
956 /**
957 * in work
958 * @param appendix
959 * @return
960 */
961 private Reference handleNonCitationSpecific(String type, String authorStr,
962 String titleStr, String titleCache, String volume, String edition,
963 String editors, String pubName, String appendix) {
964
965 Reference reference;
966
967 if (isNotBlank(appendix)){
968 pubName = pubName == null ? appendix : (pubName + " " + appendix).replaceAll(" ", " ");
969 }
970
971 if (isArticle(type, volume, editors)) {
972 IArticle article = ReferenceFactory.newArticle();
973 if (pubName != null) {
974 IJournal journal = ReferenceFactory.newJournal();
975 journal.setTitle(pubName);
976 article.setInJournal(journal);
977 }
978 reference = (Reference) article;
979
980 } else {
981 Reference bookOrPartOf = ReferenceFactory.newGeneric();
982 reference = bookOrPartOf;
983 }
984
985 // TODO type
986 TeamOrPersonBase<?> author = createAuthor(authorStr);
987 reference.setAuthorship(author);
988
989 //title
990 reference.setTitle(titleStr);
991 if (StringUtils.isNotBlank(titleCache)) {
992 reference.setTitleCache(titleCache, true);
993 }
994
995 //edition
996 reference.setEdition(edition);
997 reference.setEditor(editors);
998
999 //pubName
1000 if (pubName != null) {
1001 Reference inReference;
1002 if (reference.getType().equals(ReferenceType.Article)) {
1003 inReference = ReferenceFactory.newJournal();
1004 } else {
1005 inReference = ReferenceFactory.newGeneric();
1006 }
1007 inReference.setTitle(pubName);
1008 reference.setInReference(inReference);
1009 }
1010
1011 //volume
1012 reference.setVolume(volume);
1013 return reference;
1014 }
1015
1016 private void handlePages(MarkupImportState state,
1017 Map<String, String> refMap, XMLEvent parentEvent,
1018 Reference reference, String pages) {
1019 // TODO check if this is handled correctly in FM markup
1020 boolean switchPages = state.getConfig().isHandlePagesAsDetailWhereNeeded();
1021 if (switchPages){
1022 if (pages != null ){
1023 String detail = refMap.get(DETAILS);
1024 if (isBlank(detail)){
1025 if (pages.contains("-")){
1026 String message = "There is a pages tag with '-'. Unclear if this really means pages";
1027 fireWarningEvent(message, parentEvent, 8);
1028 reference.setPages(pages);
1029 }else{
1030 //handle pages as detail, this is at least true for Flora Malesiana
1031 refMap.put(DETAILS, pages);
1032 }
1033 }else{
1034 if (! pages.contains("-")){
1035 String message = "There are pages and detail available where pages may also hold details information.";
1036 fireWarningEvent(message, parentEvent, 8);
1037 }
1038 reference.setPages(pages);
1039 }
1040 }
1041 }
1042 }
1043
1044 public Reference handleReference(MarkupImportState state,
1045 XMLEventReader reader, XMLEvent parentEvent)
1046 throws XMLStreamException {
1047 checkNoAttributes(parentEvent);
1048
1049 boolean hasRefPart = false;
1050 Map<String, String> refMap = new HashMap<String, String>();
1051 while (reader.hasNext()) {
1052 XMLEvent next = readNoWhitespace(reader);
1053 if (isMyEndingElement(next, parentEvent)) {
1054 checkMandatoryElement(hasRefPart, parentEvent.asStartElement(), REF_PART);
1055 Reference reference = createReference(state, refMap, next);
1056 return reference;
1057 } else if (isStartingElement(next, REF_PART)) {
1058 handleRefPart(state, reader, next, refMap);
1059 hasRefPart = true;
1060 } else {
1061 handleUnexpectedElement(next);
1062 }
1063 }
1064 // TODO handle missing end element
1065 throw new IllegalStateException("<Reference> has no closing tag");
1066 }
1067
1068 }