2 * Copyright (C) 2007 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
.Arrays
;
13 import java
.util
.Collection
;
14 import java
.util
.HashMap
;
15 import java
.util
.Iterator
;
16 import java
.util
.List
;
18 import java
.util
.Stack
;
19 import java
.util
.UUID
;
21 import javax
.xml
.namespace
.QName
;
22 import javax
.xml
.stream
.Location
;
23 import javax
.xml
.stream
.XMLEventReader
;
24 import javax
.xml
.stream
.XMLStreamConstants
;
25 import javax
.xml
.stream
.XMLStreamException
;
26 import javax
.xml
.stream
.events
.Attribute
;
27 import javax
.xml
.stream
.events
.EndElement
;
28 import javax
.xml
.stream
.events
.StartElement
;
29 import javax
.xml
.stream
.events
.XMLEvent
;
31 import org
.apache
.commons
.lang
.StringUtils
;
32 import org
.apache
.commons
.lang
.WordUtils
;
33 import org
.apache
.log4j
.Logger
;
35 import eu
.etaxonomy
.cdm
.api
.service
.IClassificationService
;
36 import eu
.etaxonomy
.cdm
.api
.service
.ITermService
;
37 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
38 import eu
.etaxonomy
.cdm
.ext
.geo
.GeoServiceArea
;
39 import eu
.etaxonomy
.cdm
.ext
.geo
.IEditGeoService
;
40 import eu
.etaxonomy
.cdm
.io
.common
.CdmImportBase
;
41 import eu
.etaxonomy
.cdm
.io
.common
.CdmImportBase
.TermMatchMode
;
42 import eu
.etaxonomy
.cdm
.io
.common
.events
.IIoEvent
;
43 import eu
.etaxonomy
.cdm
.io
.common
.events
.IoProblemEvent
;
44 import eu
.etaxonomy
.cdm
.io
.common
.mapping
.UndefinedTransformerMethodException
;
45 import eu
.etaxonomy
.cdm
.model
.agent
.Team
;
46 import eu
.etaxonomy
.cdm
.model
.agent
.TeamOrPersonBase
;
47 import eu
.etaxonomy
.cdm
.model
.common
.AnnotationType
;
48 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
49 import eu
.etaxonomy
.cdm
.model
.common
.DefinedTermBase
;
50 import eu
.etaxonomy
.cdm
.model
.common
.ExtensionType
;
51 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
52 import eu
.etaxonomy
.cdm
.model
.common
.TermVocabulary
;
53 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
54 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
55 import eu
.etaxonomy
.cdm
.model
.description
.PolytomousKey
;
56 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
57 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
58 import eu
.etaxonomy
.cdm
.model
.location
.NamedAreaLevel
;
59 import eu
.etaxonomy
.cdm
.model
.location
.NamedAreaType
;
60 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
61 import eu
.etaxonomy
.cdm
.model
.name
.NomenclaturalCode
;
62 import eu
.etaxonomy
.cdm
.model
.name
.NonViralName
;
63 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
64 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
65 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
66 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
67 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
68 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
69 import eu
.etaxonomy
.cdm
.strategy
.exceptions
.UnknownCdmTypeException
;
75 public abstract class MarkupImportBase
{
76 @SuppressWarnings("unused")
77 private static final Logger logger
= Logger
.getLogger(MarkupImportBase
.class);
80 protected static final String ALTITUDE
= "altitude";
81 protected static final String ANNOTATION
= "annotation";
82 protected static final String BOLD
= "bold";
83 protected static final String BR
= "br";
84 protected static final String CITATION
= "citation";
85 protected static final String CLASS
= "class";
86 protected static final String COORDINATES
= "coordinates";
87 protected static final String DATES
= "dates";
88 protected static final String GATHERING
= "gathering";
89 protected static final String GENUS_ABBREVIATION
= "genus abbreviation";
90 protected static final String FOOTNOTE
= "footnote";
91 protected static final String FOOTNOTE_REF
= "footnoteRef";
92 protected static final String FULL_NAME
= "fullName";
93 protected static final String ITALICS
= "italics";
94 protected static final String NUM
= "num";
95 protected static final String NOTES
= "notes";
96 protected static final String PUBLICATION
= "publication";
97 protected static final String SPECIMEN_TYPE
= "specimenType";
98 protected static final String STATUS
= "status";
99 protected static final String SUB_HEADING
= "subHeading";
100 protected static final String TYPE
= "type";
101 protected static final String TYPE_STATUS
= "typeStatus";
103 protected static final boolean CREATE_NEW
= true;
104 protected static final boolean NO_IMAGE_GALLERY
= false;
105 protected static final boolean IMAGE_GALLERY
= true;
107 protected static final String ADDENDA
= "addenda";
108 protected static final String BIBLIOGRAPHY
= "bibliography";
109 protected static final String BIOGRAPHIES
= "biographies";
110 protected static final String CHAR
= "char";
111 protected static final String DEDICATION
= "dedication";
112 protected static final String DEFAULT_MEDIA_URL
= "defaultMediaUrl";
113 protected static final String DISTRIBUTION_LIST
= "distributionList";
114 protected static final String DISTRIBUTION_LOCALITY
= "distributionLocality";
115 protected static final String FEATURE
= "feature";
116 protected static final String FIGURE
= "figure";
117 protected static final String FIGURE_LEGEND
= "figureLegend";
118 protected static final String FIGURE_PART
= "figurePart";
119 protected static final String FIGURE_REF
= "figureRef";
120 protected static final String FIGURE_TITLE
= "figureTitle";
121 protected static final String FOOTNOTE_STRING
= "footnoteString";
122 protected static final String FREQUENCY
= "frequency";
123 protected static final String HEADING
= "heading";
124 protected static final String HABITAT
= "habitat";
125 protected static final String HABITAT_LIST
= "habitatList";
126 protected static final String IS_FREETEXT
= "isFreetext";
127 protected static final String ID
= "id";
128 protected static final String KEY
= "key";
129 protected static final String LIFE_CYCLE_PERIODS
= "lifeCyclePeriods";
130 protected static final String META_DATA
= "metaData";
131 protected static final String MODS
= "mods";
133 protected static final String NOMENCLATURE
= "nomenclature";
134 protected static final String QUOTE
= "quote";
135 protected static final String RANK
= "rank";
136 protected static final String REF
= "ref";
137 protected static final String REF_NUM
= "refNum";
138 protected static final String REFERENCE
= "reference";
139 protected static final String REFERENCES
= "references";
140 protected static final String SUB_CHAR
= "subChar";
141 protected static final String TAXON
= "taxon";
142 protected static final String TAXONTITLE
= "taxontitle";
143 protected static final String TAXONTYPE
= "taxontype";
144 protected static final String TEXT_SECTION
= "textSection";
145 protected static final String TREATMENT
= "treatment";
146 protected static final String SERIALS_ABBREVIATIONS
= "serialsAbbreviations";
147 protected static final String STRING
= "string";
148 protected static final String URL
= "url";
149 protected static final String VERNACULAR_NAMES
= "vernacularNames";
150 protected static final String WRITER
= "writer";
154 protected static final String ACCEPTED
= "accepted";
155 protected static final String ACCEPTED_NAME
= "acceptedName";
156 protected static final String ALTERNATEPUBTITLE
= "alternatepubtitle";
157 protected static final String AUTHOR
= "author";
158 protected static final String DETAILS
= "details";
159 protected static final String EDITION
= "edition";
160 protected static final String EDITORS
= "editors";
161 protected static final String HOMONYM
= "homonym";
162 protected static final String HOMOTYPES
= "homotypes";
163 protected static final String INFRANK
= "infrank";
164 protected static final String INFRAUT
= "infraut";
165 protected static final String INFRPARAUT
= "infrparaut";
166 protected static final String ISSUE
= "issue";
167 protected static final String NAME
= "name";
168 protected static final String NAME_TYPE
= "nameType";
169 protected static final String NOM
= "nom";
170 protected static final String PAGES
= "pages";
171 protected static final String PARAUT
= "paraut";
172 protected static final String PUBFULLNAME
= "pubfullname";
173 protected static final String PUBNAME
= "pubname";
174 protected static final String PUBTITLE
= "pubtitle";
175 protected static final String PUBTYPE
= "pubtype";
176 protected static final String REF_PART
= "refPart";
177 protected static final String SYNONYM
= "synonym";
178 protected static final String USAGE
= "usage";
179 protected static final String VOLUME
= "volume";
180 protected static final String YEAR
= "year";
184 protected static final String COUPLET
= "couplet";
185 protected static final String IS_SPOTCHARACTERS
= "isSpotcharacters";
186 protected static final String ONLY_NUMBERED_TAXA_EXIST
= "onlyNumberedTaxaExist";
187 protected static final String EXISTS
= "exists";
188 protected static final String KEYNOTES
= "keynotes";
189 protected static final String KEY_TITLE
= "keyTitle";
190 protected static final String QUESTION
= "question";
191 protected static final String TEXT
= "text";
192 protected static final String TO_COUPLET
= "toCouplet";
193 protected static final String TO_KEY
= "toKey";
194 protected static final String TO_TAXON
= "toTaxon";
198 protected MarkupDocumentImport docImport
;
199 private IEditGeoService editGeoService
;
201 public MarkupImportBase(MarkupDocumentImport docImport
) {
203 this.docImport
= docImport
;
204 this.editGeoService
= docImport
.getEditGeoService();
207 private Stack
<QName
> unhandledElements
= new Stack
<QName
>();
208 private Stack
<QName
> handledElements
= new Stack
<QName
>();
211 protected <T
extends CdmBase
> void save(Collection
<T
> collection
, MarkupImportState state
) {
212 if (state
.isCheck() || collection
.isEmpty()){
215 T example
= collection
.iterator().next();
216 if (example
.isInstanceOf(TaxonBase
.class)){
217 Collection
<TaxonBase
> typedCollection
= (Collection
<TaxonBase
>)collection
;
218 docImport
.getTaxonService().saveOrUpdate(typedCollection
);
219 }else if (example
.isInstanceOf(Classification
.class)){
220 Collection
<Classification
> typedCollection
= (Collection
<Classification
>)collection
;
221 docImport
.getClassificationService().saveOrUpdate(typedCollection
);
222 }else if (example
.isInstanceOf(PolytomousKey
.class)){
223 Collection
<PolytomousKey
> typedCollection
= (Collection
<PolytomousKey
>)collection
;
224 docImport
.getPolytomousKeyService().saveOrUpdate(typedCollection
);
225 }else if (example
.isInstanceOf(DefinedTermBase
.class)){
226 Collection
<DefinedTermBase
> typedCollection
= (Collection
<DefinedTermBase
>)collection
;
227 getTermService().saveOrUpdate(typedCollection
);
234 //TODO move to service layer for all IdentifiableEntities
235 protected void save(CdmBase cdmBase
, MarkupImportState state
) {
236 if (state
.isCheck()){
239 cdmBase
= CdmBase
.deproxy(cdmBase
, CdmBase
.class);
240 if (cdmBase
== null){
241 String message
= "Tried to save a null object.";
242 fireWarningEvent(message
, "--location ?? --", 6,1);
243 } else if (cdmBase
.isInstanceOf(TaxonBase
.class)){
244 docImport
.getTaxonService().saveOrUpdate((TaxonBase
<?
>)cdmBase
);
245 }else if (cdmBase
.isInstanceOf(Classification
.class)){
246 docImport
.getClassificationService().saveOrUpdate((Classification
)cdmBase
);
247 }else if (cdmBase
.isInstanceOf(PolytomousKey
.class)){
248 docImport
.getPolytomousKeyService().saveOrUpdate((PolytomousKey
)cdmBase
);
249 }else if (cdmBase
.isInstanceOf(DefinedTermBase
.class)){
250 docImport
.getTermService().saveOrUpdate((DefinedTermBase
<?
>)cdmBase
);
251 }else if (cdmBase
.isInstanceOf(Media
.class)){
252 docImport
.getMediaService().saveOrUpdate((Media
)cdmBase
);
253 }else if (cdmBase
.isInstanceOf(SpecimenOrObservationBase
.class)){
254 docImport
.getOccurrenceService().saveOrUpdate((SpecimenOrObservationBase
<?
>)cdmBase
);
255 }else if (cdmBase
.isInstanceOf(DescriptionElementBase
.class)){
256 docImport
.getDescriptionService().saveDescriptionElement((DescriptionElementBase
)cdmBase
);
258 String message
= "Unknown cdmBase type to save: " + cdmBase
.getClass();
259 fireWarningEvent(message
, "Unknown location", 8);
261 //logger.warn("Saved " + cdmBase);
265 protected ITermService
getTermService() {
266 return docImport
.getTermService();
269 protected IClassificationService
getClassificationService() {
270 return docImport
.getClassificationService();
273 //*********************** Attribute methods *************************************/
276 * Returns a map for all attributes of an start element
280 protected Map
<String
, Attribute
> getAttributes(XMLEvent event
) {
281 Map
<String
, Attribute
> result
= new HashMap
<String
, Attribute
>();
282 if (!event
.isStartElement()){
283 fireWarningEvent("Event is not an startElement. Can't check attributes", makeLocationStr(event
.getLocation()), 1, 1);
286 StartElement element
= event
.asStartElement();
287 Iterator
<Attribute
> attributes
= element
.getAttributes();
288 while (attributes
.hasNext()){
289 Attribute attribute
= attributes
.next();
291 result
.put(attribute
.getName().getLocalPart(), attribute
);
297 * Throws an unexpected attributes event if the event has any attributes.
300 protected void checkNoAttributes(Map
<String
, Attribute
> attributes
, XMLEvent event
) {
301 String
[] exceptions
= new String
[]{};
302 handleUnexpectedAttributes(event
.getLocation(), attributes
, 1, exceptions
);
308 * Throws an unexpected attributes event if the event has any attributes.
311 protected void checkNoAttributes(XMLEvent event
) {
312 String
[] exceptions
= new String
[]{};
313 checkNoAttributes(event
, 1, exceptions
);
317 * Throws an unexpected attributes event if the event has any attributes except those mentioned in "exceptions".
321 protected void checkNoAttributes(XMLEvent event
, int stackDepth
, String
... exceptions
) {
322 if (! event
.isStartElement()){
323 fireWarningEvent("Event is not an startElement. Can't check attributes", makeLocationStr(event
.getLocation()), 1, 1);
326 StartElement startElement
= event
.asStartElement();
327 Map
<String
, Attribute
> attributes
= getAttributes(startElement
);
328 handleUnexpectedAttributes(startElement
.getLocation(), attributes
, stackDepth
+1, exceptions
);
333 * Checks if the given attribute exists and has the given value.
334 * If yes, true is returned and the attribute is removed from the attributes map.
335 * Otherwise false is returned.
339 * @return <code>true</code> if attribute has given value, <code>false</code> otherwise
341 protected boolean checkAndRemoveAttributeValue( Map
<String
, Attribute
> attributes
, String attrName
, String value
) {
342 Attribute attr
= attributes
.get(attrName
);
343 if (attr
== null ||value
== null ){
346 if (value
.equals(attr
.getValue())){
347 attributes
.remove(attrName
);
357 * Returns the value of a given attribute name and removes the attribute from the attributes map.
362 protected String
getAndRemoveAttributeValue(Map
<String
, Attribute
> attributes
, String attrName
) {
363 return getAndRemoveAttributeValue(null, attributes
, attrName
, false, 1);
367 * Returns the value of a boolean attribute with the given name and removes the attribute from the attributes map.
368 * Returns <code>defaultValue</code> if the attribute does not exist. ALso returns <code>defaultValue</code> and throws a warning if the
369 * attribute has no boolean value (true, false).
371 * @param attributes the
372 * @param attrName the name of the attribute
373 * @param defaultValue the default value to return if attribute does not exist or can not be defined
376 protected Boolean
getAndRemoveBooleanAttributeValue(XMLEvent event
, Map
<String
, Attribute
> attributes
, String attrName
, Boolean defaultValue
) {
377 String value
= getAndRemoveAttributeValue(null, attributes
, attrName
, false, 1);
378 Boolean result
= defaultValue
;
380 if (value
.equalsIgnoreCase("true")){
382 }else if (value
.equalsIgnoreCase("false")){
385 String message
= "Boolean attribute has no boolean value ('true', 'false') but '%s'";
386 fireWarningEvent(String
.format(message
, value
), makeLocationStr(event
.getLocation()), 6, 1);
394 * Returns the value of a given attribute name and returns the attribute from the attributes map.
395 * Fires a mandatory field is missing event if the attribute does not exist.
401 protected String
getAndRemoveRequiredAttributeValue(XMLEvent xmlEvent
, Map
<String
, Attribute
> attributes
, String attrName
) {
402 return getAndRemoveAttributeValue(xmlEvent
, attributes
, attrName
, true, 1);
406 * Returns the value of a given attribute name and returns the attribute from the attributes map.
407 * If required is <code>true</code> and the attribute does not exist a mandatory field is missing event is fired.
414 private String
getAndRemoveAttributeValue(XMLEvent xmlEvent
, Map
<String
, Attribute
> attributes
, String attrName
, boolean isRequired
, int stackDepth
) {
415 Attribute attr
= attributes
.get(attrName
);
418 fireMandatoryElementIsMissing(xmlEvent
, attrName
, 8, stackDepth
+1);
422 attributes
.remove(attrName
);
423 return attr
.getValue();
428 * Fires an not yet implemented event if the given attribute exists in attributes.
432 protected void handleNotYetImplementedAttribute(Map
<String
, Attribute
> attributes
, String attrName
) {
433 Attribute attr
= attributes
.get(attrName
);
435 attributes
.remove(attrName
);
436 QName qName
= attr
.getName();
437 fireNotYetImplementedAttribute(attr
.getLocation(), qName
, 1);
442 * Fires an unhandled attributes event, if attributes exist in attributes map not covered by the exceptions.
443 * No event is fired if the unhandled elements stack is not empty.
448 protected void handleUnexpectedAttributes(Location location
,Map
<String
, Attribute
> attributes
, String
... exceptions
) {
449 handleUnexpectedAttributes(location
, attributes
, 1, exceptions
);
453 * see {@link #handleUnexpectedAttributes(Location, Map, String...)}
457 * @param stackDepth the stack trace depth
460 private void handleUnexpectedAttributes(Location location
,Map
<String
, Attribute
> attributes
, int stackDepth
, String
... exceptions
) {
461 if (attributes
.size() > 0){
462 if (this.unhandledElements
.size() == 0 ){
463 boolean hasUnhandledAttributes
= false;
464 for (String key
: attributes
.keySet()){
465 boolean isException
= false;
466 for (String exception
: exceptions
){
467 if(key
.equals(exception
)){
472 hasUnhandledAttributes
= true;
475 if (hasUnhandledAttributes
){
476 fireUnexpectedAttributes(location
, attributes
, stackDepth
+1);
483 private void fireUnexpectedAttributes(Location location
, Map
<String
, Attribute
> attributes
, int stackDepth
) {
484 String attributesString
= "";
485 for (String key
: attributes
.keySet()){
486 Attribute attribute
= attributes
.get(key
);
487 attributesString
= CdmUtils
.concat(",", attributesString
, attribute
.getName().getLocalPart() + ":" + attribute
.getValue());
489 String message
= "Unexpected attributes: %s";
490 IoProblemEvent event
= makeProblemEvent(location
, String
.format(message
, attributesString
), 1 , stackDepth
+1 );
495 protected void fireUnexpectedAttributeValue(XMLEvent parentEvent
, String attrName
, String attrValue
) {
496 String message
= "Unexpected attribute value %s='%s'";
497 message
= String
.format(message
, attrName
, attrValue
);
498 IoProblemEvent event
= makeProblemEvent(parentEvent
.getLocation(), message
, 1 , 1 );
502 protected void handleNotYetImplementedAttributeValue(XMLEvent xmlEvent
, String attrName
, String attrValue
) {
503 String message
= "Attribute %s not yet implemented for value '%s'";
504 message
= String
.format(message
, attrName
, attrValue
);
505 IIoEvent event
= makeProblemEvent(xmlEvent
.getLocation(), message
, 1, 1 );
509 protected void fireNotYetImplementedAttribute(Location location
, QName qName
, int stackDepth
) {
510 String message
= "Attribute not yet implemented: %s";
511 IIoEvent event
= makeProblemEvent(location
, String
.format(message
, qName
.getLocalPart()), 1, stackDepth
+1 );
518 protected void fireUnexpectedEvent(XMLEvent xmlEvent
, int stackDepth
) {
519 Location location
= xmlEvent
.getLocation();
520 String message
= "Unexpected event: %s";
521 IIoEvent event
= makeProblemEvent(location
, String
.format(message
, xmlEvent
.toString()), 2, stackDepth
+1);
525 protected void fireUnexpectedStartElement(Location location
, StartElement startElement
, int stackDepth
) {
526 QName qName
= startElement
.getName();
527 String message
= "Unexpected start element: %s";
528 IIoEvent event
= makeProblemEvent(location
, String
.format(message
, qName
.getLocalPart()), 2, stackDepth
+1);
533 protected void fireUnexpectedEndElement(Location location
, EndElement endElement
, int stackDepth
) {
534 QName qName
= endElement
.getName();
535 String message
= "Unexpected end element: %s";
536 IIoEvent event
= makeProblemEvent(location
, String
.format(message
, qName
.getLocalPart()), 16, stackDepth
+1);
541 protected void fireNotYetImplementedElement(Location location
, QName qName
, int stackDepth
) {
542 String message
= "Element not yet implemented: %s";
543 IIoEvent event
= makeProblemEvent(location
, String
.format(message
, qName
.getLocalPart()), 1, stackDepth
+1 );
548 * Creates a problem event.
549 * Be aware of the right depths of the stack trace !
555 private IoProblemEvent
makeProblemEvent(Location location
, String message
, int severity
, int stackDepth
) {
557 StackTraceElement
[] stackTrace
= new Exception().getStackTrace();
558 int lineNumber
= stackTrace
[stackDepth
].getLineNumber();
559 String methodName
= stackTrace
[stackDepth
].getMethodName();
560 String locationStr
= makeLocationStr(location
);
561 IoProblemEvent event
= IoProblemEvent
.NewInstance(this.getClass(), message
,
562 locationStr
, lineNumber
, severity
, methodName
);
567 * Creates a string from a location
571 protected String
makeLocationStr(Location location
) {
572 String locationStr
= location
== null ?
" - no location - " : "l." + location
.getLineNumber() + "/c."+ location
.getColumnNumber();
578 * Fires an unexpected element event if the unhandled elements stack is empty.
579 * Otherwise adds the element to the stack.
582 protected void handleUnexpectedStartElement(XMLEvent event
) {
583 handleUnexpectedStartElement(event
, 1);
587 * Fires an unexpected element event if the unhandled elements stack is empty.
588 * Otherwise adds the element to the stack.
591 protected void handleUnexpectedStartElement(XMLEvent event
, int stackDepth
) {
592 QName qName
= event
.asStartElement().getName();
593 if (! unhandledElements
.empty()){
594 unhandledElements
.push(qName
);
596 fireUnexpectedStartElement(event
.getLocation(), event
.asStartElement(), stackDepth
+ 1);
601 protected void handleUnexpectedEndElement(EndElement event
) {
602 handleUnexpectedEndElement(event
, 1);
606 * Fires an unexpected element event if the event is not the last on the stack.
607 * Otherwise removes last stack element.
610 protected void handleUnexpectedEndElement(EndElement event
, int stackDepth
) {
611 QName qName
= event
.asEndElement().getName();
612 if (!unhandledElements
.isEmpty() && unhandledElements
.peek().equals(qName
)){
613 unhandledElements
.pop();
615 fireUnexpectedEndElement(event
.getLocation(), event
.asEndElement(), stackDepth
+ 1);
623 protected void popUnimplemented(EndElement endElement
) {
624 QName qName
= endElement
.asEndElement().getName();
625 if (unhandledElements
.peek().equals(qName
)){
626 unhandledElements
.pop();
628 String message
= "End element is not last on stack: %s";
629 message
= String
.format(message
, qName
.getLocalPart());
630 IIoEvent event
= makeProblemEvent(endElement
.getLocation(), message
, 16, 1);
638 * Fires an unexpected element event if the unhandled element stack is empty.
641 protected void handleUnexpectedElement(XMLEvent event
) {
642 if (event
.isStartElement()){
643 handleUnexpectedStartElement(event
);
644 }else if (event
.isEndElement()){
645 handleUnexpectedEndElement(event
.asEndElement());
646 }else if (event
.getEventType() == XMLStreamConstants
.COMMENT
){
648 }else if (! unhandledElements
.empty()){
651 fireUnexpectedEvent(event
, 1);
656 * Fires an not yet implemented event and adds the element name to the unhandled elements stack.
659 protected void handleNotYetImplementedElement(XMLEvent event
) {
660 QName qName
= event
.asStartElement().getName();
661 boolean isTopLevel
= unhandledElements
.isEmpty();
662 unhandledElements
.push(qName
);
664 fireNotYetImplementedElement(event
.getLocation(), qName
, 1);
669 * Checks if a mandatory text is not empty or null.
670 * Returns true if text is given.
671 * Fires an mandatory element is missing event otherwise and returns <code>null</code>.
676 protected boolean checkMandatoryText(String text
, XMLEvent parentEvent
) {
677 if (! StringUtils
.isNotBlank(text
)){
678 fireMandatoryElementIsMissing(parentEvent
, "CData", 4, 1);
685 * Fires an mandatory element is missing event if exists is <code>false</code>.
686 * @param hasMandatory
690 protected void checkMandatoryElement(boolean exists
, StartElement parentEvent
, String attrName
) {
692 fireMandatoryElementIsMissing(parentEvent
, attrName
, 5, 1);
698 * Fires an element is missing event.
703 * @throws IllegalStateException if xmlEvent is not a StartElement and not an Attribute
705 private void fireMandatoryElementIsMissing(XMLEvent xmlEvent
, String missingEventName
, int severity
, int stackDepth
) throws IllegalStateException
{
706 Location location
= xmlEvent
.getLocation();
709 if (xmlEvent
.isAttribute()){
710 Attribute attribute
= ((Attribute
)xmlEvent
);
711 typeName
= "attribute";
712 qName
= attribute
.getName();
713 }else if (xmlEvent
.isStartElement()){
714 typeName
= "element";
715 qName
= xmlEvent
.asStartElement().getName();
717 throw new IllegalStateException("mandatory element only allowed for attributes and start tags in " + makeLocationStr(location
));
719 String message
= "Mandatory %s '%s' is missing in %s";
720 message
= String
.format(message
, typeName
, missingEventName
, qName
.getLocalPart());
721 IIoEvent event
= makeProblemEvent(location
, message
, severity
, stackDepth
+1);
729 * Returns true if the "next" event is the ending tag for the "parent" event.
730 * @param next end element to test, must not be null
731 * @param parentEvent start element to test
732 * @return true if the "next" event is the ending tag for the "parent" event.
733 * @throws XMLStreamException
735 protected boolean isMyEndingElement(XMLEvent next
, XMLEvent parentEvent
) throws XMLStreamException
{
736 if (! parentEvent
.isStartElement()){
737 String message
= "Parent event should be start tag";
738 fireWarningEvent(message
, makeLocationStr(next
.getLocation()), 6);
741 return isEndingElement(next
, parentEvent
.asStartElement().getName().getLocalPart());
745 * Trims the text and removes turns all whitespaces into single empty space.
749 protected String
normalize(String text
) {
750 text
= StringUtils
.trimToEmpty(text
);
751 text
= text
.replaceAll("\\s+", " ");
758 * Removes whitespaces at beginning and end and makes the first letter
759 * a capital letter and all other letters small letters.
763 protected String
toFirstCapital(String value
) {
764 if (StringUtils
.isBlank(value
)){
768 value
= value
.trim();
769 result
+= value
.trim().substring(0,1).toUpperCase();
770 if (value
.length()>1){
771 result
+= value
.substring(1).toLowerCase();
778 * Currently not used.
780 * @param allowedNumberOfCharacters
781 * @param onlyFirstCapital
784 protected boolean isAbbreviation(String str
, int allowedNumberOfCharacters
, boolean onlyFirstCapital
){
789 if (! str
.endsWith(".")){
792 str
= str
.substring(0, str
.length() -1);
793 if (str
.length() > allowedNumberOfCharacters
){
796 final String re
= "^\\p{javaUpperCase}\\p{javaLowerCase}*$";
797 if (str
.matches(re
)){
805 * Checks if <code>abbrev</code> is the short form for the genus name (strGenusName).
806 * Usually this is the case if <code>abbrev</code> is the first letter (optional with ".")
807 * of strGenusName. But in older floras it may also be the first 2 or 3 letters (optional with dot).
808 * However, we allow only a maximum of 2 letters to be anambigous. In cases with 3 letters better
809 * change the original markup data.
811 * @param strGenusName
814 protected boolean isGenusAbbrev(String abbrev
, String strGenusName
) {
815 if (! abbrev
.matches("[A-Z][a-z]?\\.?")) {
817 }else if (abbrev
.length() == 0 || strGenusName
== null || strGenusName
.length() == 0){
820 abbrev
= abbrev
.replace(".", "");
821 return strGenusName
.startsWith(abbrev
);
822 // boolean result = true;
823 // for (int i = 0 ; i < abbrev.length(); i++){
824 // result &= ( abbrev.charAt(i) == strGenusName.charAt(i));
832 * Checks if all words in the given string start with a capital letter but do not have any further capital letter.
833 * @param word the string to be checekd. Usually should be a single word.
834 * @return true if the above is the case, false otherwise
836 protected boolean isFirstCapitalWord(String word
) {
837 if (WordUtils
.capitalizeFully(word
).equals(word
)){
839 }else if (WordUtils
.capitalizeFully(word
,new char[]{'-'}).equals(word
)){
840 //for words like Le-Testui (which is a species epithet)
849 * Read next event. Ignore whitespace events.
852 * @throws XMLStreamException
854 protected XMLEvent
readNoWhitespace(XMLEventReader reader
) throws XMLStreamException
{
855 XMLEvent event
= reader
.nextEvent();
856 while (event
.isCharacters() && event
.asCharacters().isWhiteSpace()){
857 event
= reader
.nextEvent();
863 * Returns the REQUIRED "class" attribute for a given event and checks that it is the only attribute.
867 protected String
getClassOnlyAttribute(XMLEvent parentEvent
) {
868 return getClassOnlyAttribute(parentEvent
, true);
873 * Returns the "class" attribute for a given event and checks that it is the only attribute.
877 protected String
getClassOnlyAttribute(XMLEvent parentEvent
, boolean required
) {
878 return getOnlyAttribute(parentEvent
, CLASS
, required
);
882 * Returns the value for the only attribute for a given event and checks that it is the only attribute.
886 protected String
getOnlyAttribute(XMLEvent parentEvent
, String attrName
, boolean required
) {
887 Map
<String
, Attribute
> attributes
= getAttributes(parentEvent
);
888 String classValue
=getAndRemoveAttributeValue(parentEvent
, attributes
, attrName
, required
, 1);
889 checkNoAttributes(attributes
, parentEvent
);
894 protected void fireWarningEvent(String message
, String locationStr
, Integer severity
, Integer depth
) {
895 docImport
.fireWarningEvent(message
, locationStr
, severity
, depth
);
898 protected void fireWarningEvent(String message
, XMLEvent event
, Integer severity
) {
899 docImport
.fireWarningEvent(message
, makeLocationStr(event
.getLocation()), severity
, 1);
902 protected void fireSchemaConflictEventExpectedStartTag(String elName
, XMLEventReader reader
) throws XMLStreamException
{
903 docImport
.fireSchemaConflictEventExpectedStartTag(elName
, reader
);
907 protected void fireWarningEvent(String message
, String locationStr
, int severity
) {
908 docImport
.fireWarningEvent(message
, locationStr
, severity
, 1);
911 protected void fire(IIoEvent event
) {
912 docImport
.fire(event
);
915 protected boolean isNotBlank(String str
){
916 return StringUtils
.isNotBlank(str
);
919 protected boolean isBlank(String str
){
920 return StringUtils
.isBlank(str
);
923 public TaxonDescription
getTaxonDescription(Taxon taxon
, Reference
<?
> ref
, boolean isImageGallery
, boolean createNewIfNotExists
) {
924 return docImport
.getTaxonDescription(taxon
, isImageGallery
, createNewIfNotExists
);
929 * Returns the default language defined in the state. If no default language is defined in the state,
930 * the CDM default language is returned.
934 protected Language
getDefaultLanguage(MarkupImportState state
) {
935 Language result
= state
.getDefaultLanguage();
937 result
= Language
.DEFAULT();
943 //*********************** FROM XML IMPORT BASE ****************************************
944 protected boolean isEndingElement(XMLEvent event
, String elName
) throws XMLStreamException
{
945 return docImport
.isEndingElement(event
, elName
);
948 protected boolean isStartingElement(XMLEvent event
, String elName
) throws XMLStreamException
{
949 return docImport
.isStartingElement(event
, elName
);
953 protected void fillMissingEpithetsForTaxa(Taxon parentTaxon
, Taxon childTaxon
) {
954 docImport
.fillMissingEpithetsForTaxa(parentTaxon
, childTaxon
);
957 protected Feature
getFeature(MarkupImportState state
, UUID uuid
, String label
, String text
, String labelAbbrev
, TermVocabulary
<Feature
> voc
){
958 return docImport
.getFeature(state
, uuid
, label
, text
, labelAbbrev
, voc
);
961 protected ExtensionType
getExtensionType(MarkupImportState state
, UUID uuid
, String label
, String text
, String labelAbbrev
){
962 return docImport
.getExtensionType(state
, uuid
, label
, text
, labelAbbrev
);
965 protected AnnotationType
getAnnotationType(MarkupImportState state
, UUID uuid
, String label
, String text
, String labelAbbrev
, TermVocabulary
<AnnotationType
> voc
){
966 return docImport
.getAnnotationType(state
, uuid
, label
, text
, labelAbbrev
, voc
);
969 protected NamedAreaLevel
getNamedAreaLevel(MarkupImportState state
, UUID uuid
, String label
, String text
, String labelAbbrev
, TermVocabulary
<NamedAreaLevel
> voc
){
970 return docImport
.getNamedAreaLevel(state
, uuid
, label
, text
, labelAbbrev
, voc
);
973 protected NamedArea
getNamedArea(MarkupImportState state
, UUID uuid
, String label
, String text
, String labelAbbrev
, NamedAreaType areaType
, NamedAreaLevel level
, TermVocabulary voc
, TermMatchMode matchMode
){
974 return docImport
.getNamedArea(state
, uuid
, label
, text
, labelAbbrev
, areaType
, level
, voc
, matchMode
);
977 protected Language
getLanguage(MarkupImportState state
, UUID uuid
, String label
, String text
, String labelAbbrev
, TermVocabulary
<?
> voc
){
978 return docImport
.getLanguage(state
, uuid
, label
, text
, labelAbbrev
, voc
);
981 // *************************************** Concrete methods **********************************************/
990 protected Rank
makeRank(MarkupImportState state
, String value
, boolean byAbbrev
) {
992 if (StringUtils
.isBlank(value
)) {
996 boolean useUnknown
= true;
997 NomenclaturalCode nc
= makeNomenclaturalCode(state
);
998 if (value
.equals(GENUS_ABBREVIATION
)){
1000 }else if (byAbbrev
) {
1001 rank
= Rank
.getRankByAbbreviation(value
, nc
, useUnknown
);
1003 rank
= Rank
.getRankByEnglishName(value
, nc
, useUnknown
);
1005 if (rank
.equals(Rank
.UNKNOWN_RANK())) {
1008 } catch (UnknownCdmTypeException e
) {
1016 protected TeamOrPersonBase
<?
> createAuthor(String authorTitle
) {
1017 // TODO atomize and also use by name creation
1018 TeamOrPersonBase
<?
> result
= Team
.NewTitledInstance(authorTitle
, authorTitle
);
1022 protected String
getAndRemoveMapKey(Map
<String
, String
> map
, String key
) {
1023 String result
= map
.get(key
);
1025 if (result
!= null) {
1026 result
= normalize(result
);
1028 return StringUtils
.stripToNull(result
);
1033 * Creates a {@link NonViralName} object depending on the defined {@link NomenclaturalCode}
1034 * and the given parameters.
1039 protected NonViralName
<?
> createNameByCode(MarkupImportState state
, Rank rank
) {
1040 NonViralName
<?
> name
;
1041 NomenclaturalCode nc
= makeNomenclaturalCode(state
);
1042 name
= (NonViralName
<?
>) nc
.getNewTaxonNameInstance(rank
);
1048 * Returns the {@link NomenclaturalCode} for this import. Default is {@link NomenclaturalCode#ICBN} if
1049 * no code is defined.
1053 protected NomenclaturalCode
makeNomenclaturalCode(MarkupImportState state
) {
1054 NomenclaturalCode nc
= state
.getConfig().getNomenclaturalCode();
1056 nc
= NomenclaturalCode
.ICBN
; // default;
1064 * @param levelString
1068 protected NamedAreaLevel
makeNamedAreaLevel(MarkupImportState state
,
1069 String levelString
, XMLEvent next
) {
1070 NamedAreaLevel level
;
1072 level
= state
.getTransformer().getNamedAreaLevelByKey(levelString
);
1073 if (level
== null) {
1074 UUID levelUuid
= state
.getTransformer().getNamedAreaLevelUuid(levelString
);
1075 if (levelUuid
== null) {
1076 String message
= "Unknown distribution locality class (named area level): %s. Create new level instead.";
1077 message
= String
.format(message
, levelString
);
1078 fireWarningEvent(message
, next
, 6);
1080 level
= getNamedAreaLevel(state
, levelUuid
, levelString
, levelString
, levelString
, null);
1082 } catch (UndefinedTransformerMethodException e
) {
1083 throw new RuntimeException(e
);
1095 protected NamedArea
makeArea(MarkupImportState state
, String areaName
, NamedAreaLevel level
) {
1097 //TODO FM vocabulary
1098 TermVocabulary
<NamedArea
> voc
= null;
1099 NamedAreaType areaType
= null;
1101 NamedArea area
= null;
1103 area
= state
.getTransformer().getNamedAreaByKey(areaName
);
1104 } catch (UndefinedTransformerMethodException e
) {
1105 throw new RuntimeException(e
);
1108 boolean isNewInState
= false;
1109 UUID uuid
= state
.getAreaUuid(areaName
);
1111 isNewInState
= true;
1115 uuid
= state
.getTransformer().getNamedAreaUuid(areaName
);
1116 } catch (UndefinedTransformerMethodException e
) {
1117 throw new RuntimeException(e
);
1121 CdmImportBase
.TermMatchMode matchMode
= CdmImportBase
.TermMatchMode
.UUID_LABEL
;
1122 area
= getNamedArea(state
, uuid
, areaName
, areaName
, areaName
, areaType
, level
, voc
, matchMode
);
1124 state
.putAreaUuid(areaName
, area
.getUuid());
1126 //TODO just for testing -> make generic and move to better place
1127 String geoServiceLayer
="vmap0_as_bnd_political_boundary_a";
1128 String layerFieldName
="nam";
1130 if ("Bangka".equals(areaName
)){
1131 String areaValue
= "PULAU BANGKA#SUMATERA SELATAN";
1132 GeoServiceArea geoServiceArea
= new GeoServiceArea();
1133 geoServiceArea
.add(geoServiceLayer
, layerFieldName
, areaValue
);
1134 this.editGeoService
.setMapping(area
, geoServiceArea
);
1135 // save(area, state);
1137 if ("Luzon".equals(areaName
)){
1138 GeoServiceArea geoServiceArea
= new GeoServiceArea();
1140 List
<String
> list
= Arrays
.asList("HERMANA MAYOR ISLAND#CENTRAL LUZON",
1141 "HERMANA MENOR ISLAND#CENTRAL LUZON",
1143 for (String areaValue
: list
){
1144 geoServiceArea
.add(geoServiceLayer
, layerFieldName
, areaValue
);
1147 this.editGeoService
.setMapping(area
, geoServiceArea
);
1148 // save(area, state);
1150 if ("Mindanao".equals(areaName
)){
1151 GeoServiceArea geoServiceArea
= new GeoServiceArea();
1153 List
<String
> list
= Arrays
.asList("NORTHERN MINDANAO",
1154 "SOUTHERN MINDANAO",
1155 "WESTERN MINDANAO");
1156 //TODO to be continued
1157 for (String areaValue
: list
){
1158 geoServiceArea
.add(geoServiceLayer
, layerFieldName
, areaValue
);
1161 this.editGeoService
.setMapping(area
, geoServiceArea
);
1162 // save(area, state);
1164 if ("Palawan".equals(areaName
)){
1165 GeoServiceArea geoServiceArea
= new GeoServiceArea();
1167 List
<String
> list
= Arrays
.asList("PALAWAN#SOUTHERN TAGALOG");
1168 for (String areaValue
: list
){
1169 geoServiceArea
.add(geoServiceLayer
, layerFieldName
, areaValue
);
1172 this.editGeoService
.setMapping(area
, geoServiceArea
);
1173 // save(area, state);
1184 * Reads character data. Any element other than character data or the ending
1185 * tag will fire an unexpected element event.
1187 * @see #getCData(MarkupImportState, XMLEventReader, XMLEvent, boolean)
1192 * @throws XMLStreamException
1194 protected String
getCData(MarkupImportState state
, XMLEventReader reader
, XMLEvent next
) throws XMLStreamException
{
1195 return getCData(state
, reader
, next
, true);
1199 * Reads character data. Any element other than character data or the ending
1200 * tag will fire an unexpected element event.
1205 * @param inlineMarkup map for inline markup, this is used for e.g. the locality markup within a subheading
1206 * The map will be filled by the markup element name as key. The value may be a String, a CdmBase or any other object.
1207 * If null any markup text will be neglected but a warning will be fired if they exist.
1208 * @param removeInlineMarkupText if true the markedup text will be removed from the returned String
1209 * @param checkAttributes
1211 * @throws XMLStreamException
1213 protected String
getCData(MarkupImportState state
, XMLEventReader reader
, XMLEvent parent
, /*Map<String, Object> inlineMarkup, *boolean removeInlineMarkupText,*/ boolean checkAttributes
) throws XMLStreamException
{
1214 if (checkAttributes
){
1215 checkNoAttributes(parent
);
1219 while (reader
.hasNext()) {
1220 XMLEvent next
= readNoWhitespace(reader
);
1221 if (isMyEndingElement(next
, parent
)) {
1223 } else if (next
.isCharacters()) {
1224 text
+= next
.asCharacters().getData();
1225 } else if (isStartingElement(next
, FOOTNOTE_REF
)){
1226 handleNotYetImplementedElement(next
);
1227 // } else if (isStartingElement(next, LOCALITY)){
1228 // handleCDataLocality(state, reader, parent);
1230 handleUnexpectedElement(next
);
1233 throw new IllegalStateException("Event has no closing tag");
1237 // private void handleCDataLocality(MarkupImportState state, XMLEventReader reader, XMLEvent parent) {
1238 // checkAndRemoveAttributeValue(attributes, attrName, value)
1245 * For it returns a pure CData annotation string. This behaviour may change in future. More complex annotations
1246 * should be handled differently.
1249 * @param parentEvent
1251 * @throws XMLStreamException
1253 protected String
handleSimpleAnnotation(MarkupImportState state
, XMLEventReader reader
, XMLEvent parentEvent
) throws XMLStreamException
{
1254 String annotation
= getCData(state
, reader
, parentEvent
);
1259 * True if text is single "." oder "," or ";" or ":"
1263 protected boolean isPunctuation(String text
) {
1264 return text
== null ?
false : text
.trim().matches("^[\\.,;:]$");
1269 //********************************************** OLD *************************************
1271 // protected boolean testAdditionalElements(Element parentElement, List<String> excludeList){
1272 // boolean result = true;
1273 // List<Element> list = parentElement.getChildren();
1274 // for (Element element : list){
1275 // if (! excludeList.contains(element.getName())){
1276 // logger.warn("Unknown element (" + element.getName() + ") in parent element (" + parentElement.getName() + ")");
1284 // protected <T extends IdentifiableEntity> T makeReferenceType(Element element, Class<? extends T> clazz, MapWrapper<? extends T> objectMap, ResultWrapper<Boolean> success){
1286 // String linkType = element.getAttributeValue("linkType");
1287 // String ref = element.getAttributeValue("ref");
1288 // if(ref == null && linkType == null){
1289 // result = getInstance(clazz);
1290 // if (result != null){
1291 // String title = element.getTextNormalize();
1292 // result.setTitleCache(title, true);
1294 // }else if (linkType == null || linkType.equals("local")){
1296 // result = objectMap.get(ref);
1297 // if (result == null){
1298 // logger.warn("Object (ref = " + ref + ")could not be found in WrapperMap");
1300 // }else if(linkType.equals("external")){
1301 // logger.warn("External link types not yet implemented");
1302 // }else if(linkType.equals("other")){
1303 // logger.warn("Other link types not yet implemented");
1305 // logger.warn("Unknown link type or missing ref");
1307 // if (result == null){
1308 // success.setValue(false);
1314 // protected Reference makeAccordingTo(Element elAccordingTo, MapWrapper<Reference> referenceMap, ResultWrapper<Boolean> success){
1315 // Reference result = null;
1316 // if (elAccordingTo != null){
1317 // String childName = "AccordingToDetailed";
1318 // boolean obligatory = false;
1319 // Element elAccordingToDetailed = XmlHelp.getSingleChildElement(success, elAccordingTo, childName, elAccordingTo.getNamespace(), obligatory);
1321 // childName = "Simple";
1322 // obligatory = true;
1323 // Element elSimple = XmlHelp.getSingleChildElement(success, elAccordingTo, childName, elAccordingTo.getNamespace(), obligatory);
1325 // if (elAccordingToDetailed != null){
1326 // result = makeAccordingToDetailed(elAccordingToDetailed, referenceMap, success);
1328 // result = ReferenceFactory.newGeneric();
1329 // String title = elSimple.getTextNormalize();
1330 // result.setTitleCache(title, true);
1337 // private Reference makeAccordingToDetailed(Element elAccordingToDetailed, MapWrapper<Reference> referenceMap, ResultWrapper<Boolean> success){
1338 // Reference result = null;
1339 // Namespace tcsNamespace = elAccordingToDetailed.getNamespace();
1340 // if (elAccordingToDetailed != null){
1342 // String childName = "AuthorTeam";
1343 // boolean obligatory = false;
1344 // Element elAuthorTeam = XmlHelp.getSingleChildElement(success, elAccordingToDetailed, childName, tcsNamespace, obligatory);
1345 // makeAccordingToAuthorTeam(elAuthorTeam, success);
1348 // childName = "PublishedIn";
1349 // obligatory = false;
1350 // Element elPublishedIn = XmlHelp.getSingleChildElement(success, elAccordingToDetailed, childName, tcsNamespace, obligatory);
1351 // result = makeReferenceType(elPublishedIn, Reference.class, referenceMap, success);
1354 // childName = "MicroReference";
1355 // obligatory = false;
1356 // Element elMicroReference = XmlHelp.getSingleChildElement(success, elAccordingToDetailed, childName, tcsNamespace, obligatory);
1357 // String microReference = elMicroReference.getTextNormalize();
1358 // if (CdmUtils.Nz(microReference).equals("")){
1360 // logger.warn("MicroReference not yet implemented for AccordingToDetailed");
1366 // private Team makeAccordingToAuthorTeam(Element elAuthorTeam, ResultWrapper<Boolean> succes){
1367 // Team result = null;
1368 // if (elAuthorTeam != null){
1370 // logger.warn("AuthorTeam not yet implemented for AccordingToDetailed");