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.
9 package eu
.etaxonomy
.cdm
.io
.markup
;
11 import java
.util
.HashMap
;
14 import javax
.xml
.stream
.XMLEventReader
;
15 import javax
.xml
.stream
.XMLStreamException
;
16 import javax
.xml
.stream
.events
.Attribute
;
17 import javax
.xml
.stream
.events
.StartElement
;
18 import javax
.xml
.stream
.events
.XMLEvent
;
20 import org
.apache
.commons
.lang
.StringUtils
;
21 import org
.apache
.logging
.log4j
.LogManager
;
22 import org
.apache
.logging
.log4j
.Logger
;
24 import eu
.etaxonomy
.cdm
.model
.agent
.TeamOrPersonBase
;
25 import eu
.etaxonomy
.cdm
.model
.common
.VerbatimTimePeriod
;
26 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
27 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
28 import eu
.etaxonomy
.cdm
.model
.description
.TextData
;
29 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
30 import eu
.etaxonomy
.cdm
.model
.name
.INonViralName
;
31 import eu
.etaxonomy
.cdm
.model
.name
.NameTypeDesignationStatus
;
32 import eu
.etaxonomy
.cdm
.model
.name
.NomenclaturalStatus
;
33 import eu
.etaxonomy
.cdm
.model
.name
.NomenclaturalStatusType
;
34 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
35 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
36 import eu
.etaxonomy
.cdm
.model
.reference
.IArticle
;
37 import eu
.etaxonomy
.cdm
.model
.reference
.IBook
;
38 import eu
.etaxonomy
.cdm
.model
.reference
.IJournal
;
39 import eu
.etaxonomy
.cdm
.model
.reference
.OriginalSourceType
;
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
.SynonymType
;
44 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
45 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
46 import eu
.etaxonomy
.cdm
.strategy
.exceptions
.UnknownCdmTypeException
;
47 import eu
.etaxonomy
.cdm
.strategy
.parser
.NameTypeParser
;
48 import eu
.etaxonomy
.cdm
.strategy
.parser
.TimePeriodParser
;
54 public class MarkupNomenclatureImport
extends MarkupImportBase
{
56 @SuppressWarnings("unused")
57 private static final Logger logger
= LogManager
.getLogger(MarkupNomenclatureImport
.class);
59 private final MarkupSpecimenImport specimenImport
;
61 public MarkupNomenclatureImport(MarkupDocumentImport docImport
, MarkupSpecimenImport specimenImport
) {
63 this.specimenImport
= specimenImport
;
66 public void handleNomenclature(MarkupImportState state
, XMLEventReader reader
, XMLEvent parentEvent
)
67 throws XMLStreamException
{
68 checkNoAttributes(parentEvent
);
70 while (reader
.hasNext()) {
71 XMLEvent next
= readNoWhitespace(reader
);
72 if (isMyEndingElement(next
, parentEvent
)) {
74 } else if (isStartingElement(next
, HOMOTYPES
)) {
75 handleHomotypes(state
, reader
, next
.asStartElement());
76 } else if (isStartingElement(next
, NOMENCLATURAL_NOTES
)) {
77 handleAmbigousManually(state
, reader
, next
.asStartElement());
79 fireSchemaConflictEventExpectedStartTag(HOMOTYPES
, reader
);
80 state
.setUnsuccessfull();
87 private void handleHomotypes(MarkupImportState state
,
88 XMLEventReader reader
, StartElement parentEvent
)
89 throws XMLStreamException
{
90 checkNoAttributes(parentEvent
);
92 HomotypicalGroup homotypicalGroup
= null;
94 boolean hasNom
= false;
95 while (reader
.hasNext()) {
96 XMLEvent next
= readNoWhitespace(reader
);
97 if (isMyEndingElement(next
, parentEvent
)) {
98 checkMandatoryElement(hasNom
, parentEvent
, NOM
);
99 state
.setLatestAuthorInHomotype(null);
101 } else if (isEndingElement(next
, NAME_TYPE
)) {
102 state
.setNameType(false);
103 } else if (isStartingElement(next
, NOM
)) {
104 INonViralName name
= handleNom(state
, reader
, next
, homotypicalGroup
);
105 homotypicalGroup
= name
.getHomotypicalGroup();
107 } else if (isStartingElement(next
, NAME_TYPE
)) {
108 state
.setNameType(true);
109 handleNameType(state
, reader
, next
, homotypicalGroup
);
110 } else if (isStartingElement(next
, SPECIMEN_TYPE
)) {
111 specimenImport
.handleSpecimenType(state
, reader
, next
, homotypicalGroup
);
112 } else if (isStartingElement(next
, NOTES
)) {
113 handleNotYetImplementedElement(next
);
115 handleUnexpectedElement(next
);
118 state
.setLatestAuthorInHomotype(null);
119 // TODO handle missing end element
120 throw new IllegalStateException("Homotypes has no closing tag");
123 private void handleNameType(MarkupImportState state
, XMLEventReader reader
,
124 XMLEvent parentEvent
, HomotypicalGroup homotypicalGroup
)
125 throws XMLStreamException
{
126 Map
<String
, Attribute
> attributes
= getAttributes(parentEvent
);
127 String typeStatus
= getAndRemoveAttributeValue(attributes
, TYPE_STATUS
);
128 checkNoAttributes(attributes
, parentEvent
);
130 NameTypeDesignationStatus status
;
132 status
= NameTypeParser
.parseNameTypeStatus(typeStatus
);
133 } catch (UnknownCdmTypeException e
) {
134 String message
= "Type status could not be recognized: %s";
135 message
= String
.format(message
, typeStatus
);
136 fireWarningEvent(message
, parentEvent
, 4);
140 boolean hasNom
= false;
141 while (reader
.hasNext()) {
142 XMLEvent next
= readNoWhitespace(reader
);
143 if (next
.isEndElement()) {
144 if (isMyEndingElement(next
, parentEvent
)) {
145 checkMandatoryElement(hasNom
, parentEvent
.asStartElement(),
147 state
.setNameType(false);
150 if (isEndingElement(next
, ACCEPTED_NAME
)) {
151 // NOT YET IMPLEMENTED
152 popUnimplemented(next
.asEndElement());
154 handleUnexpectedEndElement(next
.asEndElement());
157 } else if (next
.isStartElement()) {
158 if (isStartingElement(next
, NOM
)) {
159 // TODO should we check if the type is always a species, is
161 TaxonName speciesName
= TaxonName
.castAndDeproxy(
162 handleNom(state
, reader
, next
, null));
163 for (TaxonName name
: homotypicalGroup
164 .getTypifiedNames()) {
165 name
.addNameTypeDesignation(speciesName
, null, null,
166 null, status
, false, false, false, false);
169 } else if (isStartingElement(next
, ACCEPTED_NAME
)) {
170 handleNotYetImplementedElement(next
);
172 handleUnexpectedStartElement(next
);
175 handleUnexpectedElement(next
);
178 // TODO handle missing end element
179 throw new IllegalStateException("Homotypes has no closing tag");
183 * Creates the name defined by a nom tag. Adds it to the given homotypical
184 * group (if not null).
189 * @param homotypicalGroup
191 * @throws XMLStreamException
193 private INonViralName
handleNom(MarkupImportState state
, XMLEventReader reader
,
194 XMLEvent parentEvent
, HomotypicalGroup homotypicalGroup
) throws XMLStreamException
{
195 boolean isSynonym
= false;
196 boolean isNameType
= state
.isNameType();
198 Map
<String
, Attribute
> attributes
= getAttributes(parentEvent
);
199 boolean isMisidentification
= getAndRemoveBooleanAttributeValue(parentEvent
, attributes
, "misidentification", false);
200 String classValue
= getAndRemoveRequiredAttributeValue(parentEvent
, attributes
, "class");
201 checkNoAttributes(attributes
, parentEvent
);
204 TaxonRelationship misappliedRelation
= null;
205 if (isMisidentification
) {
206 if (isNameType
|| ACCEPTED
.equalsIgnoreCase(classValue
) ){
207 fireWarningEvent("Misidentification only defined for synonyms", parentEvent
, 4);
209 name
= createNameByCode(state
, null);
210 Taxon acc
= state
.getCurrentTaxon();
211 Taxon misapplied
= Taxon
.NewInstance(name
, null);
212 this.save(acc
, state
); //we got a transient object exception later in the taxon otherwise
213 this.save(misapplied
, state
);
214 misappliedRelation
= acc
.addMisappliedName(misapplied
, null, null);
215 }else if (!isNameType
&& ACCEPTED
.equalsIgnoreCase(classValue
)) {
217 name
= createName(state
, homotypicalGroup
, isSynonym
);
218 } else if (!isNameType
&& SYNONYM
.equalsIgnoreCase(classValue
)) {
220 name
= createName(state
, homotypicalGroup
, isSynonym
);
221 } else if (isNameType
&& NAME_TYPE
.equalsIgnoreCase(classValue
)) {
222 // TODO do we need to define the rank here?
223 name
= createNameByCode(state
, null);
225 fireUnexpectedAttributeValue(parentEvent
, CLASS
, classValue
);
226 name
= createNameByCode(state
, null);
229 Map
<String
, String
> nameMap
= new HashMap
<>();
232 boolean nameFilled
= false;
233 state
.setNameStatus(null);
234 while (reader
.hasNext()) {
235 XMLEvent next
= readNoWhitespace(reader
);
236 if (isMyEndingElement(next
, parentEvent
)) {
237 // fill the name with all data gathered, if not yet done before
238 if (nameFilled
== false){
239 fillName(state
, nameMap
, name
, misappliedRelation
, next
);
241 handleNomText(state
, parentEvent
, text
, isNameType
);
242 state
.getDeduplicationHelper().replaceAuthorNamesAndNomRef(name
);
243 handleNameStatus(state
, name
, next
);
244 state
.setNameStatus(null);
246 } else if (isEndingElement(next
, ANNOTATION
)) {
247 // NOT YET IMPLEMENTED //TODO test
248 // handleSimpleAnnotation
249 popUnimplemented(next
.asEndElement());
250 }else if (isStartingElement(next
, FULL_NAME
)) {
251 handleFullName(state
, reader
, name
, next
);
252 } else if (isStartingElement(next
, NUM
)) {
253 handleNomNum(state
, reader
, next
);
254 } else if (isStartingElement(next
, NAME
)) {
255 handleName(state
, reader
, next
, nameMap
);
256 } else if (isStartingElement(next
, CITATION
)) {
257 //we need to fill the name here to have nomenclatural author available for the following citations
258 fillName(state
, nameMap
, name
, misappliedRelation
, next
);
260 handleCitation(state
, reader
, next
, name
, misappliedRelation
);
261 } else if (next
.isCharacters()) {
262 text
+= next
.asCharacters().getData();
263 } else if (isStartingElement(next
, HOMONYM
)) {
264 handleNotYetImplementedElement(next
);
265 } else if (isStartingElement(next
, NOTES
)) {
266 handleNotYetImplementedElement(next
);
267 } else if (isStartingElement(next
, NOMENCLATURAL_NOTES
)) {
268 handleNotYetImplementedElement(next
);
269 } else if (isStartingElement(next
, ANNOTATION
)) {
270 handleNotYetImplementedElement(next
);
272 handleUnexpectedElement(next
);
275 throw new IllegalStateException("Nom has no closing tag");
283 private void handleNameStatus(MarkupImportState state
, INonViralName name
, XMLEvent next
) {
284 if (isNotBlank(state
.getNameStatus())){
285 String nameStatus
= state
.getNameStatus().trim();
287 NomenclaturalStatusType nomStatusType
= NomenclaturalStatusType
288 .getNomenclaturalStatusTypeByAbbreviation(nameStatus
, name
);
289 name
.addStatus(NomenclaturalStatus
.NewInstance(nomStatusType
));
290 } catch (UnknownCdmTypeException e
) {
291 String message
= "Status '%s' could not be recognized";
292 message
= String
.format(message
, nameStatus
);
293 fireWarningEvent(message
, next
, 4);
299 * Handles appearance of text within <nom> tags.
300 * Usually this is not expected except for some information that is already handled
301 * elsewhere, e.g. the string Nametype is holding information that is available already
302 * via the surrounding nametype tag. Therefore this information can be neglected.
303 * This method is open for upcoming cases which need to be handled.
309 private void handleNomText(MarkupImportState state
, XMLEvent event
, String text
, boolean isNameType
) {
314 //neglect known redundant strings
315 if (isNameType
&& (text
.matches("(?i)^Esp[\u00E8\u00C8]ce[·\\-\\s]type\\:$")
316 || charIsSimpleType(text
) )){
318 }//neglect meaningless punctuation
319 else if (isPunctuation(text
)){
322 else if (isPunctuation(text
)){
325 String message
= "Unhandled text in <nom> tag: \"%s\"";
326 fireWarningEvent(String
.format(message
, text
), event
, 4);
334 * @throws XMLStreamException
336 private void handleNomNum(MarkupImportState state
, XMLEventReader reader
,
337 XMLEvent next
) throws XMLStreamException
{
338 String num
= getCData(state
, reader
, next
);
339 num
= num
.replace(".", "");
340 num
= num
.replace(")", "");
341 if (StringUtils
.isNotBlank(num
)) {
342 if (state
.getCurrentTaxonNum() != null
343 && !state
.getCurrentTaxonNum().equals(num
)) {
344 String message
= "Taxontitle num and homotypes/nom/num differ ( %s <-> %s ). I use the later one.";
345 message
= String
.format(message
,
346 state
.getCurrentTaxonNum(), num
);
347 fireWarningEvent(message
, next
, 4);
349 state
.setCurrentTaxonNum(num
);
355 private void handleName(MarkupImportState state
, XMLEventReader reader
,
356 XMLEvent parentEvent
, Map
<String
, String
> nameMap
)
357 throws XMLStreamException
{
358 String classValue
= getClassOnlyAttribute(parentEvent
);
361 while (reader
.hasNext()) {
362 XMLEvent next
= readNoWhitespace(reader
);
363 if (isMyEndingElement(next
, parentEvent
)) {
364 nameMap
.put(classValue
, text
);
366 } else if (isStartingElement(next
, ANNOTATION
)) {
367 handleNotYetImplementedElement(next
); // TODO test handleSimpleAnnotation
368 } else if (isStartingElement(next
, FOOTNOTE_REF
)) {
369 handleNotYetImplementedElement(next
);
370 } else if (next
.isCharacters()) {
371 text
+= next
.asCharacters().getData();
373 handleUnexpectedElement(next
);
376 throw new IllegalStateException("name has no closing tag");
379 private void fillName(MarkupImportState state
, Map
<String
, String
> nameMap
,
380 INonViralName name
, TaxonRelationship misappliedRel
, XMLEvent event
) {
382 // Ranks: family, subfamily, tribus, genus, subgenus, section,
383 // subsection, species, subspecies, variety, subvariety, forma
384 // infrank, paraut, author, infrparaut, infraut, status, notes
386 String infrank
= getAndRemoveMapKey(nameMap
, INFRANK
);
387 String authorStr
= getAndRemoveMapKey(nameMap
, AUTHOR
);
388 String paraut
= getAndRemoveMapKey(nameMap
, PARAUT
);
390 String infrParAut
= getAndRemoveMapKey(nameMap
, INFRPARAUT
);
391 String infrAut
= getAndRemoveMapKey(nameMap
, INFRAUT
);
393 String statusStr
= getAndRemoveMapKey(nameMap
, STATUS
);
394 String notes
= getAndRemoveMapKey(nameMap
, NOTES
);
396 if (misappliedRel
!= null && authorStr
!= null && authorStr
.startsWith("auct.")){
397 misappliedRel
.getFromTaxon().setAppendedPhrase(authorStr
);
401 if (!name
.isProtectedTitleCache()) { // otherwise fullName
403 makeRankDecision(state
, nameMap
, name
, event
, infrank
);
405 // test consistency of rank and authors
406 testRankAuthorConsistency(name
, event
, authorStr
, paraut
,infrParAut
, infrAut
);
409 makeNomenclaturalAuthors(state
, event
, name
, authorStr
, paraut
, infrParAut
, infrAut
);
413 // TODO handle pro parte, pro syn. etc.
414 if (isNotBlank(statusStr
)) {
415 String proPartePattern
= "(pro parte|p.p.)";
416 if (statusStr
.matches(proPartePattern
)) {
417 state
.setProParte(true);
420 // TODO handle trim earlier
421 statusStr
= statusStr
.trim();
422 NomenclaturalStatusType nomStatusType
= NomenclaturalStatusType
423 .getNomenclaturalStatusTypeByAbbreviation(statusStr
, name
);
424 name
.addStatus(NomenclaturalStatus
.NewInstance(nomStatusType
));
425 } catch (UnknownCdmTypeException e
) {
426 String message
= "Status '%s' could not be recognized";
427 message
= String
.format(message
, statusStr
);
428 fireWarningEvent(message
, event
, 4);
433 if (StringUtils
.isNotBlank(notes
)) {
434 handleNotYetImplementedAttributeValue(event
, CLASS
, NOTES
);
444 private String
normalizeStatus(String statusStr
) {
445 if (statusStr
== null){
447 }else if (statusStr
.equals("nomen")){
448 statusStr
= "nom. nud.";
450 return statusStr
.trim();
460 private void makeRankDecision(MarkupImportState state
,
461 Map
<String
, String
> nameMap
, INonViralName name
, XMLEvent event
,
464 for (String key
: nameMap
.keySet()) {
465 Rank rank
= makeRank(state
, key
, false);
467 handleNotYetImplementedAttributeValue(event
, CLASS
, key
);
469 if (name
.getRank() == null || rank
.isLower(name
.getRank())) {
472 String value
= nameMap
.get(key
);
473 if (rank
.isSupraGeneric() || rank
.isGenus()) {
474 if ((key
.equalsIgnoreCase(GENUS_ABBREVIATION
)
475 && isNotBlank(state
.getLatestGenusEpithet()) || isGenusAbbrev(
476 value
, state
.getLatestGenusEpithet()))) {
477 value
= state
.getLatestGenusEpithet();
479 name
.setGenusOrUninomial(toFirstCapital(value
));
480 } else if (rank
.isInfraGeneric()) {
481 name
.setInfraGenericEpithet(toFirstCapital(value
));
482 } else if (rank
.isSpecies()) {
483 if (state
.getConfig().isAllowCapitalSpeciesEpithet()
484 && isFirstCapitalWord(value
)) { // capital letters
491 name
.setSpecificEpithet(value
);
493 name
.setSpecificEpithet(value
.toLowerCase());
495 } else if (rank
.isInfraSpecific()) {
496 name
.setInfraSpecificEpithet(value
.toLowerCase());
498 String message
= "Invalid rank '%s'. Can't decide which epithet to fill with '%s'";
499 message
= String
.format(message
, rank
.getTitleCache(),
501 fireWarningEvent(message
, event
, 4);
506 // handle given infrank marker
507 if (StringUtils
.isNotBlank(infrankStr
)) {
508 Rank infRank
= makeRank(state
, infrankStr
, true);
510 if (infRank
== null) {
511 String message
= "Infrank '%s' rank not recognized";
512 message
= String
.format(message
, infrankStr
);
513 fireWarningEvent(message
, event
, 4);
515 if (name
.getRank() == null) {
516 name
.setRank(infRank
);
517 } else if (infRank
.isLower(name
.getRank())) {
518 String message
= "InfRank '%s' is lower than existing rank ";
519 message
= String
.format(message
, infrankStr
);
520 fireWarningEvent(message
, event
, 2);
521 name
.setRank(infRank
);
522 } else if (infRank
.equals(name
.getRank())) {
525 String message
= "InfRank '%s' is higher than existing rank ";
526 message
= String
.format(message
, infrankStr
);
527 fireWarningEvent(message
, event
, 2);
542 private void makeNomenclaturalAuthors(MarkupImportState state
, XMLEvent event
, INonViralName name
,
543 String authorStr
, String paraut
, String infrParAut
, String infrAut
) {
544 if (name
.getRank() != null && name
.getRank().isInfraSpecific()) {
545 if (StringUtils
.isNotBlank(infrAut
)) {
546 TeamOrPersonBase
<?
>[] authorAndEx
= authorAndEx(state
, infrAut
, event
);
547 name
.setCombinationAuthorship(authorAndEx
[0]);
548 name
.setExCombinationAuthorship(authorAndEx
[1]);
550 if (StringUtils
.isNotBlank(infrParAut
)) {
551 TeamOrPersonBase
<?
>[] authorAndEx
= authorAndEx(state
, infrParAut
,event
);
552 name
.setBasionymAuthorship(authorAndEx
[0]);
553 name
.setExBasionymAuthorship(authorAndEx
[1]);
556 if (name
.getRank() == null) {
557 String message
= "No rank defined. Check correct usage of authors!";
558 fireWarningEvent(message
, event
, 4);
559 if (isNotBlank(infrParAut
) || isNotBlank(infrAut
)) {
564 if (StringUtils
.isNotBlank(authorStr
)) {
565 TeamOrPersonBase
<?
>[] authorAndEx
= authorAndEx(state
, authorStr
, event
);
566 name
.setCombinationAuthorship(authorAndEx
[0]);
567 name
.setExCombinationAuthorship(authorAndEx
[1]);
569 if (StringUtils
.isNotBlank(paraut
)) {
570 TeamOrPersonBase
<?
>[] authorAndEx
= authorAndEx(state
, paraut
, event
);
571 name
.setBasionymAuthorship(authorAndEx
[0]);
572 name
.setExBasionymAuthorship(authorAndEx
[1]);
576 //remember author for following citations
577 state
.setLatestAuthorInHomotype(name
.getCombinationAuthorship());
580 private TeamOrPersonBase
<?
>[] authorAndEx(MarkupImportState state
, String authorAndEx
, XMLEvent xmlEvent
) {
581 authorAndEx
= authorAndEx
.trim();
582 TeamOrPersonBase
<?
>[] result
= new TeamOrPersonBase
[2];
584 String
[] split
= authorAndEx
.split("\\sex\\s");
585 if (split
.length
> 2) {
586 String message
= "There is more then 1 ' ex ' in author string. Can't separate author and ex-author";
587 fireWarningEvent(message
, xmlEvent
, 4);
588 result
[0] = createAuthor(state
, authorAndEx
);
589 } else if (split
.length
== 2) {
590 result
[0] = createAuthor(state
, split
[1]);
591 result
[1] = createAuthor(state
, split
[0]);
593 result
[0] = createAuthor(state
, split
[0]);
599 * Returns the (empty) name with the correct homotypical group depending on
600 * the taxon status and in case of synonym adds it to the taxon.
601 * Throws NPE if no currentTaxon is set in state.
604 * @param homotypicalGroup
608 private INonViralName
createName(MarkupImportState state
,
609 HomotypicalGroup homotypicalGroup
, boolean isSynonym
) {
611 Taxon taxon
= state
.getCurrentTaxon();
613 Rank defaultRank
= Rank
.SPECIES(); // can be any
614 name
= createNameByCode(state
, defaultRank
);
615 if (homotypicalGroup
!= null) {
616 name
.setHomotypicalGroup(homotypicalGroup
);
618 SynonymType synonymType
= SynonymType
.HETEROTYPIC_SYNONYM_OF
;
619 if (taxon
.getHomotypicGroup().equals(homotypicalGroup
)) {
620 synonymType
= SynonymType
.HOMOTYPIC_SYNONYM_OF
;
622 taxon
.addSynonymName(TaxonName
.castAndDeproxy(name
), synonymType
);
624 name
= taxon
.getName();
629 private void handleCitation(MarkupImportState state
, XMLEventReader reader
,
630 XMLEvent parentEvent
, INonViralName nvn
, TaxonRelationship misappliedRel
) throws XMLStreamException
{
631 String classValue
= getClassOnlyAttribute(parentEvent
);
633 TaxonName name
= TaxonName
.castAndDeproxy(nvn
);
634 state
.setCitation(true);
635 boolean hasRefPart
= false;
636 Map
<String
, String
> refMap
= new HashMap
<>();
637 while (reader
.hasNext()) {
638 XMLEvent next
= readNoWhitespace(reader
);
639 if (isMyEndingElement(next
, parentEvent
)) {
640 checkMandatoryElement(hasRefPart
, parentEvent
.asStartElement(), REF_PART
);
641 Reference reference
= createReference(state
, refMap
, next
);
642 String microReference
= refMap
.get(DETAILS
);
643 doCitation(state
, name
, classValue
, misappliedRel
,
644 reference
, microReference
, parentEvent
);
645 state
.setCitation(false);
647 } else if (isStartingElement(next
, REF_PART
)) {
648 handleRefPart(state
, reader
, next
, refMap
);
651 handleUnexpectedElement(next
);
654 throw new IllegalStateException("Citation has no closing tag");
659 private void doCitation(MarkupImportState state
, TaxonName name
,
660 String classValue
, TaxonRelationship misappliedRel
,
661 Reference reference
, String microCitation
,
662 XMLEvent parentEvent
) {
663 reference
= state
.getDeduplicationHelper().getExistingReference(reference
, true);
664 if (misappliedRel
!= null){
665 if (!PUBLICATION
.equalsIgnoreCase(classValue
)){
666 fireWarningEvent("'Usage' not handled correctly for misidentifications", parentEvent
, 4);
668 Taxon misappliedTaxon
= misappliedRel
.getFromTaxon();
669 misappliedTaxon
.setSec(reference
);
670 misappliedTaxon
.setSecMicroReference(microCitation
);
671 misappliedRel
.setCitation(state
.getConfig().getSourceReference());
673 }else if (PUBLICATION
.equalsIgnoreCase(classValue
)) {
674 name
.setNomenclaturalReference(reference
);
675 name
.setNomenclaturalMicroReference(microCitation
);
676 } else if (USAGE
.equalsIgnoreCase(classValue
)) {
677 Taxon taxon
= state
.getCurrentTaxon();
678 TaxonDescription td
= getDefaultTaxonDescription(taxon
, false, true, state
.getConfig().getSourceReference());
679 TextData citation
= TextData
.NewInstance(Feature
.CITATION());
680 // TODO name used in source
681 citation
.addSource(OriginalSourceType
.PrimaryTaxonomicSource
, null, null, reference
, microCitation
, name
, null);
682 td
.addElement(citation
);
683 } else if (TYPE
.equalsIgnoreCase(classValue
)) {
684 handleNotYetImplementedAttributeValue(parentEvent
, CLASS
, classValue
);
686 // TODO Not yet implemented
687 handleNotYetImplementedAttributeValue(parentEvent
, CLASS
, classValue
);
692 * Tests if the names rank is consistent with the given author strings.
693 * NOTE: Tags for authors are differ depending on the rank.
702 private void testRankAuthorConsistency(INonViralName name
, XMLEvent event
,
703 String authorStr
, String paraut
, String infrParAut
, String infrAut
) {
704 if (name
.getRank() == null) {
707 if (name
.getRank().isInfraSpecific()) {
708 if (StringUtils
.isBlank(infrParAut
)
709 && StringUtils
.isBlank(infrAut
) // was isNotBlank before
711 && (StringUtils
.isNotBlank(paraut
) || StringUtils
712 .isNotBlank(authorStr
)) && !name
.isAutonym()) {
713 String message
= "Rank is infraspecicific but has only specific or higher author(s)";
714 fireWarningEvent(message
, event
, 4);
717 // is not infraspecific
718 if (StringUtils
.isNotBlank(infrParAut
)
719 || StringUtils
.isNotBlank(infrAut
)) {
720 String message
= "Rank is not infraspecicific but name has infra author(s)";
721 fireWarningEvent(message
, event
, 4);
726 private Reference
createReference(MarkupImportState state
,
727 Map
<String
, String
> refMap
, XMLEvent parentEvent
) {
731 String type
= getAndRemoveMapKey(refMap
, PUBTYPE
);
732 String authorStr
= getAndRemoveMapKey(refMap
, AUTHOR
);
733 String titleStr
= getAndRemoveMapKey(refMap
, PUBTITLE
);
734 String titleCache
= getAndRemoveMapKey(refMap
, PUBFULLNAME
);
735 String volume
= getAndRemoveMapKey(refMap
, VOLUME
);
736 String edition
= getAndRemoveMapKey(refMap
, EDITION
);
737 String editors
= getAndRemoveMapKey(refMap
, EDITORS
);
738 String year
= getAndRemoveMapKey(refMap
, YEAR
);
739 String pubName
= getAndRemoveMapKey(refMap
, PUBNAME
);
740 String pages
= getAndRemoveMapKey(refMap
, PAGES
);
741 String publocation
= getAndRemoveMapKey(refMap
, PUBLOCATION
);
742 String publisher
= getAndRemoveMapKey(refMap
, PUBLISHER
);
743 String appendix
= getAndRemoveMapKey(refMap
, APPENDIX
);
744 String issue
= getAndRemoveMapKey(refMap
, ISSUE
);
745 String nameStatus
= getAndRemoveMapKey(refMap
, NAME_STATUS
);
747 if (state
.isCitation()) {
748 reference
= handleCitationSpecific(state
, type
, authorStr
,
749 titleStr
, titleCache
, volume
, issue
, edition
, editors
, pubName
, pages
, appendix
, refMap
, parentEvent
);
751 } else { // no citation
752 reference
= handleNonCitationSpecific(state
, type
, authorStr
, titleStr
,
753 titleCache
, volume
, issue
, edition
, editors
, pubName
, appendix
, pages
, parentEvent
);
757 VerbatimTimePeriod timeperiod
= TimePeriodParser
.parseStringVerbatim(year
);
758 if (reference
.getType().equals(ReferenceType
.BookSection
)){
759 reference
.getInBook().setDatePublished(timeperiod
);
761 reference
.setDatePublished(timeperiod
);
763 //Quickfix for these 2 attributes used in feature.references
764 Reference inRef
= reference
.getInReference() == null ? reference
: reference
.getInReference();
766 if (isNotBlank(publisher
)){
767 inRef
.setPublisher(publisher
);
771 if (isNotBlank(publocation
)){
772 inRef
.setPlacePublished(publocation
);
775 if (isNotBlank(nameStatus
)){
776 state
.setNameStatus(nameStatus
);
780 String
[] unhandledList
= new String
[] { ALTERNATEPUBTITLE
, NOTES
, STATUS
};
781 for (String unhandled
: unhandledList
) {
782 String value
= getAndRemoveMapKey(refMap
, unhandled
);
783 if (isNotBlank(value
)) {
784 this.handleNotYetImplementedAttributeValue(parentEvent
, CLASS
, unhandled
);
788 for (String key
: refMap
.keySet()) {
789 if (!DETAILS
.equalsIgnoreCase(key
)) {
790 this.fireUnexpectedAttributeValue(parentEvent
, CLASS
, key
);
799 * Handles references used in the citation tag
801 * @see #handleNonCitationSpecific(String, String, String, String, String, String, String, String)
803 private Reference
handleCitationSpecific(MarkupImportState state
,
804 String type
, String authorStr
, String titleStr
, String titleCache
,
805 String volume
, String issue
, String edition
, String editors
, String pubName
,
806 String pages
, String appendix
, Map
<String
, String
> refMap
, XMLEvent parentEvent
) {
808 if (titleStr
!= null){
809 String message
= "Currently it is not expected that a titleStr exists in a citation";
810 fireWarningEvent(message
, parentEvent
, 4);
812 if (isBlank(volume
) && isNotBlank(issue
)){
813 String message
= "Issue ('"+issue
+"') exists but no volume";
814 fireWarningEvent(message
, parentEvent
, 4);
816 }else if (isNotBlank(issue
)){
817 volume
= volume
+ "("+ issue
+ ")";
821 RefType refType
= defineRefTypeForCitation(type
, volume
, editors
, authorStr
, pubName
, parentEvent
);
824 if (isNotBlank(appendix
)){
825 pubName
= pubName
== null ? appendix
: (pubName
+ " " + appendix
).replaceAll(" ", " ");
828 if (refType
== RefType
.Article
) {
829 IArticle article
= ReferenceFactory
.newArticle();
830 if (pubName
!= null) {
831 IJournal journal
= ReferenceFactory
.newJournal();
832 journal
.setTitle(pubName
);
833 article
.setInJournal(journal
);
834 article
.setVolume(volume
);
835 if (isNotBlank(edition
)){
836 String message
= "Article must not have an edition.";
837 fireWarningEvent(message
, parentEvent
, 4);
840 reference
= (Reference
) article
;
841 } else if (refType
== RefType
.BookSection
) {
843 reference
= ReferenceFactory
.newBookSection();
844 IBook book
= ReferenceFactory
.newBook();
845 reference
.setInBook(book
);
846 book
.setTitle(pubName
);
847 book
.setVolume(volume
);
848 book
.setEdition(edition
);
850 if (state
.getConfig().isUseEditorAsInAuthorWhereNeeded()){
851 TeamOrPersonBase
<?
> inAuthor
= createAuthor(state
, editors
);
852 book
.setAuthorship(inAuthor
);
855 } else if (refType
== RefType
.Book
){
857 reference
= ReferenceFactory
.newBook();
858 reference
.setTitle(pubName
);
859 reference
.setVolume(volume
);
860 reference
.setEdition(edition
);
861 }else if (refType
== RefType
.Generic
){
862 //Generic - undefinable
863 // String message = "Can't define the type of the reference. Use generic instead";
864 // fireWarningEvent(message, parentEvent, 4);
865 reference
= ReferenceFactory
.newGeneric();
866 reference
.setTitle(pubName
);
867 reference
.setEdition(edition
);
869 //volume indicates an in-reference
870 if (isNotBlank(volume
)){
871 Reference partOf
= ReferenceFactory
.newGeneric();
872 partOf
.setVolume(volume
);
873 partOf
.setInReference(reference
);
876 }else if (refType
== RefType
.LatestUsed
){
877 Reference latestReference
= state
.getLatestReferenceInHomotype();
878 if (latestReference
== null){
879 String message
= "No former reference available for incomplete citation";
880 fireWarningEvent(message
, parentEvent
, 6);
881 reference
= ReferenceFactory
.newGeneric();
883 if (latestReference
.getInReference() != null){
884 reference
= latestReference
.clone();
886 String message
= "Latest reference is not an in-reference. This is not yet handled.";
887 fireWarningEvent(message
, parentEvent
, 6);
888 reference
= ReferenceFactory
.newGeneric();
891 reference
.setVolume(volume
);
892 if (isNotBlank(edition
)){
893 String message
= "Edition not yet handled for incomplete citations";
894 fireWarningEvent(message
, parentEvent
, 4);
898 String message
= "Unhandled reference type: %s" ;
899 fireWarningEvent(String
.format(message
, refType
.toString()), parentEvent
, 8);
900 reference
= ReferenceFactory
.newGeneric();
904 TeamOrPersonBase
<?
> author
;
905 if (isBlank(authorStr
)){
906 if (refType
!= RefType
.LatestUsed
){
907 author
= state
.getLatestAuthorInHomotype();
908 reference
.setAuthorship(author
);
911 author
= createAuthor(state
, authorStr
);
912 state
.setLatestAuthorInHomotype(author
);
913 reference
.setAuthorship(author
);
918 handleTitlesInCitation(titleStr
, titleCache
, parentEvent
, reference
);
921 handleEditorsInCitation(edition
, editors
, reference
, parentEvent
);
924 handlePages(state
, refMap
, parentEvent
, reference
, pages
);
926 // state.getDeduplicationHelper(docImport).getExistingReference(state, reference);
928 //remember reference for following citation
929 state
.setLatestReferenceInHomotype(reference
);
934 private void handleEditorsInCitation(String edition
, String editors
, Reference reference
, XMLEvent parentEvent
) {
936 reference
.setEditor(editors
);
937 if ( editors
!= null){
938 String message
= "Citation reference has an editor. This is unusual for a citation reference (appears regularly in <reference> references";
939 fireWarningEvent(message
, parentEvent
, 4);
943 private void handleTitlesInCitation(String titleStr
, String titleCache
,
944 XMLEvent parentEvent
, Reference reference
) {
945 if (isNotBlank(titleStr
)){
946 reference
.setTitle(titleStr
);
949 if (StringUtils
.isNotBlank(titleCache
)) {
950 reference
.setTitleCache(titleCache
, true);
952 if (titleStr
!= null || titleCache
!= null){
953 String message
= "Citation reference has a title or a full title. Both is unusual for a citation reference (appears regularly in <reference> references";
954 fireWarningEvent(message
, parentEvent
, 4);
958 private enum RefType
{
966 private RefType
defineRefTypeForCitation(String type
, String volume
, String editors
,
967 String authorStr
, String pubName
, XMLEvent parentEvent
) {
968 if ("journal".equalsIgnoreCase(type
)){
969 return RefType
.Article
;
971 if (editors
== null){
973 if (pubName
== null){
974 //looks like we need to use reference info from former citations here
975 return RefType
.LatestUsed
;
976 }else if (volume
== null){
977 return RefType
.Book
; //Book must not have in-authors
978 }else if (IJournal
.guessIsJournalName(pubName
)){
979 return RefType
.Article
;
981 return RefType
.Generic
;
986 if (pubName
!= null){
987 return RefType
.BookSection
;
989 String message
= "Unexpected state: Citation has editors but no pubName";
990 fireWarningEvent(message
, parentEvent
, 4);
991 return RefType
.Generic
;
998 private void handlePages(MarkupImportState state
,
999 Map
<String
, String
> refMap
, XMLEvent parentEvent
,
1000 Reference reference
, String pages
) {
1001 // TODO check if this is handled correctly in FM markup
1002 boolean switchPages
= state
.getConfig().isHandlePagesAsDetailWhereNeeded();
1004 if (pages
!= null ){
1005 String detail
= refMap
.get(DETAILS
);
1006 if (isBlank(detail
)){
1007 if (pages
.contains("-")){
1008 String message
= "There is a pages tag with '-'. Unclear if this really means pages";
1009 fireWarningEvent(message
, parentEvent
, 8);
1010 reference
.setPages(pages
);
1012 //handle pages as detail, this is at least true for Flora Malesiana
1013 refMap
.put(DETAILS
, pages
);
1016 if (! pages
.contains("-")){
1017 String message
= "There are pages and detail available where pages may also hold details information.";
1018 fireWarningEvent(message
, parentEvent
, 8);
1020 reference
.setPages(pages
);
1026 public Reference
handleReference(MarkupImportState state
,
1027 XMLEventReader reader
, XMLEvent parentEvent
)
1028 throws XMLStreamException
{
1029 checkNoAttributes(parentEvent
);
1031 boolean hasRefPart
= false;
1032 Map
<String
, String
> refMap
= new HashMap
<String
, String
>();
1033 while (reader
.hasNext()) {
1034 XMLEvent next
= readNoWhitespace(reader
);
1035 if (isMyEndingElement(next
, parentEvent
)) {
1036 checkMandatoryElement(hasRefPart
, parentEvent
.asStartElement(), REF_PART
);
1037 Reference reference
= createReference(state
, refMap
, next
);
1039 } else if (isStartingElement(next
, REF_PART
)) {
1040 handleRefPart(state
, reader
, next
, refMap
);
1043 handleUnexpectedElement(next
);
1046 // TODO handle missing end element
1047 throw new IllegalStateException("<Reference> has no closing tag");