2 * Copyright (C) 2009 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
10 package eu
.etaxonomy
.cdm
.io
.markup
;
12 import java
.util
.HashMap
;
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
;
21 import org
.apache
.commons
.lang
.StringUtils
;
22 import org
.apache
.log4j
.Logger
;
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
;
56 public class MarkupNomenclatureImport
extends MarkupImportBase
{
57 @SuppressWarnings("unused")
58 private static final Logger logger
= Logger
.getLogger(MarkupNomenclatureImport
.class);
61 // private NonViralNameParserImpl parser = new NonViralNameParserImpl();
63 private MarkupSpecimenImport specimenImport
;
65 public MarkupNomenclatureImport(MarkupDocumentImport docImport
, MarkupSpecimenImport specimenImport
) {
67 this.specimenImport
= specimenImport
;
70 public void handleNomenclature(MarkupImportState state
, XMLEventReader reader
, XMLEvent parentEvent
)
71 throws XMLStreamException
{
72 checkNoAttributes(parentEvent
);
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
)) {
81 fireSchemaConflictEventExpectedStartTag(HOMOTYPES
, reader
);
82 state
.setUnsuccessfull();
88 private void handleHomotypes(MarkupImportState state
,
89 XMLEventReader reader
, StartElement parentEvent
)
90 throws XMLStreamException
{
91 checkNoAttributes(parentEvent
);
93 HomotypicalGroup homotypicalGroup
= null;
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);
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();
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
);
116 handleUnexpectedElement(next
);
119 state
.setLatestAuthorInHomotype(null);
120 // TODO handle missing end element
121 throw new IllegalStateException("Homotypes has no closing tag");
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
);
132 NameTypeDesignationStatus status
;
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);
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(),
149 state
.setNameType(false);
152 if (isEndingElement(next
, ACCEPTED_NAME
)) {
153 // NOT YET IMPLEMENTED
154 popUnimplemented(next
.asEndElement());
156 handleUnexpectedEndElement(next
.asEndElement());
159 } else if (next
.isStartElement()) {
160 if (isStartingElement(next
, NOM
)) {
161 // TODO should we check if the type is always a species, is
163 NonViralName
<?
> speciesName
= handleNom(state
, reader
,
165 for (TaxonNameBase
<?
, ?
> name
: homotypicalGroup
166 .getTypifiedNames()) {
167 name
.addNameTypeDesignation(speciesName
, null, null,
168 null, status
, false, false, false, false);
171 } else if (isStartingElement(next
, ACCEPTED_NAME
)) {
172 handleNotYetImplementedElement(next
);
174 handleUnexpectedStartElement(next
);
177 handleUnexpectedElement(next
);
180 // TODO handle missing end element
181 throw new IllegalStateException("Homotypes has no closing tag");
186 * Creates the name defined by a nom tag. Adds it to the given homotypical
187 * group (if not null).
192 * @param homotypicalGroup
194 * @throws XMLStreamException
196 private NonViralName
<?
> handleNom(MarkupImportState state
, XMLEventReader reader
,
197 XMLEvent parentEvent
, HomotypicalGroup homotypicalGroup
) throws XMLStreamException
{
198 boolean isSynonym
= false;
199 boolean isNameType
= state
.isNameType();
201 String classValue
= getClassOnlyAttribute(parentEvent
);
202 NonViralName
<?
> name
;
203 if (!isNameType
&& ACCEPTED
.equalsIgnoreCase(classValue
)) {
205 name
= createName(state
, homotypicalGroup
, isSynonym
);
206 } else if (!isNameType
&& SYNONYM
.equalsIgnoreCase(classValue
)) {
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);
213 fireUnexpectedAttributeValue(parentEvent
, CLASS
, classValue
);
214 name
= createNameByCode(state
, null);
217 Map
<String
, String
> nameMap
= new HashMap
<String
, String
>();
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
);
228 handleNomText(state
, parentEvent
, text
, isNameType
);
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
);
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
);
254 handleUnexpectedElement(next
);
257 // TODO handle missing end element
258 throw new IllegalStateException("Nom has no closing tag");
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.
272 private void handleNomText(MarkupImportState state
, XMLEvent event
, String text
, boolean isNameType
) {
277 //neglect known redundant strings
278 if (isNameType
&& text
.matches("(?i)^Esp[\u00E8\u00C8]ce[·\\-\\s]type\\:$")){
280 }//neglect meaningless punctuation
281 else if (isPunctuation(text
)){
284 String message
= "Unhandled text in <nom> tag: \"%s\"";
285 fireWarningEvent(String
.format(message
, text
), event
, 4);
293 * @throws XMLStreamException
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);
308 state
.setCurrentTaxonNum(num
);
317 * @throws XMLStreamException
319 private void handleFullName(MarkupImportState state
, XMLEventReader reader
,
320 NonViralName
<?
> name
, XMLEvent next
) throws XMLStreamException
{
322 Map
<String
, Attribute
> attrs
= getAttributes(next
);
323 String rankStr
= getAndRemoveRequiredAttributeValue(next
,
325 Rank rank
= makeRank(state
, rankStr
, false);
328 String message
= "Rank was computed as null. This must not be.";
329 fireWarningEvent(message
, next
, 6);
330 name
.setRank(Rank
.UNKNOWN_RANK());
332 if (!attrs
.isEmpty()) {
333 handleUnexpectedAttributes(next
.getLocation(), attrs
);
335 fullNameStr
= getCData(state
, reader
, next
);
336 name
.setTitleCache(fullNameStr
, true);
339 private void handleName(MarkupImportState state
, XMLEventReader reader
,
340 XMLEvent parentEvent
, Map
<String
, String
> nameMap
)
341 throws XMLStreamException
{
342 String classValue
= getClassOnlyAttribute(parentEvent
);
345 while (reader
.hasNext()) {
346 XMLEvent next
= readNoWhitespace(reader
);
347 if (isMyEndingElement(next
, parentEvent
)) {
348 nameMap
.put(classValue
, text
);
350 } else if (isStartingElement(next
, ANNOTATION
)) {
351 handleNotYetImplementedElement(next
); // TODO test handleSimpleAnnotation
352 } else if (next
.isCharacters()) {
353 text
+= next
.asCharacters().getData();
355 handleUnexpectedElement(next
);
358 throw new IllegalStateException("name has no closing tag");
361 private void fillName(MarkupImportState state
, Map
<String
, String
> nameMap
,
362 NonViralName
<?
> name
, XMLEvent event
) {
364 // Ranks: family, subfamily, tribus, genus, subgenus, section,
365 // subsection, species, subspecies, variety, subvariety, forma
366 // infrank, paraut, author, infrparaut, infraut, status, notes
368 String infrank
= getAndRemoveMapKey(nameMap
, INFRANK
);
369 String authorStr
= getAndRemoveMapKey(nameMap
, AUTHOR
);
370 String paraut
= getAndRemoveMapKey(nameMap
, PARAUT
);
372 String infrParAut
= getAndRemoveMapKey(nameMap
, INFRPARAUT
);
373 String infrAut
= getAndRemoveMapKey(nameMap
, INFRAUT
);
375 String statusStr
= getAndRemoveMapKey(nameMap
, STATUS
);
376 String notes
= getAndRemoveMapKey(nameMap
, NOTES
);
378 if (!name
.isProtectedTitleCache()) { // otherwise fullName
380 makeRankDecision(state
, nameMap
, name
, event
, infrank
);
382 // test consistency of rank and authors
383 testRankAuthorConsistency(name
, event
, authorStr
, paraut
,infrParAut
, infrAut
);
386 makeNomenclaturalAuthors(state
, event
, name
, authorStr
, paraut
, infrParAut
, infrAut
);
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);
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);
409 if (StringUtils
.isNotBlank(notes
)) {
410 handleNotYetImplementedAttributeValue(event
, CLASS
, NOTES
);
423 private void makeRankDecision(MarkupImportState state
,
424 Map
<String
, String
> nameMap
, NonViralName
<?
> name
, XMLEvent event
,
427 for (String key
: nameMap
.keySet()) {
428 Rank rank
= makeRank(state
, key
, false);
430 handleNotYetImplementedAttributeValue(event
, CLASS
, key
);
432 if (name
.getRank() == null || rank
.isLower(name
.getRank())) {
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();
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
454 name
.setSpecificEpithet(value
);
456 name
.setSpecificEpithet(value
.toLowerCase());
458 } else if (rank
.isInfraSpecific()) {
459 name
.setInfraSpecificEpithet(value
.toLowerCase());
461 String message
= "Invalid rank '%s'. Can't decide which epithet to fill with '%s'";
462 message
= String
.format(message
, rank
.getTitleCache(),
464 fireWarningEvent(message
, event
, 4);
469 // handle given infrank marker
470 if (StringUtils
.isNotBlank(infrankStr
)) {
471 Rank infRank
= makeRank(state
, infrankStr
, true);
473 if (infRank
== null) {
474 String message
= "Infrank '%s' rank not recognized";
475 message
= String
.format(message
, infrankStr
);
476 fireWarningEvent(message
, event
, 4);
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())) {
488 String message
= "InfRank '%s' is higher than existing rank ";
489 message
= String
.format(message
, infrankStr
);
490 fireWarningEvent(message
, event
, 2);
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]);
513 if (StringUtils
.isNotBlank(infrParAut
)) {
514 INomenclaturalAuthor
[] authorAndEx
= authorAndEx(infrParAut
,event
);
515 name
.setBasionymAuthorTeam(authorAndEx
[0]);
516 name
.setExBasionymAuthorTeam(authorAndEx
[1]);
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
)) {
527 if (StringUtils
.isNotBlank(authorStr
)) {
528 INomenclaturalAuthor
[] authorAndEx
= authorAndEx(authorStr
, event
);
529 name
.setCombinationAuthorTeam(authorAndEx
[0]);
530 name
.setExCombinationAuthorTeam(authorAndEx
[1]);
532 if (StringUtils
.isNotBlank(paraut
)) {
533 INomenclaturalAuthor
[] authorAndEx
= authorAndEx(paraut
, event
);
534 name
.setBasionymAuthorTeam(authorAndEx
[0]);
535 name
.setExBasionymAuthorTeam(authorAndEx
[1]);
539 //remember author for following citations
540 state
.setLatestAuthorInHomotype((TeamOrPersonBase
<?
>)name
.getCombinationAuthorTeam());
543 private TeamOrPersonBase
<?
>[] authorAndEx(String authorAndEx
, XMLEvent xmlEvent
) {
544 authorAndEx
= authorAndEx
.trim();
545 TeamOrPersonBase
<?
>[] result
= new TeamOrPersonBase
[2];
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]);
556 result
[0] = createAuthor(split
[0]);
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.
566 * @param homotypicalGroup
570 private NonViralName
<?
> createName(MarkupImportState state
,
571 HomotypicalGroup homotypicalGroup
, boolean isSynonym
) {
572 NonViralName
<?
> name
;
573 Taxon taxon
= state
.getCurrentTaxon();
575 Rank defaultRank
= Rank
.SPECIES(); // can be any
576 name
= createNameByCode(state
, defaultRank
);
577 if (homotypicalGroup
!= null) {
578 name
.setHomotypicalGroup(homotypicalGroup
);
580 SynonymRelationshipType synonymType
= SynonymRelationshipType
.HETEROTYPIC_SYNONYM_OF();
581 if (taxon
.getHomotypicGroup().equals(homotypicalGroup
)) {
582 synonymType
= SynonymRelationshipType
.HOMOTYPIC_SYNONYM_OF();
584 taxon
.addSynonymName(name
, synonymType
);
586 name
= CdmBase
.deproxy(taxon
.getName(), NonViralName
.class);
591 private void handleCitation(MarkupImportState state
, XMLEventReader reader
,
592 XMLEvent parentEvent
, NonViralName
<?
> name
, Map
<String
, String
> nameMap
) throws XMLStreamException
{
593 String classValue
= getClassOnlyAttribute(parentEvent
);
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);
607 } else if (isStartingElement(next
, REF_PART
)) {
608 handleRefPart(state
, reader
, next
, refMap
);
611 handleUnexpectedElement(next
);
614 throw new IllegalStateException("Citation has no closing tag");
618 private void handleRefPart(MarkupImportState state
, XMLEventReader reader
,
619 XMLEvent parentEvent
, Map
<String
, String
> refMap
)
620 throws XMLStreamException
{
621 String classValue
= getClassOnlyAttribute(parentEvent
);
624 while (reader
.hasNext()) {
625 XMLEvent next
= readNoWhitespace(reader
);
626 if (isMyEndingElement(next
, parentEvent
)) {
627 refMap
.put(classValue
, text
);
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
);
638 handleUnexpectedStartElement(next
.asStartElement());
640 } else if (next
.isCharacters()) {
641 text
+= next
.asCharacters().getData();
643 handleUnexpectedEndElement(next
.asEndElement());
646 throw new IllegalStateException("RefPart has no closing tag");
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
);
666 // TODO Not yet implemented
667 handleNotYetImplementedAttributeValue(parentEvent
, CLASS
, classValue
);
672 * Tests if the names rank is consistent with the given author strings.
673 * NOTE: Tags for authors are differ depending on the rank.
682 private void testRankAuthorConsistency(NonViralName
<?
> name
, XMLEvent event
,
683 String authorStr
, String paraut
, String infrParAut
, String infrAut
) {
684 if (name
.getRank() == null) {
687 if (name
.getRank().isInfraSpecific()) {
688 if (StringUtils
.isBlank(infrParAut
)
689 && StringUtils
.isBlank(infrAut
) // was isNotBlank before
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);
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);
706 private Reference
<?
> createReference(MarkupImportState state
,
707 Map
<String
, String
> refMap
, XMLEvent parentEvent
) {
709 Reference
<?
> reference
;
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
);
722 if (state
.isCitation()) {
723 reference
= handleCitationSpecific(state
, type
, authorStr
,
724 titleStr
, titleCache
, volume
, edition
, editors
, pubName
, pages
, refMap
, parentEvent
);
726 } else { // no citation
727 reference
= handleNonCitationSpecific(type
, authorStr
, titleStr
,
728 titleCache
, volume
, edition
, editors
, pubName
);
732 TimePeriod timeperiod
= TimePeriodParser
.parseString(year
);
733 if (reference
.getType().equals(ReferenceType
.BookSection
)){
734 reference
.getInBook().setDatePublished(timeperiod
);
736 reference
.setDatePublished(timeperiod
);
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
);
747 for (String key
: refMap
.keySet()) {
748 if (!DETAILS
.equalsIgnoreCase(key
)) {
749 this.fireUnexpectedAttributeValue(parentEvent
, CLASS
, key
);
758 * Handles references used in the citation tag
759 * @see #handleNonCitationSpecific(String, String, String, String, String, String, String, String)
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
) {
765 if (titleStr
!= null){
766 String message
= "Currently it is not expected that a titleStr exists in a citation";
767 fireWarningEvent(message
, parentEvent
, 4);
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);
784 reference
= (Reference
<?
>) article
;
785 } else if (refType
== RefType
.BookSection
) {
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
);
794 if (state
.getConfig().isUseEditorAsInAuthorWhereNeeded()){
795 TeamOrPersonBase
<?
> inAuthor
= createAuthor(editors
);
796 book
.setAuthorTeam(inAuthor
);
799 } else if (refType
== RefType
.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
);
813 //volume indicates an in-reference
814 if (isNotBlank(volume
)){
815 Reference
<?
> partOf
= ReferenceFactory
.newGeneric();
816 partOf
.setVolume(volume
);
817 partOf
.setInReference(reference
);
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();
827 if (latestReference
.getInReference() != null){
828 reference
= (Reference
<?
>)latestReference
.clone();
830 String message
= "Latest reference is not an in-reference. This is not yet handled.";
831 fireWarningEvent(message
, parentEvent
, 6);
832 reference
= ReferenceFactory
.newGeneric();
835 reference
.setVolume(volume
);
836 if (isNotBlank(edition
)){
837 String message
= "Edition not yet handled for incomplete citations";
838 fireWarningEvent(message
, parentEvent
, 4);
842 String message
= "Unhandled reference type: %s" ;
843 fireWarningEvent(String
.format(message
, refType
.toString()), parentEvent
, 8);
844 reference
= ReferenceFactory
.newGeneric();
848 TeamOrPersonBase
<?
> author
;
849 if (isBlank(authorStr
)){
850 if (refType
!= RefType
.LatestUsed
){
851 author
= state
.getLatestAuthorInHomotype();
852 reference
.setAuthorTeam(author
);
855 author
= createAuthor(authorStr
);
856 state
.setLatestAuthorInHomotype(author
);
857 reference
.setAuthorTeam(author
);
862 handleTitlesInCitation(titleStr
, titleCache
, parentEvent
, reference
);
865 handleEditorsInCitation(edition
, editors
, reference
, parentEvent
);
868 handlePages(state
, refMap
, parentEvent
, reference
, pages
);
870 //remember reference for following citation
871 state
.setLatestReferenceInHomotype(reference
);
876 private void handleEditorsInCitation(String edition
, String editors
, Reference
<?
> reference
, XMLEvent parentEvent
) {
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);
885 private void handleTitlesInCitation(String titleStr
, String titleCache
,
886 XMLEvent parentEvent
, Reference
<?
> reference
) {
887 if (isNotBlank(titleStr
)){
888 reference
.setTitle(titleStr
);
891 if (StringUtils
.isNotBlank(titleCache
)) {
892 reference
.setTitleCache(titleCache
, true);
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);
900 private enum RefType
{
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
;
913 if (editors
== null){
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
921 return RefType
.Generic
;
926 if (pubName
!= null){
927 return RefType
.BookSection
;
929 String message
= "Unexpected state: Citation has editors but no pubName";
930 fireWarningEvent(message
, parentEvent
, 4);
931 return RefType
.Generic
;
938 private boolean isArticle(String type
, String volume
, String editors
) {
939 if ("journal".equalsIgnoreCase(type
)){
941 }else if (volume
!= null && editors
== null){
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
);
963 reference
= (Reference
<?
>) article
;
966 Reference
<?
> bookOrPartOf
= ReferenceFactory
.newGeneric();
967 reference
= bookOrPartOf
;
971 TeamOrPersonBase
<?
> author
= createAuthor(authorStr
);
972 reference
.setAuthorTeam(author
);
975 reference
.setTitle(titleStr
);
976 if (StringUtils
.isNotBlank(titleCache
)) {
977 reference
.setTitleCache(titleCache
, true);
981 reference
.setEdition(edition
);
982 reference
.setEditor(editors
);
985 if (pubName
!= null) {
986 Reference
<?
> inReference
;
987 if (reference
.getType().equals(ReferenceType
.Article
)) {
988 inReference
= ReferenceFactory
.newJournal();
990 inReference
= ReferenceFactory
.newGeneric();
992 inReference
.setTitle(pubName
);
993 reference
.setInReference(inReference
);
997 reference
.setVolume(volume
);
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();
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
);
1015 //handle pages as detail, this is at least true for Flora Malesiana
1016 refMap
.put(DETAILS
, pages
);
1019 if (! pages
.contains("-")){
1020 String message
= "There are pages and detail available where pages may also hold details information.";
1021 fireWarningEvent(message
, parentEvent
, 8);
1023 reference
.setPages(pages
);
1029 public Reference
<?
> handleReference(MarkupImportState state
,
1030 XMLEventReader reader
, XMLEvent parentEvent
)
1031 throws XMLStreamException
{
1032 checkNoAttributes(parentEvent
);
1034 boolean hasRefPart
= false;
1035 Map
<String
, String
> refMap
= new HashMap
<String
, String
>();
1036 while (reader
.hasNext()) {
1037 XMLEvent next
= readNoWhitespace(reader
);
1038 if (isMyEndingElement(next
, parentEvent
)) {
1039 checkMandatoryElement(hasRefPart
, parentEvent
.asStartElement(), REF_PART
);
1040 Reference
<?
> reference
= createReference(state
, refMap
, next
);
1042 } else if (isStartingElement(next
, REF_PART
)) {
1043 handleRefPart(state
, reader
, next
, refMap
);
1046 handleUnexpectedElement(next
);
1049 // TODO handle missing end element
1050 throw new IllegalStateException("<Reference> has no closing tag");