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