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
.TeamOrPersonBase
;
25 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
26 import eu
.etaxonomy
.cdm
.model
.common
.OriginalSourceType
;
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
.INonViralName
;
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
.SynonymType
;
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 final 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 (isMyEndingElement(next
, parentEvent
)) {
78 } else if (isStartingElement(next
, HOMOTYPES
)) {
79 handleHomotypes(state
, reader
, next
.asStartElement());
80 } else if (isStartingElement(next
, NOMENCLATURAL_NOTES
)) {
81 handleAmbigousManually(state
, reader
, next
.asStartElement());
83 fireSchemaConflictEventExpectedStartTag(HOMOTYPES
, reader
);
84 state
.setUnsuccessfull();
91 private void handleHomotypes(MarkupImportState state
,
92 XMLEventReader reader
, StartElement parentEvent
)
93 throws XMLStreamException
{
94 checkNoAttributes(parentEvent
);
96 HomotypicalGroup homotypicalGroup
= null;
98 boolean hasNom
= false;
99 while (reader
.hasNext()) {
100 XMLEvent next
= readNoWhitespace(reader
);
101 if (isMyEndingElement(next
, parentEvent
)) {
102 checkMandatoryElement(hasNom
, parentEvent
, NOM
);
103 state
.setLatestAuthorInHomotype(null);
105 } else if (isEndingElement(next
, NAME_TYPE
)) {
106 state
.setNameType(false);
107 } else if (isStartingElement(next
, NOM
)) {
108 INonViralName name
= handleNom(state
, reader
, next
, homotypicalGroup
);
109 homotypicalGroup
= name
.getHomotypicalGroup();
111 } else if (isStartingElement(next
, NAME_TYPE
)) {
112 state
.setNameType(true);
113 handleNameType(state
, reader
, next
, homotypicalGroup
);
114 } else if (isStartingElement(next
, SPECIMEN_TYPE
)) {
115 specimenImport
.handleSpecimenType(state
, reader
, next
, homotypicalGroup
);
116 } else if (isStartingElement(next
, NOTES
)) {
117 handleNotYetImplementedElement(next
);
119 handleUnexpectedElement(next
);
122 state
.setLatestAuthorInHomotype(null);
123 // TODO handle missing end element
124 throw new IllegalStateException("Homotypes has no closing tag");
128 private void handleNameType(MarkupImportState state
, XMLEventReader reader
,
129 XMLEvent parentEvent
, HomotypicalGroup homotypicalGroup
)
130 throws XMLStreamException
{
131 Map
<String
, Attribute
> attributes
= getAttributes(parentEvent
);
132 String typeStatus
= getAndRemoveAttributeValue(attributes
, TYPE_STATUS
);
133 checkNoAttributes(attributes
, parentEvent
);
135 NameTypeDesignationStatus status
;
137 status
= NameTypeParser
.parseNameTypeStatus(typeStatus
);
138 } catch (UnknownCdmTypeException e
) {
139 String message
= "Type status could not be recognized: %s";
140 message
= String
.format(message
, typeStatus
);
141 fireWarningEvent(message
, parentEvent
, 4);
145 boolean hasNom
= false;
146 while (reader
.hasNext()) {
147 XMLEvent next
= readNoWhitespace(reader
);
148 if (next
.isEndElement()) {
149 if (isMyEndingElement(next
, parentEvent
)) {
150 checkMandatoryElement(hasNom
, parentEvent
.asStartElement(),
152 state
.setNameType(false);
155 if (isEndingElement(next
, ACCEPTED_NAME
)) {
156 // NOT YET IMPLEMENTED
157 popUnimplemented(next
.asEndElement());
159 handleUnexpectedEndElement(next
.asEndElement());
162 } else if (next
.isStartElement()) {
163 if (isStartingElement(next
, NOM
)) {
164 // TODO should we check if the type is always a species, is
166 NonViralName
<?
> speciesName
= handleNom(state
, reader
,
168 for (TaxonNameBase
<?
, ?
> name
: homotypicalGroup
169 .getTypifiedNames()) {
170 name
.addNameTypeDesignation(speciesName
, null, null,
171 null, status
, false, false, false, false);
174 } else if (isStartingElement(next
, ACCEPTED_NAME
)) {
175 handleNotYetImplementedElement(next
);
177 handleUnexpectedStartElement(next
);
180 handleUnexpectedElement(next
);
183 // TODO handle missing end element
184 throw new IllegalStateException("Homotypes has no closing tag");
189 * Creates the name defined by a nom tag. Adds it to the given homotypical
190 * group (if not null).
195 * @param homotypicalGroup
197 * @throws XMLStreamException
199 private NonViralName
<?
> handleNom(MarkupImportState state
, XMLEventReader reader
,
200 XMLEvent parentEvent
, HomotypicalGroup homotypicalGroup
) throws XMLStreamException
{
201 boolean isSynonym
= false;
202 boolean isNameType
= state
.isNameType();
204 String classValue
= getClassOnlyAttribute(parentEvent
);
205 NonViralName
<?
> name
;
206 if (!isNameType
&& ACCEPTED
.equalsIgnoreCase(classValue
)) {
208 name
= createName(state
, homotypicalGroup
, isSynonym
);
209 } else if (!isNameType
&& SYNONYM
.equalsIgnoreCase(classValue
)) {
211 name
= createName(state
, homotypicalGroup
, isSynonym
);
212 } else if (isNameType
&& NAME_TYPE
.equalsIgnoreCase(classValue
)) {
213 // TODO do we need to define the rank here?
214 name
= createNameByCode(state
, null);
216 fireUnexpectedAttributeValue(parentEvent
, CLASS
, classValue
);
217 name
= createNameByCode(state
, null);
220 Map
<String
, String
> nameMap
= new HashMap
<String
, String
>();
223 boolean nameFilled
= false;
224 while (reader
.hasNext()) {
225 XMLEvent next
= readNoWhitespace(reader
);
226 if (isMyEndingElement(next
, parentEvent
)) {
227 // fill the name with all data gathered, if not yet done before
228 if (nameFilled
== false){
229 fillName(state
, nameMap
, name
, next
);
231 handleNomText(state
, parentEvent
, text
, isNameType
);
233 } else if (isEndingElement(next
, ANNOTATION
)) {
234 // NOT YET IMPLEMENTED //TODO test
235 // handleSimpleAnnotation
236 popUnimplemented(next
.asEndElement());
237 }else if (isStartingElement(next
, FULL_NAME
)) {
238 handleFullName(state
, reader
, name
, next
);
239 } else if (isStartingElement(next
, NUM
)) {
240 handleNomNum(state
, reader
, next
);
241 } else if (isStartingElement(next
, NAME
)) {
242 handleName(state
, reader
, next
, nameMap
);
243 } else if (isStartingElement(next
, CITATION
)) {
244 //we need to fill the name here to have nomenclatural author available for the following citations
245 fillName(state
, nameMap
, name
, next
);
247 handleCitation(state
, reader
, next
, name
, nameMap
);
248 } else if (next
.isCharacters()) {
249 text
+= next
.asCharacters().getData();
250 } else if (isStartingElement(next
, HOMONYM
)) {
251 handleNotYetImplementedElement(next
);
252 } else if (isStartingElement(next
, NOTES
)) {
253 handleNotYetImplementedElement(next
);
254 } else if (isStartingElement(next
, NOMENCLATURAL_NOTES
)) {
255 handleNotYetImplementedElement(next
);
256 } else if (isStartingElement(next
, ANNOTATION
)) {
257 handleNotYetImplementedElement(next
);
259 handleUnexpectedElement(next
);
262 // TODO handle missing end element
263 throw new IllegalStateException("Nom has no closing tag");
267 * Handles appearance of text within <nom> tags.
268 * Usually this is not expected except for some information that is already handled
269 * elsewhere, e.g. the string Nametype is holding information that is available already
270 * via the surrounding nametype tag. Therefore this information can be neglected.
271 * This method is open for upcoming cases which need to be handled.
277 private void handleNomText(MarkupImportState state
, XMLEvent event
, String text
, boolean isNameType
) {
282 //neglect known redundant strings
283 if (isNameType
&& (text
.matches("(?i)^Esp[\u00E8\u00C8]ce[·\\-\\s]type\\:$")
284 || charIsSimpleType(text
) )){
286 }//neglect meaningless punctuation
287 else if (isPunctuation(text
)){
290 else if (isPunctuation(text
)){
293 String message
= "Unhandled text in <nom> tag: \"%s\"";
294 fireWarningEvent(String
.format(message
, text
), event
, 4);
302 * @throws XMLStreamException
304 private void handleNomNum(MarkupImportState state
, XMLEventReader reader
,
305 XMLEvent next
) throws XMLStreamException
{
306 String num
= getCData(state
, reader
, next
);
307 num
= num
.replace(".", "");
308 num
= num
.replace(")", "");
309 if (StringUtils
.isNotBlank(num
)) {
310 if (state
.getCurrentTaxonNum() != null
311 && !state
.getCurrentTaxonNum().equals(num
)) {
312 String message
= "Taxontitle num and homotypes/nom/num differ ( %s <-> %s ). I use the later one.";
313 message
= String
.format(message
,
314 state
.getCurrentTaxonNum(), num
);
315 fireWarningEvent(message
, next
, 4);
317 state
.setCurrentTaxonNum(num
);
323 private void handleName(MarkupImportState state
, XMLEventReader reader
,
324 XMLEvent parentEvent
, Map
<String
, String
> nameMap
)
325 throws XMLStreamException
{
326 String classValue
= getClassOnlyAttribute(parentEvent
);
329 while (reader
.hasNext()) {
330 XMLEvent next
= readNoWhitespace(reader
);
331 if (isMyEndingElement(next
, parentEvent
)) {
332 nameMap
.put(classValue
, text
);
334 } else if (isStartingElement(next
, ANNOTATION
)) {
335 handleNotYetImplementedElement(next
); // TODO test handleSimpleAnnotation
336 } else if (isStartingElement(next
, FOOTNOTE_REF
)) {
337 handleNotYetImplementedElement(next
);
338 } else if (next
.isCharacters()) {
339 text
+= next
.asCharacters().getData();
341 handleUnexpectedElement(next
);
344 throw new IllegalStateException("name has no closing tag");
347 private void fillName(MarkupImportState state
, Map
<String
, String
> nameMap
,
348 TaxonNameBase name
, XMLEvent event
) {
350 // Ranks: family, subfamily, tribus, genus, subgenus, section,
351 // subsection, species, subspecies, variety, subvariety, forma
352 // infrank, paraut, author, infrparaut, infraut, status, notes
354 String infrank
= getAndRemoveMapKey(nameMap
, INFRANK
);
355 String authorStr
= getAndRemoveMapKey(nameMap
, AUTHOR
);
356 String paraut
= getAndRemoveMapKey(nameMap
, PARAUT
);
358 String infrParAut
= getAndRemoveMapKey(nameMap
, INFRPARAUT
);
359 String infrAut
= getAndRemoveMapKey(nameMap
, INFRAUT
);
361 String statusStr
= getAndRemoveMapKey(nameMap
, STATUS
);
362 String notes
= getAndRemoveMapKey(nameMap
, NOTES
);
364 if (!name
.isProtectedTitleCache()) { // otherwise fullName
366 makeRankDecision(state
, nameMap
, name
, event
, infrank
);
368 // test consistency of rank and authors
369 testRankAuthorConsistency(name
, event
, authorStr
, paraut
,infrParAut
, infrAut
);
372 makeNomenclaturalAuthors(state
, event
, name
, authorStr
, paraut
, infrParAut
, infrAut
);
376 // TODO handle pro parte, pro syn. etc.
377 if (StringUtils
.isNotBlank(statusStr
)) {
378 String proPartePattern
= "(pro parte|p.p.)";
379 if (statusStr
.matches(proPartePattern
)) {
380 state
.setProParte(true);
383 // TODO handle trim earlier
384 statusStr
= statusStr
.trim();
385 NomenclaturalStatusType nomStatusType
= NomenclaturalStatusType
.getNomenclaturalStatusTypeByAbbreviation(statusStr
, name
);
386 name
.addStatus(NomenclaturalStatus
.NewInstance(nomStatusType
));
387 } catch (UnknownCdmTypeException e
) {
388 String message
= "Status '%s' could not be recognized";
389 message
= String
.format(message
, statusStr
);
390 fireWarningEvent(message
, event
, 4);
395 if (StringUtils
.isNotBlank(notes
)) {
396 handleNotYetImplementedAttributeValue(event
, CLASS
, NOTES
);
409 private void makeRankDecision(MarkupImportState state
,
410 Map
<String
, String
> nameMap
, INonViralName name
, XMLEvent event
,
413 for (String key
: nameMap
.keySet()) {
414 Rank rank
= makeRank(state
, key
, false);
416 handleNotYetImplementedAttributeValue(event
, CLASS
, key
);
418 if (name
.getRank() == null || rank
.isLower(name
.getRank())) {
421 String value
= nameMap
.get(key
);
422 if (rank
.isSupraGeneric() || rank
.isGenus()) {
423 if ((key
.equalsIgnoreCase(GENUS_ABBREVIATION
)
424 && isNotBlank(state
.getLatestGenusEpithet()) || isGenusAbbrev(
425 value
, state
.getLatestGenusEpithet()))) {
426 value
= state
.getLatestGenusEpithet();
428 name
.setGenusOrUninomial(toFirstCapital(value
));
429 } else if (rank
.isInfraGeneric()) {
430 name
.setInfraGenericEpithet(toFirstCapital(value
));
431 } else if (rank
.isSpecies()) {
432 if (state
.getConfig().isAllowCapitalSpeciesEpithet()
433 && isFirstCapitalWord(value
)) { // capital letters
440 name
.setSpecificEpithet(value
);
442 name
.setSpecificEpithet(value
.toLowerCase());
444 } else if (rank
.isInfraSpecific()) {
445 name
.setInfraSpecificEpithet(value
.toLowerCase());
447 String message
= "Invalid rank '%s'. Can't decide which epithet to fill with '%s'";
448 message
= String
.format(message
, rank
.getTitleCache(),
450 fireWarningEvent(message
, event
, 4);
455 // handle given infrank marker
456 if (StringUtils
.isNotBlank(infrankStr
)) {
457 Rank infRank
= makeRank(state
, infrankStr
, true);
459 if (infRank
== null) {
460 String message
= "Infrank '%s' rank not recognized";
461 message
= String
.format(message
, infrankStr
);
462 fireWarningEvent(message
, event
, 4);
464 if (name
.getRank() == null) {
465 name
.setRank(infRank
);
466 } else if (infRank
.isLower(name
.getRank())) {
467 String message
= "InfRank '%s' is lower than existing rank ";
468 message
= String
.format(message
, infrankStr
);
469 fireWarningEvent(message
, event
, 2);
470 name
.setRank(infRank
);
471 } else if (infRank
.equals(name
.getRank())) {
474 String message
= "InfRank '%s' is higher than existing rank ";
475 message
= String
.format(message
, infrankStr
);
476 fireWarningEvent(message
, event
, 2);
491 private void makeNomenclaturalAuthors(MarkupImportState state
, XMLEvent event
, INonViralName name
,
492 String authorStr
, String paraut
, String infrParAut
, String infrAut
) {
493 if (name
.getRank() != null && name
.getRank().isInfraSpecific()) {
494 if (StringUtils
.isNotBlank(infrAut
)) {
495 TeamOrPersonBase
<?
>[] authorAndEx
= authorAndEx(infrAut
, event
);
496 name
.setCombinationAuthorship(authorAndEx
[0]);
497 name
.setExCombinationAuthorship(authorAndEx
[1]);
499 if (StringUtils
.isNotBlank(infrParAut
)) {
500 TeamOrPersonBase
<?
>[] authorAndEx
= authorAndEx(infrParAut
,event
);
501 name
.setBasionymAuthorship(authorAndEx
[0]);
502 name
.setExBasionymAuthorship(authorAndEx
[1]);
505 if (name
.getRank() == null) {
506 String message
= "No rank defined. Check correct usage of authors!";
507 fireWarningEvent(message
, event
, 4);
508 if (isNotBlank(infrParAut
) || isNotBlank(infrAut
)) {
513 if (StringUtils
.isNotBlank(authorStr
)) {
514 TeamOrPersonBase
<?
>[] authorAndEx
= authorAndEx(authorStr
, event
);
515 name
.setCombinationAuthorship(authorAndEx
[0]);
516 name
.setExCombinationAuthorship(authorAndEx
[1]);
518 if (StringUtils
.isNotBlank(paraut
)) {
519 TeamOrPersonBase
<?
>[] authorAndEx
= authorAndEx(paraut
, event
);
520 name
.setBasionymAuthorship(authorAndEx
[0]);
521 name
.setExBasionymAuthorship(authorAndEx
[1]);
525 //remember author for following citations
526 state
.setLatestAuthorInHomotype(name
.getCombinationAuthorship());
529 private TeamOrPersonBase
<?
>[] authorAndEx(String authorAndEx
, XMLEvent xmlEvent
) {
530 authorAndEx
= authorAndEx
.trim();
531 TeamOrPersonBase
<?
>[] result
= new TeamOrPersonBase
[2];
533 String
[] split
= authorAndEx
.split("\\sex\\s");
534 if (split
.length
> 2) {
535 String message
= "There is more then 1 ' ex ' in author string. Can't separate author and ex-author";
536 fireWarningEvent(message
, xmlEvent
, 4);
537 result
[0] = createAuthor(authorAndEx
);
538 } else if (split
.length
== 2) {
539 result
[0] = createAuthor(split
[1]);
540 result
[1] = createAuthor(split
[0]);
542 result
[0] = createAuthor(split
[0]);
548 * Returns the (empty) name with the correct homotypical group depending on
549 * the taxon status. Throws NPE if no currentTaxon is set in state.
552 * @param homotypicalGroup
556 private NonViralName
<?
> createName(MarkupImportState state
,
557 HomotypicalGroup homotypicalGroup
, boolean isSynonym
) {
558 NonViralName
<?
> name
;
559 Taxon taxon
= state
.getCurrentTaxon();
561 Rank defaultRank
= Rank
.SPECIES(); // can be any
562 name
= createNameByCode(state
, defaultRank
);
563 if (homotypicalGroup
!= null) {
564 name
.setHomotypicalGroup(homotypicalGroup
);
566 SynonymType synonymType
= SynonymType
.HETEROTYPIC_SYNONYM_OF();
567 if (taxon
.getHomotypicGroup().equals(homotypicalGroup
)) {
568 synonymType
= SynonymType
.HOMOTYPIC_SYNONYM_OF();
570 taxon
.addSynonymName(name
, synonymType
);
572 name
= CdmBase
.deproxy(taxon
.getName(), NonViralName
.class);
577 private void handleCitation(MarkupImportState state
, XMLEventReader reader
,
578 XMLEvent parentEvent
, INonViralName name
, Map
<String
, String
> nameMap
) throws XMLStreamException
{
579 String classValue
= getClassOnlyAttribute(parentEvent
);
581 state
.setCitation(true);
582 boolean hasRefPart
= false;
583 Map
<String
, String
> refMap
= new HashMap
<String
, String
>();
584 while (reader
.hasNext()) {
585 XMLEvent next
= readNoWhitespace(reader
);
586 if (isMyEndingElement(next
, parentEvent
)) {
587 checkMandatoryElement(hasRefPart
, parentEvent
.asStartElement(), REF_PART
);
588 Reference reference
= createReference(state
, refMap
, next
);
589 String microReference
= refMap
.get(DETAILS
);
590 doCitation(state
, name
, classValue
, reference
, microReference
, parentEvent
);
591 state
.setCitation(false);
593 } else if (isStartingElement(next
, REF_PART
)) {
594 handleRefPart(state
, reader
, next
, refMap
);
597 handleUnexpectedElement(next
);
600 throw new IllegalStateException("Citation has no closing tag");
604 private void handleRefPart(MarkupImportState state
, XMLEventReader reader
,
605 XMLEvent parentEvent
, Map
<String
, String
> refMap
)
606 throws XMLStreamException
{
607 String classValue
= getClassOnlyAttribute(parentEvent
);
610 while (reader
.hasNext()) {
611 XMLEvent next
= readNoWhitespace(reader
);
612 if (isMyEndingElement(next
, parentEvent
)) {
613 refMap
.put(classValue
, text
);
615 } else if (next
.isStartElement()) {
616 if (isStartingElement(next
, ANNOTATION
)) {
617 handleNotYetImplementedElement(next
); // TODO test
618 // handleSimpleAnnotation
619 } else if (isStartingElement(next
, ITALICS
)) {
620 handleNotYetImplementedElement(next
);
621 } else if (isStartingElement(next
, BOLD
)) {
622 handleNotYetImplementedElement(next
);
624 handleUnexpectedStartElement(next
.asStartElement());
626 } else if (next
.isCharacters()) {
627 text
+= next
.asCharacters().getData();
629 handleUnexpectedEndElement(next
.asEndElement());
632 throw new IllegalStateException("RefPart has no closing tag");
636 private void doCitation(MarkupImportState state
, INonViralName name
,
637 String classValue
, Reference reference
, String microCitation
,
638 XMLEvent parentEvent
) {
639 if (PUBLICATION
.equalsIgnoreCase(classValue
)) {
640 name
.setNomenclaturalReference(reference
);
641 name
.setNomenclaturalMicroReference(microCitation
);
642 } else if (USAGE
.equalsIgnoreCase(classValue
)) {
643 Taxon taxon
= state
.getCurrentTaxon();
644 TaxonDescription td
= getTaxonDescription(taxon
, state
.getConfig().getSourceReference(), false, true);
645 TextData citation
= TextData
.NewInstance(Feature
.CITATION());
646 // TODO name used in source
647 citation
.addSource(OriginalSourceType
.PrimaryTaxonomicSource
, null, null, reference
, microCitation
);
648 td
.addElement(citation
);
649 } else if (TYPE
.equalsIgnoreCase(classValue
)) {
650 handleNotYetImplementedAttributeValue(parentEvent
, CLASS
, classValue
);
652 // TODO Not yet implemented
653 handleNotYetImplementedAttributeValue(parentEvent
, CLASS
, classValue
);
658 * Tests if the names rank is consistent with the given author strings.
659 * NOTE: Tags for authors are differ depending on the rank.
668 private void testRankAuthorConsistency(INonViralName name
, XMLEvent event
,
669 String authorStr
, String paraut
, String infrParAut
, String infrAut
) {
670 if (name
.getRank() == null) {
673 if (name
.getRank().isInfraSpecific()) {
674 if (StringUtils
.isBlank(infrParAut
)
675 && StringUtils
.isBlank(infrAut
) // was isNotBlank before
677 && (StringUtils
.isNotBlank(paraut
) || StringUtils
678 .isNotBlank(authorStr
)) && !name
.isAutonym()) {
679 String message
= "Rank is infraspecicific but has only specific or higher author(s)";
680 fireWarningEvent(message
, event
, 4);
683 // is not infraspecific
684 if (StringUtils
.isNotBlank(infrParAut
)
685 || StringUtils
.isNotBlank(infrAut
)) {
686 String message
= "Rank is not infraspecicific but name has infra author(s)";
687 fireWarningEvent(message
, event
, 4);
692 private Reference
createReference(MarkupImportState state
,
693 Map
<String
, String
> refMap
, XMLEvent parentEvent
) {
697 String type
= getAndRemoveMapKey(refMap
, PUBTYPE
);
698 String authorStr
= getAndRemoveMapKey(refMap
, AUTHOR
);
699 String titleStr
= getAndRemoveMapKey(refMap
, PUBTITLE
);
700 String titleCache
= getAndRemoveMapKey(refMap
, PUBFULLNAME
);
701 String volume
= getAndRemoveMapKey(refMap
, VOLUME
);
702 String edition
= getAndRemoveMapKey(refMap
, EDITION
);
703 String editors
= getAndRemoveMapKey(refMap
, EDITORS
);
704 String year
= getAndRemoveMapKey(refMap
, YEAR
);
705 String pubName
= getAndRemoveMapKey(refMap
, PUBNAME
);
706 String pages
= getAndRemoveMapKey(refMap
, PAGES
);
707 String publocation
= getAndRemoveMapKey(refMap
, PUBLOCATION
);
708 String publisher
= getAndRemoveMapKey(refMap
, PUBLISHER
);
709 String appendix
= getAndRemoveMapKey(refMap
, APPENDIX
);
711 if (state
.isCitation()) {
712 reference
= handleCitationSpecific(state
, type
, authorStr
,
713 titleStr
, titleCache
, volume
, edition
, editors
, pubName
, pages
, appendix
, refMap
, parentEvent
);
715 } else { // no citation
716 reference
= handleNonCitationSpecific(type
, authorStr
, titleStr
,
717 titleCache
, volume
, edition
, editors
, pubName
, appendix
);
721 TimePeriod timeperiod
= TimePeriodParser
.parseString(year
);
722 if (reference
.getType().equals(ReferenceType
.BookSection
)){
723 reference
.getInBook().setDatePublished(timeperiod
);
725 reference
.setDatePublished(timeperiod
);
727 //Quickfix for these 2 attributes used in feature.references
728 Reference inRef
= reference
.getInReference() == null ? reference
: reference
.getInReference();
730 if (StringUtils
.isNotEmpty(publisher
)){
731 inRef
.setPublisher(publisher
);
735 if (StringUtils
.isNotEmpty(publocation
)){
736 inRef
.setPlacePublished(publocation
);
740 String
[] unhandledList
= new String
[] { ALTERNATEPUBTITLE
, ISSUE
, NOTES
, STATUS
};
741 for (String unhandled
: unhandledList
) {
742 String value
= getAndRemoveMapKey(refMap
, unhandled
);
743 if (isNotBlank(value
)) {
744 this.handleNotYetImplementedAttributeValue(parentEvent
, CLASS
, unhandled
);
748 for (String key
: refMap
.keySet()) {
749 if (!DETAILS
.equalsIgnoreCase(key
)) {
750 this.fireUnexpectedAttributeValue(parentEvent
, CLASS
, key
);
759 * Handles references used in the citation tag
761 * @see #handleNonCitationSpecific(String, String, String, String, String, String, String, String)
763 private Reference
handleCitationSpecific(MarkupImportState state
,
764 String type
, String authorStr
, String titleStr
, String titleCache
,
765 String volume
, String edition
, String editors
, String pubName
,
766 String pages
, String appendix
, Map
<String
, String
> refMap
, XMLEvent parentEvent
) {
768 if (titleStr
!= null){
769 String message
= "Currently it is not expected that a titleStr exists in a citation";
770 fireWarningEvent(message
, parentEvent
, 4);
773 RefType refType
= defineRefTypeForCitation(type
, volume
, editors
, authorStr
, pubName
, parentEvent
);
776 if (isNotBlank(appendix
)){
777 pubName
= pubName
== null ? appendix
: (pubName
+ " " + appendix
).replaceAll(" ", " ");
780 if (refType
== RefType
.Article
) {
781 IArticle article
= ReferenceFactory
.newArticle();
782 if (pubName
!= null) {
783 IJournal journal
= ReferenceFactory
.newJournal();
784 journal
.setTitle(pubName
);
785 article
.setInJournal(journal
);
786 article
.setVolume(volume
);
787 if (isNotBlank(edition
)){
788 String message
= "Article must not have an edition.";
789 fireWarningEvent(message
, parentEvent
, 4);
792 reference
= (Reference
) article
;
793 } else if (refType
== RefType
.BookSection
) {
795 reference
= ReferenceFactory
.newBookSection();
796 IBook book
= ReferenceFactory
.newBook();
797 reference
.setInBook(book
);
798 book
.setTitle(pubName
);
799 book
.setVolume(volume
);
800 book
.setEdition(edition
);
802 if (state
.getConfig().isUseEditorAsInAuthorWhereNeeded()){
803 TeamOrPersonBase
<?
> inAuthor
= createAuthor(editors
);
804 book
.setAuthorship(inAuthor
);
807 } else if (refType
== RefType
.Book
){
809 reference
= ReferenceFactory
.newBook();
810 reference
.setTitle(pubName
);
811 reference
.setVolume(volume
);
812 reference
.setEdition(edition
);
813 }else if (refType
== RefType
.Generic
){
814 //Generic - undefinable
815 // String message = "Can't define the type of the reference. Use generic instead";
816 // fireWarningEvent(message, parentEvent, 4);
817 reference
= ReferenceFactory
.newGeneric();
818 reference
.setTitle(pubName
);
819 reference
.setEdition(edition
);
821 //volume indicates an in-reference
822 if (isNotBlank(volume
)){
823 Reference partOf
= ReferenceFactory
.newGeneric();
824 partOf
.setVolume(volume
);
825 partOf
.setInReference(reference
);
828 }else if (refType
== RefType
.LatestUsed
){
829 Reference latestReference
= state
.getLatestReferenceInHomotype();
830 if (latestReference
== null){
831 String message
= "No former reference available for incomplete citation";
832 fireWarningEvent(message
, parentEvent
, 6);
833 reference
= ReferenceFactory
.newGeneric();
835 if (latestReference
.getInReference() != null){
836 reference
= (Reference
)latestReference
.clone();
838 String message
= "Latest reference is not an in-reference. This is not yet handled.";
839 fireWarningEvent(message
, parentEvent
, 6);
840 reference
= ReferenceFactory
.newGeneric();
843 reference
.setVolume(volume
);
844 if (isNotBlank(edition
)){
845 String message
= "Edition not yet handled for incomplete citations";
846 fireWarningEvent(message
, parentEvent
, 4);
850 String message
= "Unhandled reference type: %s" ;
851 fireWarningEvent(String
.format(message
, refType
.toString()), parentEvent
, 8);
852 reference
= ReferenceFactory
.newGeneric();
856 TeamOrPersonBase
<?
> author
;
857 if (isBlank(authorStr
)){
858 if (refType
!= RefType
.LatestUsed
){
859 author
= state
.getLatestAuthorInHomotype();
860 reference
.setAuthorship(author
);
863 author
= createAuthor(authorStr
);
864 state
.setLatestAuthorInHomotype(author
);
865 reference
.setAuthorship(author
);
870 handleTitlesInCitation(titleStr
, titleCache
, parentEvent
, reference
);
873 handleEditorsInCitation(edition
, editors
, reference
, parentEvent
);
876 handlePages(state
, refMap
, parentEvent
, reference
, pages
);
878 //remember reference for following citation
879 state
.setLatestReferenceInHomotype(reference
);
884 private void handleEditorsInCitation(String edition
, String editors
, Reference reference
, XMLEvent parentEvent
) {
886 reference
.setEditor(editors
);
887 if ( editors
!= null){
888 String message
= "Citation reference has an editor. This is unusual for a citation reference (appears regularly in <reference> references";
889 fireWarningEvent(message
, parentEvent
, 4);
893 private void handleTitlesInCitation(String titleStr
, String titleCache
,
894 XMLEvent parentEvent
, Reference reference
) {
895 if (isNotBlank(titleStr
)){
896 reference
.setTitle(titleStr
);
899 if (StringUtils
.isNotBlank(titleCache
)) {
900 reference
.setTitleCache(titleCache
, true);
902 if (titleStr
!= null || titleCache
!= null){
903 String message
= "Citation reference has a title or a full title. Both is unusual for a citation reference (appears regularly in <reference> references";
904 fireWarningEvent(message
, parentEvent
, 4);
908 private enum RefType
{
916 private RefType
defineRefTypeForCitation(String type
, String volume
, String editors
,
917 String authorStr
, String pubName
, XMLEvent parentEvent
) {
918 if ("journal".equalsIgnoreCase(type
)){
919 return RefType
.Article
;
921 if (editors
== null){
923 if (pubName
== null){
924 //looks like we need to use reference info from former citations here
925 return RefType
.LatestUsed
;
926 }else if (volume
== null){
927 return RefType
.Book
; //Book must not have in-authors
929 return RefType
.Generic
;
934 if (pubName
!= null){
935 return RefType
.BookSection
;
937 String message
= "Unexpected state: Citation has editors but no pubName";
938 fireWarningEvent(message
, parentEvent
, 4);
939 return RefType
.Generic
;
946 private boolean isArticle(String type
, String volume
, String editors
) {
947 if ("journal".equalsIgnoreCase(type
)){
949 }else if (volume
!= null && editors
== null){
961 private Reference
handleNonCitationSpecific(String type
, String authorStr
,
962 String titleStr
, String titleCache
, String volume
, String edition
,
963 String editors
, String pubName
, String appendix
) {
967 if (isNotBlank(appendix
)){
968 pubName
= pubName
== null ? appendix
: (pubName
+ " " + appendix
).replaceAll(" ", " ");
971 if (isArticle(type
, volume
, editors
)) {
972 IArticle article
= ReferenceFactory
.newArticle();
973 if (pubName
!= null) {
974 IJournal journal
= ReferenceFactory
.newJournal();
975 journal
.setTitle(pubName
);
976 article
.setInJournal(journal
);
978 reference
= (Reference
) article
;
981 Reference bookOrPartOf
= ReferenceFactory
.newGeneric();
982 reference
= bookOrPartOf
;
986 TeamOrPersonBase
<?
> author
= createAuthor(authorStr
);
987 reference
.setAuthorship(author
);
990 reference
.setTitle(titleStr
);
991 if (StringUtils
.isNotBlank(titleCache
)) {
992 reference
.setTitleCache(titleCache
, true);
996 reference
.setEdition(edition
);
997 reference
.setEditor(editors
);
1000 if (pubName
!= null) {
1001 Reference inReference
;
1002 if (reference
.getType().equals(ReferenceType
.Article
)) {
1003 inReference
= ReferenceFactory
.newJournal();
1005 inReference
= ReferenceFactory
.newGeneric();
1007 inReference
.setTitle(pubName
);
1008 reference
.setInReference(inReference
);
1012 reference
.setVolume(volume
);
1016 private void handlePages(MarkupImportState state
,
1017 Map
<String
, String
> refMap
, XMLEvent parentEvent
,
1018 Reference reference
, String pages
) {
1019 // TODO check if this is handled correctly in FM markup
1020 boolean switchPages
= state
.getConfig().isHandlePagesAsDetailWhereNeeded();
1022 if (pages
!= null ){
1023 String detail
= refMap
.get(DETAILS
);
1024 if (isBlank(detail
)){
1025 if (pages
.contains("-")){
1026 String message
= "There is a pages tag with '-'. Unclear if this really means pages";
1027 fireWarningEvent(message
, parentEvent
, 8);
1028 reference
.setPages(pages
);
1030 //handle pages as detail, this is at least true for Flora Malesiana
1031 refMap
.put(DETAILS
, pages
);
1034 if (! pages
.contains("-")){
1035 String message
= "There are pages and detail available where pages may also hold details information.";
1036 fireWarningEvent(message
, parentEvent
, 8);
1038 reference
.setPages(pages
);
1044 public Reference
handleReference(MarkupImportState state
,
1045 XMLEventReader reader
, XMLEvent parentEvent
)
1046 throws XMLStreamException
{
1047 checkNoAttributes(parentEvent
);
1049 boolean hasRefPart
= false;
1050 Map
<String
, String
> refMap
= new HashMap
<String
, String
>();
1051 while (reader
.hasNext()) {
1052 XMLEvent next
= readNoWhitespace(reader
);
1053 if (isMyEndingElement(next
, parentEvent
)) {
1054 checkMandatoryElement(hasRefPart
, parentEvent
.asStartElement(), REF_PART
);
1055 Reference reference
= createReference(state
, refMap
, next
);
1057 } else if (isStartingElement(next
, REF_PART
)) {
1058 handleRefPart(state
, reader
, next
, refMap
);
1061 handleUnexpectedElement(next
);
1064 // TODO handle missing end element
1065 throw new IllegalStateException("<Reference> has no closing tag");