update factory methods for original sources #1549
[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.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.NonViralNameParserImpl;
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 MarkupKeyImport keyImport;
63 private MarkupSpecimenImport specimenImport;
64
65 public MarkupNomenclatureImport(MarkupDocumentImport docImport,
66 MarkupKeyImport keyImport, MarkupSpecimenImport specimenImport) {
67 super(docImport);
68 this.keyImport = keyImport;
69 this.specimenImport = specimenImport;
70 }
71
72 public void handleNomenclature(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent)
73 throws XMLStreamException {
74 checkNoAttributes(parentEvent);
75
76 while (reader.hasNext()) {
77 XMLEvent next = readNoWhitespace(reader);
78 if (isStartingElement(next, HOMOTYPES)) {
79 handleHomotypes(state, reader, next.asStartElement());
80 } else if (isMyEndingElement(next, parentEvent)) {
81 return;
82 } else {
83 fireSchemaConflictEventExpectedStartTag(HOMOTYPES, reader);
84 state.setUnsuccessfull();
85 }
86 }
87 return;
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 (next.isEndElement()) {
101 if (isMyEndingElement(next, parentEvent)) {
102 checkMandatoryElement(hasNom, parentEvent, NOM);
103 return;
104 } else {
105 if (isEndingElement(next, NAME_TYPE)) {
106 state.setNameType(false);
107 } else if (isEndingElement(next, NOTES)) {
108 // NOT YET IMPLEMENTED
109 popUnimplemented(next.asEndElement());
110 } else {
111 handleUnexpectedEndElement(next.asEndElement());
112 }
113 }
114 } else if (next.isStartElement()) {
115 if (isStartingElement(next, NOM)) {
116 NonViralName<?> name = handleNom(state, reader, next, homotypicalGroup);
117 homotypicalGroup = name.getHomotypicalGroup();
118 hasNom = true;
119 } else if (isStartingElement(next, NAME_TYPE)) {
120 state.setNameType(true);
121 handleNameType(state, reader, next, homotypicalGroup);
122 } else if (isStartingElement(next, SPECIMEN_TYPE)) {
123 specimenImport.handleSpecimenType(state, reader, next,
124 homotypicalGroup);
125 } else if (isStartingElement(next, NOTES)) {
126 handleNotYetImplementedElement(next);
127 } else {
128 handleUnexpectedStartElement(next);
129 }
130 } else {
131 handleUnexpectedElement(next);
132 }
133 }
134 // TODO handle missing end element
135 throw new IllegalStateException("Homotypes has no closing tag");
136
137 }
138
139 private void handleNameType(MarkupImportState state, XMLEventReader reader,
140 XMLEvent parentEvent, HomotypicalGroup homotypicalGroup)
141 throws XMLStreamException {
142 Map<String, Attribute> attributes = getAttributes(parentEvent);
143 String typeStatus = getAndRemoveAttributeValue(attributes, TYPE_STATUS);
144 checkNoAttributes(attributes, parentEvent);
145
146 NameTypeDesignationStatus status;
147 try {
148 status = NameTypeParser.parseNameTypeStatus(typeStatus);
149 } catch (UnknownCdmTypeException e) {
150 String message = "Type status could not be recognized: %s";
151 message = String.format(message, typeStatus);
152 fireWarningEvent(message, parentEvent, 4);
153 status = null;
154 }
155
156 boolean hasNom = false;
157 while (reader.hasNext()) {
158 XMLEvent next = readNoWhitespace(reader);
159 if (next.isEndElement()) {
160 if (isMyEndingElement(next, parentEvent)) {
161 checkMandatoryElement(hasNom, parentEvent.asStartElement(),
162 NOM);
163 state.setNameType(false);
164 return;
165 } else {
166 if (isEndingElement(next, ACCEPTED_NAME)) {
167 // NOT YET IMPLEMENTED
168 popUnimplemented(next.asEndElement());
169 } else {
170 handleUnexpectedEndElement(next.asEndElement());
171 }
172 }
173 } else if (next.isStartElement()) {
174 if (isStartingElement(next, NOM)) {
175 // TODO should we check if the type is always a species, is
176 // this a rule?
177 NonViralName<?> speciesName = handleNom(state, reader,
178 next, null);
179 for (TaxonNameBase<?, ?> name : homotypicalGroup
180 .getTypifiedNames()) {
181 name.addNameTypeDesignation(speciesName, null, null,
182 null, status, false, false, false, false);
183 }
184 hasNom = true;
185 } else if (isStartingElement(next, ACCEPTED_NAME)) {
186 handleNotYetImplementedElement(next);
187 } else {
188 handleUnexpectedStartElement(next);
189 }
190 } else {
191 handleUnexpectedElement(next);
192 }
193 }
194 // TODO handle missing end element
195 throw new IllegalStateException("Homotypes has no closing tag");
196
197 }
198
199 /**
200 * Creates the name defined by a nom tag. Adds it to the given homotypical
201 * group (if not null).
202 *
203 * @param state
204 * @param reader
205 * @param parentEvent
206 * @param homotypicalGroup
207 * @return
208 * @throws XMLStreamException
209 */
210 private NonViralName<?> handleNom(MarkupImportState state,
211 XMLEventReader reader, XMLEvent parentEvent,
212 HomotypicalGroup homotypicalGroup) throws XMLStreamException {
213 boolean isSynonym = false;
214 boolean isNameType = state.isNameType();
215 // attributes
216 String classValue = getClassOnlyAttribute(parentEvent);
217 NonViralName<?> name;
218 if (!isNameType && ACCEPTED.equalsIgnoreCase(classValue)) {
219 isSynonym = false;
220 name = createName(state, homotypicalGroup, isSynonym);
221 } else if (!isNameType && SYNONYM.equalsIgnoreCase(classValue)) {
222 isSynonym = true;
223 name = createName(state, homotypicalGroup, isSynonym);
224 } else if (isNameType && NAME_TYPE.equalsIgnoreCase(classValue)) {
225 // TODO do we need to define the rank here?
226 name = createNameByCode(state, null);
227 } else {
228 fireUnexpectedAttributeValue(parentEvent, CLASS, classValue);
229 name = createNameByCode(state, null);
230 }
231
232 Map<String, String> nameMap = new HashMap<String, String>();
233 String text = "";
234
235 while (reader.hasNext()) {
236 XMLEvent next = readNoWhitespace(reader);
237 if (isMyEndingElement(next, parentEvent)) {
238 // fill the name with all data gathered
239 fillName(state, nameMap, name, next);
240 handleNomText(state, parentEvent, text, isNameType);
241 return name;
242 } else if (isEndingElement(next, ANNOTATION)) {
243 // NOT YET IMPLEMENTED //TODO test
244 // handleSimpleAnnotation
245 popUnimplemented(next.asEndElement());
246 }else if (isStartingElement(next, FULL_NAME)) {
247 handleFullName(state, reader, name, next);
248 } else if (isStartingElement(next, NUM)) {
249 handleNomNum(state, reader, next);
250 } else if (isStartingElement(next, NAME)) {
251 handleName(state, reader, next, nameMap);
252 } else if (isStartingElement(next, CITATION)) {
253 handleCitation(state, reader, next, name);
254 } else if (next.isCharacters()) {
255 text += next.asCharacters().getData();
256 } else if (isStartingElement(next, HOMONYM)) {
257 handleNotYetImplementedElement(next);
258 } else if (isStartingElement(next, NOTES)) {
259 handleNotYetImplementedElement(next);
260 } else if (isStartingElement(next, ANNOTATION)) {
261 handleNotYetImplementedElement(next);
262 } else {
263 handleUnexpectedElement(next);
264 }
265 }
266 // TODO handle missing end element
267 throw new IllegalStateException("Nom has no closing tag");
268 }
269
270 /**
271 * Handles appearance of text within <nom> tags.
272 * Usually this is not expected except for some information that is already handled
273 * elsewhere, e.g. the string Nametype is holding information that is available already
274 * via the surrounding nametype tag. Therefore this information can be neglected.
275 * This method is open for upcoming cases which need to be handled.
276 * @param state
277 * @param event
278 * @param text
279 * @param isNameType
280 */
281 private void handleNomText(MarkupImportState state, XMLEvent event, String text, boolean isNameType) {
282 if (isBlank(text)){
283 return;
284 }
285 text = text.trim();
286 //neglect known redundant strings
287 if (isNameType && text.matches("(?i)^Esp[\u00E8\u00C8]ce[·\\-\\s]type\\:$")){
288 return;
289 }//neglect meaningless punctuation
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 * @param state
323 * @param reader
324 * @param name
325 * @param next
326 * @throws XMLStreamException
327 */
328 private void handleFullName(MarkupImportState state, XMLEventReader reader,
329 NonViralName<?> name, XMLEvent next) throws XMLStreamException {
330 String fullNameStr;
331 Map<String, Attribute> attrs = getAttributes(next);
332 String rankStr = getAndRemoveRequiredAttributeValue(next,
333 attrs, "rank");
334 Rank rank = makeRank(state, rankStr, false);
335 name.setRank(rank);
336 if (rank == null) {
337 String message = "Rank was computed as null. This must not be.";
338 fireWarningEvent(message, next, 6);
339 name.setRank(Rank.UNKNOWN_RANK());
340 }
341 if (!attrs.isEmpty()) {
342 handleUnexpectedAttributes(next.getLocation(), attrs);
343 }
344 fullNameStr = getCData(state, reader, next);
345 name.setTitleCache(fullNameStr, true);
346 }
347
348 private void handleName(MarkupImportState state, XMLEventReader reader,
349 XMLEvent parentEvent, Map<String, String> nameMap)
350 throws XMLStreamException {
351 String classValue = getClassOnlyAttribute(parentEvent);
352
353 String text = "";
354 while (reader.hasNext()) {
355 XMLEvent next = readNoWhitespace(reader);
356 if (isMyEndingElement(next, parentEvent)) {
357 nameMap.put(classValue, text);
358 return;
359 } else if (isStartingElement(next, ANNOTATION)) {
360 handleNotYetImplementedElement(next); // TODO test
361 // handleSimpleAnnotation
362 } else if (next.isCharacters()) {
363 text += next.asCharacters().getData();
364 } else {
365 handleUnexpectedElement(next);
366 }
367 }
368 throw new IllegalStateException("name has no closing tag");
369 }
370
371 private void fillName(MarkupImportState state, Map<String, String> nameMap,
372 NonViralName<?> name, XMLEvent event) {
373
374 // Ranks: family, subfamily, tribus, genus, subgenus, section,
375 // subsection, species, subspecies, variety, subvariety, forma
376 // infrank, paraut, author, infrparaut, infraut, status, notes
377
378 String infrank = getAndRemoveMapKey(nameMap, INFRANK);
379 String authorStr = getAndRemoveMapKey(nameMap, AUTHOR);
380 String paraut = getAndRemoveMapKey(nameMap, PARAUT);
381
382 String infrParAut = getAndRemoveMapKey(nameMap, INFRPARAUT);
383 String infrAut = getAndRemoveMapKey(nameMap, INFRAUT);
384
385 String statusStr = getAndRemoveMapKey(nameMap, STATUS);
386 String notes = getAndRemoveMapKey(nameMap, NOTES);
387
388 if (!name.isProtectedTitleCache()) { // otherwise fullName
389
390 makeRankDecision(state, nameMap, name, event, infrank);
391
392 // test consistency of rank and authors
393 testRankAuthorConsistency(name, event, authorStr, paraut,
394 infrParAut, infrAut);
395
396 // authors
397 makeNomenclaturalAuthors(name, event, authorStr, paraut,
398 infrParAut, infrAut);
399 }
400
401 // status
402 // TODO handle pro parte, pro syn. etc.
403 if (StringUtils.isNotBlank(statusStr)) {
404 String proPartePattern = "(pro parte|p.p.)";
405 if (statusStr.matches(proPartePattern)) {
406 state.setProParte(true);
407 }
408 try {
409 // TODO handle trim earlier
410 statusStr = statusStr.trim();
411 NomenclaturalStatusType nomStatusType = NomenclaturalStatusType
412 .getNomenclaturalStatusTypeByAbbreviation(statusStr);
413 name.addStatus(NomenclaturalStatus.NewInstance(nomStatusType));
414 } catch (UnknownCdmTypeException e) {
415 String message = "Status '%s' could not be recognized";
416 message = String.format(message, statusStr);
417 fireWarningEvent(message, event, 4);
418 }
419 }
420
421 // notes
422 if (StringUtils.isNotBlank(notes)) {
423 handleNotYetImplementedAttributeValue(event, CLASS, NOTES);
424 }
425
426 return;
427 }
428
429 /**
430 * @param state
431 * @param nameMap
432 * @param name
433 * @param event
434 * @param infrankStr
435 */
436 private void makeRankDecision(MarkupImportState state,
437 Map<String, String> nameMap, NonViralName<?> name, XMLEvent event,
438 String infrankStr) {
439 // TODO ranks
440 for (String key : nameMap.keySet()) {
441 Rank rank = makeRank(state, key, false);
442 if (rank == null) {
443 handleNotYetImplementedAttributeValue(event, CLASS, key);
444 } else {
445 if (name.getRank() == null || rank.isLower(name.getRank())) {
446 name.setRank(rank);
447 }
448 String value = nameMap.get(key);
449 if (rank.isSupraGeneric() || rank.isGenus()) {
450 if ((key.equalsIgnoreCase(GENUS_ABBREVIATION)
451 && isNotBlank(state.getLatestGenusEpithet()) || isGenusAbbrev(
452 value, state.getLatestGenusEpithet()))) {
453 value = state.getLatestGenusEpithet();
454 }
455 name.setGenusOrUninomial(toFirstCapital(value));
456 } else if (rank.isInfraGeneric()) {
457 name.setInfraGenericEpithet(toFirstCapital(value));
458 } else if (rank.isSpecies()) {
459 if (state.getConfig().isAllowCapitalSpeciesEpithet()
460 && isFirstCapitalWord(value)) { // capital letters
461 // are allowed for
462 // species epithet
463 // in case of person
464 // names (e.g.
465 // Manilkara
466 // Welwitschii Engl.
467 name.setSpecificEpithet(value);
468 } else {
469 name.setSpecificEpithet(value.toLowerCase());
470 }
471 } else if (rank.isInfraSpecific()) {
472 name.setInfraSpecificEpithet(value.toLowerCase());
473 } else {
474 String message = "Invalid rank '%s'. Can't decide which epithet to fill with '%s'";
475 message = String.format(message, rank.getTitleCache(),
476 value);
477 fireWarningEvent(message, event, 4);
478 }
479 }
480
481 }
482 // handle given infrank marker
483 if (StringUtils.isNotBlank(infrankStr)) {
484 Rank infRank = makeRank(state, infrankStr, true);
485
486 if (infRank == null) {
487 String message = "Infrank '%s' rank not recognized";
488 message = String.format(message, infrankStr);
489 fireWarningEvent(message, event, 4);
490 } else {
491 if (name.getRank() == null) {
492 name.setRank(infRank);
493 } else if (infRank.isLower(name.getRank())) {
494 String message = "InfRank '%s' is lower than existing rank ";
495 message = String.format(message, infrankStr);
496 fireWarningEvent(message, event, 2);
497 name.setRank(infRank);
498 } else if (infRank.equals(name.getRank())) {
499 // nothing
500 } else {
501 String message = "InfRank '%s' is higher than existing rank ";
502 message = String.format(message, infrankStr);
503 fireWarningEvent(message, event, 2);
504 }
505 }
506 }
507 }
508
509 /**
510 * @param name
511 * @param event
512 * @param authorStr
513 * @param paraut
514 * @param infrParAut
515 * @param infrAut
516 */
517 private void makeNomenclaturalAuthors(NonViralName name, XMLEvent event,
518 String authorStr, String paraut, String infrParAut, String infrAut) {
519 if (name.getRank() != null && name.getRank().isInfraSpecific()) {
520 if (StringUtils.isNotBlank(infrAut)) {
521 INomenclaturalAuthor[] authorAndEx = authorAndEx(infrAut, event);
522 name.setCombinationAuthorTeam(authorAndEx[0]);
523 name.setExCombinationAuthorTeam(authorAndEx[1]);
524 }
525 if (StringUtils.isNotBlank(infrParAut)) {
526 INomenclaturalAuthor[] authorAndEx = authorAndEx(infrParAut,
527 event);
528 name.setBasionymAuthorTeam(authorAndEx[0]);
529 name.setExBasionymAuthorTeam(authorAndEx[1]);
530 }
531 } else {
532 if (name.getRank() == null) {
533 String message = "No rank defined. Check correct usage of authors!";
534 fireWarningEvent(message, event, 4);
535 if (isNotBlank(infrParAut) || isNotBlank(infrAut)) {
536 authorStr = infrAut;
537 paraut = infrParAut;
538 }
539 }
540 if (StringUtils.isNotBlank(authorStr)) {
541 INomenclaturalAuthor[] authorAndEx = authorAndEx(authorStr,
542 event);
543 name.setCombinationAuthorTeam(authorAndEx[0]);
544 name.setExCombinationAuthorTeam(authorAndEx[1]);
545 }
546 if (StringUtils.isNotBlank(paraut)) {
547 INomenclaturalAuthor[] authorAndEx = authorAndEx(paraut, event);
548 name.setBasionymAuthorTeam(authorAndEx[0]);
549 name.setExBasionymAuthorTeam(authorAndEx[1]);
550 }
551 }
552 }
553
554 private TeamOrPersonBase[] authorAndEx(String authorAndEx, XMLEvent xmlEvent) {
555 authorAndEx = authorAndEx.trim();
556 TeamOrPersonBase[] result = new TeamOrPersonBase[2];
557
558 String[] split = authorAndEx.split("\\sex\\s");
559 if (split.length > 2) {
560 String message = "There is more then 1 ' ex ' in author string. Can't separate author and ex-author";
561 fireWarningEvent(message, xmlEvent, 4);
562 result[0] = createAuthor(authorAndEx);
563 } else if (split.length == 2) {
564 result[0] = createAuthor(split[1]);
565 result[1] = createAuthor(split[0]);
566 } else {
567 result[0] = createAuthor(split[0]);
568 }
569 return result;
570 }
571
572 /**
573 * Returns the (empty) name with the correct homotypical group depending on
574 * the taxon status. Throws NPE if no currentTaxon is set in state.
575 *
576 * @param state
577 * @param homotypicalGroup
578 * @param isSynonym
579 * @return
580 */
581 private NonViralName<?> createName(MarkupImportState state,
582 HomotypicalGroup homotypicalGroup, boolean isSynonym) {
583 NonViralName<?> name;
584 Taxon taxon = state.getCurrentTaxon();
585 if (isSynonym) {
586 Rank defaultRank = Rank.SPECIES(); // can be any
587 name = createNameByCode(state, defaultRank);
588 if (homotypicalGroup != null) {
589 name.setHomotypicalGroup(homotypicalGroup);
590 }
591 SynonymRelationshipType synonymType = SynonymRelationshipType
592 .HETEROTYPIC_SYNONYM_OF();
593 if (taxon.getHomotypicGroup().equals(homotypicalGroup)) {
594 synonymType = SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF();
595 }
596 taxon.addSynonymName(name, synonymType);
597 } else {
598 name = CdmBase.deproxy(taxon.getName(), NonViralName.class);
599 }
600 return name;
601 }
602
603 private void handleCitation(MarkupImportState state, XMLEventReader reader,
604 XMLEvent parentEvent, NonViralName name) throws XMLStreamException {
605 String classValue = getClassOnlyAttribute(parentEvent);
606
607 state.setCitation(true);
608 boolean hasRefPart = false;
609 Map<String, String> refMap = new HashMap<String, String>();
610 while (reader.hasNext()) {
611 XMLEvent next = readNoWhitespace(reader);
612 if (isMyEndingElement(next, parentEvent)) {
613 checkMandatoryElement(hasRefPart, parentEvent.asStartElement(),
614 REF_PART);
615 Reference<?> reference = createReference(state, refMap, next);
616 String microReference = refMap.get(DETAILS);
617 doCitation(state, name, classValue, reference, microReference,
618 parentEvent);
619 state.setCitation(false);
620 return;
621 } else if (isStartingElement(next, REF_PART)) {
622 handleRefPart(state, reader, next, refMap);
623 hasRefPart = true;
624 } else {
625 handleUnexpectedElement(next);
626 }
627 }
628 throw new IllegalStateException("Citation has no closing tag");
629
630 }
631
632 private void handleRefPart(MarkupImportState state, XMLEventReader reader,
633 XMLEvent parentEvent, Map<String, String> refMap)
634 throws XMLStreamException {
635 String classValue = getClassOnlyAttribute(parentEvent);
636
637 String text = "";
638 while (reader.hasNext()) {
639 XMLEvent next = readNoWhitespace(reader);
640 if (isMyEndingElement(next, parentEvent)) {
641 refMap.put(classValue, text);
642 return;
643 } else if (next.isStartElement()) {
644 if (isStartingElement(next, ANNOTATION)) {
645 handleNotYetImplementedElement(next); // TODO test
646 // handleSimpleAnnotation
647 } else if (isStartingElement(next, ITALICS)) {
648 handleNotYetImplementedElement(next);
649 } else if (isStartingElement(next, BOLD)) {
650 handleNotYetImplementedElement(next);
651 } else {
652 handleUnexpectedStartElement(next.asStartElement());
653 }
654 } else if (next.isCharacters()) {
655 text += next.asCharacters().getData();
656 } else {
657 handleUnexpectedEndElement(next.asEndElement());
658 }
659 }
660 throw new IllegalStateException("RefPart has no closing tag");
661
662 }
663
664 private void doCitation(MarkupImportState state, NonViralName name,
665 String classValue, Reference reference, String microCitation,
666 XMLEvent parentEvent) {
667 if (PUBLICATION.equalsIgnoreCase(classValue)) {
668 name.setNomenclaturalReference(reference);
669 name.setNomenclaturalMicroReference(microCitation);
670 } else if (USAGE.equalsIgnoreCase(classValue)) {
671 Taxon taxon = state.getCurrentTaxon();
672 TaxonDescription td = getTaxonDescription(taxon, state.getConfig()
673 .getSourceReference(), false, true);
674 TextData citation = TextData.NewInstance(Feature.CITATION());
675 // TODO name used in source
676 citation.addSource(OriginalSourceType.PrimaryTaxonomicSource, null, null, reference, microCitation);
677 td.addElement(citation);
678 } else if (TYPE.equalsIgnoreCase(classValue)) {
679 handleNotYetImplementedAttributeValue(parentEvent, CLASS,
680 classValue);
681 } else {
682 // TODO Not yet implemented
683 handleNotYetImplementedAttributeValue(parentEvent, CLASS,
684 classValue);
685 }
686 }
687
688 /**
689 * Tests if the names rank is consistent with the given author strings.
690 * NOTE: Tags for authors are differ depending on the rank.
691 *
692 * @param name
693 * @param event
694 * @param authorStr
695 * @param paraut
696 * @param infrParAut
697 * @param infrAut
698 */
699 private void testRankAuthorConsistency(NonViralName name, XMLEvent event,
700 String authorStr, String paraut, String infrParAut, String infrAut) {
701 if (name.getRank() == null) {
702 return;
703 }
704 if (name.getRank().isInfraSpecific()) {
705 if (StringUtils.isBlank(infrParAut)
706 && StringUtils.isBlank(infrAut) // was isNotBlank before
707 // 29.5.2012
708 && (StringUtils.isNotBlank(paraut) || StringUtils
709 .isNotBlank(authorStr)) && !name.isAutonym()) {
710 String message = "Rank is infraspecicific but has only specific or higher author(s)";
711 fireWarningEvent(message, event, 4);
712 }
713 } else {
714 // is not infraspecific
715 if (StringUtils.isNotBlank(infrParAut)
716 || StringUtils.isNotBlank(infrAut)) {
717 String message = "Rank is not infraspecicific but name has infra author(s)";
718 fireWarningEvent(message, event, 4);
719 }
720 }
721 }
722
723 private Reference<?> createReference(MarkupImportState state,
724 Map<String, String> refMap, XMLEvent parentEvent) {
725 // TODO
726 Reference<?> reference;
727
728 String type = getAndRemoveMapKey(refMap, PUBTYPE);
729 String authorStr = getAndRemoveMapKey(refMap, AUTHOR);
730 String titleStr = getAndRemoveMapKey(refMap, PUBTITLE);
731 String titleCache = getAndRemoveMapKey(refMap, PUBFULLNAME);
732 String volume = getAndRemoveMapKey(refMap, VOLUME);
733 String edition = getAndRemoveMapKey(refMap, EDITION);
734 String editors = getAndRemoveMapKey(refMap, EDITORS);
735 String year = getAndRemoveMapKey(refMap, YEAR);
736 String pubName = getAndRemoveMapKey(refMap, PUBNAME);
737 String pages = getAndRemoveMapKey(refMap, PAGES);
738
739 if (state.isCitation()) {
740 if (volume != null || "journal".equalsIgnoreCase(type)) {
741 IArticle article = ReferenceFactory.newArticle();
742 if (pubName != null) {
743 IJournal journal = ReferenceFactory.newJournal();
744 journal.setTitle(pubName);
745 article.setInJournal(journal);
746 }
747 reference = (Reference<?>) article;
748
749 } else {
750 // TODO
751 if (pubName != null) {
752 reference = ReferenceFactory.newBookSection();
753 } else {
754 reference = ReferenceFactory.newBook();
755 }
756 }
757 // TODO use existing author from name or before
758 TeamOrPersonBase<?> author = createAuthor(authorStr);
759 reference.setAuthorTeam(author);
760
761 reference.setTitle(titleStr);
762 if (StringUtils.isNotBlank(titleCache)) {
763 reference.setTitleCache(titleCache, true);
764 }
765 reference.setEdition(edition);
766 reference.setEditor(editors);
767
768 if (pubName != null) {
769 Reference<?> inReference;
770 if (reference.getType().equals(ReferenceType.Article)) {
771 inReference = ReferenceFactory.newJournal();
772 } else {
773 inReference = ReferenceFactory.newGeneric();
774 }
775 inReference.setTitle(pubName);
776 reference.setInReference(inReference);
777 }
778
779 } else { // no citation
780 if (volume != null || "journal".equalsIgnoreCase(type)) {
781 IArticle article = ReferenceFactory.newArticle();
782 if (pubName != null) {
783 IJournal journal = ReferenceFactory.newJournal();
784 journal.setTitle(pubName);
785 article.setInJournal(journal);
786 }
787 reference = (Reference<?>) article;
788
789 } else {
790 Reference<?> bookOrPartOf = ReferenceFactory.newGeneric();
791 reference = bookOrPartOf;
792 }
793
794 // TODO type
795 TeamOrPersonBase<?> author = createAuthor(authorStr);
796 reference.setAuthorTeam(author);
797
798 reference.setTitle(titleStr);
799 if (StringUtils.isNotBlank(titleCache)) {
800 reference.setTitleCache(titleCache, true);
801 }
802 reference.setEdition(edition);
803 reference.setEditor(editors);
804
805 if (pubName != null) {
806 Reference<?> inReference;
807 if (reference.getType().equals(ReferenceType.Article)) {
808 inReference = ReferenceFactory.newJournal();
809 } else {
810 inReference = ReferenceFactory.newGeneric();
811 }
812 inReference.setTitle(pubName);
813 reference.setInReference(inReference);
814 }
815 }
816 reference.setVolume(volume);
817 reference.setDatePublished(TimePeriod.parseString(year));
818 // TODO check if this is handled correctly in FM markup
819 reference.setPages(pages);
820
821 // TODO
822 String[] unhandledList = new String[] { ALTERNATEPUBTITLE, ISSUE,
823 NOTES, STATUS };
824 for (String unhandled : unhandledList) {
825 String value = getAndRemoveMapKey(refMap, unhandled);
826 if (isNotBlank(value)) {
827 this.handleNotYetImplementedAttributeValue(parentEvent, CLASS,
828 unhandled);
829 }
830 }
831
832 for (String key : refMap.keySet()) {
833 if (!DETAILS.equalsIgnoreCase(key)) {
834 this.fireUnexpectedAttributeValue(parentEvent, CLASS, key);
835 }
836 }
837
838 return reference;
839 }
840
841 public Reference<?> handleReference(MarkupImportState state,
842 XMLEventReader reader, XMLEvent parentEvent)
843 throws XMLStreamException {
844 checkNoAttributes(parentEvent);
845
846 boolean hasRefPart = false;
847 Map<String, String> refMap = new HashMap<String, String>();
848 while (reader.hasNext()) {
849 XMLEvent next = readNoWhitespace(reader);
850 if (isMyEndingElement(next, parentEvent)) {
851 checkMandatoryElement(hasRefPart, parentEvent.asStartElement(),
852 REF_PART);
853 Reference<?> reference = createReference(state, refMap, next);
854 return reference;
855 } else if (isStartingElement(next, REF_PART)) {
856 handleRefPart(state, reader, next, refMap);
857 hasRefPart = true;
858 } else {
859 handleUnexpectedElement(next);
860 }
861 }
862 // TODO handle missing end element
863 throw new IllegalStateException("<Reference> has no closing tag");
864 }
865
866 }