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