fix #6459 Implement deduplication for IntextReferences
[cdmlib.git] / cdmlib-io / src / main / java / eu / etaxonomy / cdm / io / markup / MarkupImportBase.java
index d14d869e164dee18f895d0bee6b22f17d0d80185..5584572477829258269954f5b7e56846cb6ff110 100644 (file)
-/**\r
-* Copyright (C) 2007 EDIT\r
-* European Distributed Institute of Taxonomy \r
-* http://www.e-taxonomy.eu\r
-* \r
-* The contents of this file are subject to the Mozilla Public License Version 1.1\r
-* See LICENSE.TXT at the top of this package for the full license terms.\r
-*/\r
-\r
-package eu.etaxonomy.cdm.io.markup;\r
-\r
-import java.net.MalformedURLException;\r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.Collection;\r
-import java.util.HashMap;\r
-import java.util.HashSet;\r
-import java.util.Iterator;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-import java.util.Stack;\r
-import java.util.UUID;\r
-import java.util.regex.Matcher;\r
-import java.util.regex.Pattern;\r
-\r
-import javax.xml.namespace.QName;\r
-import javax.xml.stream.Location;\r
-import javax.xml.stream.XMLEventReader;\r
-import javax.xml.stream.XMLStreamConstants;\r
-import javax.xml.stream.XMLStreamException;\r
-import javax.xml.stream.events.Attribute;\r
-import javax.xml.stream.events.Characters;\r
-import javax.xml.stream.events.EndElement;\r
-import javax.xml.stream.events.StartElement;\r
-import javax.xml.stream.events.XMLEvent;\r
-\r
-import org.apache.commons.lang.StringUtils;\r
-import org.apache.commons.lang.WordUtils;\r
-import org.apache.log4j.Logger;\r
-\r
-import eu.etaxonomy.cdm.api.service.IClassificationService;\r
-import eu.etaxonomy.cdm.api.service.ITermService;\r
-import eu.etaxonomy.cdm.common.CdmUtils;\r
-import eu.etaxonomy.cdm.ext.geo.GeoServiceArea;\r
-import eu.etaxonomy.cdm.ext.geo.IEditGeoService;\r
-import eu.etaxonomy.cdm.io.common.CdmImportBase;\r
-import eu.etaxonomy.cdm.io.common.CdmImportBase.TermMatchMode;\r
-import eu.etaxonomy.cdm.io.common.events.IIoEvent;\r
-import eu.etaxonomy.cdm.io.common.events.IoProblemEvent;\r
-import eu.etaxonomy.cdm.io.common.mapping.UndefinedTransformerMethodException;\r
-import eu.etaxonomy.cdm.model.agent.Team;\r
-import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;\r
-import eu.etaxonomy.cdm.model.common.AnnotatableEntity;\r
-import eu.etaxonomy.cdm.model.common.Annotation;\r
-import eu.etaxonomy.cdm.model.common.AnnotationType;\r
-import eu.etaxonomy.cdm.model.common.CdmBase;\r
-import eu.etaxonomy.cdm.model.common.DefinedTermBase;\r
-import eu.etaxonomy.cdm.model.common.Extension;\r
-import eu.etaxonomy.cdm.model.common.ExtensionType;\r
-import eu.etaxonomy.cdm.model.common.Language;\r
-import eu.etaxonomy.cdm.model.common.OriginalSourceType;\r
-import eu.etaxonomy.cdm.model.common.TermVocabulary;\r
-import eu.etaxonomy.cdm.model.description.DescriptionElementBase;\r
-import eu.etaxonomy.cdm.model.description.Distribution;\r
-import eu.etaxonomy.cdm.model.description.Feature;\r
-import eu.etaxonomy.cdm.model.description.PolytomousKey;\r
-import eu.etaxonomy.cdm.model.description.PresenceAbsenceTermBase;\r
-import eu.etaxonomy.cdm.model.description.PresenceTerm;\r
-import eu.etaxonomy.cdm.model.description.TaxonDescription;\r
-import eu.etaxonomy.cdm.model.description.TextData;\r
-import eu.etaxonomy.cdm.model.location.NamedArea;\r
-import eu.etaxonomy.cdm.model.location.NamedAreaLevel;\r
-import eu.etaxonomy.cdm.model.location.NamedAreaType;\r
-import eu.etaxonomy.cdm.model.media.IdentifiableMediaEntity;\r
-import eu.etaxonomy.cdm.model.media.Media;\r
-import eu.etaxonomy.cdm.model.name.NomenclaturalCode;\r
-import eu.etaxonomy.cdm.model.name.NonViralName;\r
-import eu.etaxonomy.cdm.model.name.Rank;\r
-import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;\r
-import eu.etaxonomy.cdm.model.reference.Reference;\r
-import eu.etaxonomy.cdm.model.taxon.Classification;\r
-import eu.etaxonomy.cdm.model.taxon.Taxon;\r
-import eu.etaxonomy.cdm.model.taxon.TaxonBase;\r
-import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;\r
-\r
-/**\r
- * @author a.mueller\r
- * @created 04.08.2008\r
- */\r
-public abstract class MarkupImportBase  {\r
-       @SuppressWarnings("unused")\r
-       private static final Logger logger = Logger.getLogger(MarkupImportBase.class);\r
-\r
-       //Base\r
-       protected static final String ALTITUDE = "altitude";\r
-       protected static final String ANNOTATION = "annotation";\r
-       protected static final String BOLD = "bold";\r
-       protected static final String BR = "br";\r
-       protected static final String CITATION = "citation";\r
-       protected static final String CLASS = "class";\r
-       protected static final String COORDINATES = "coordinates";\r
-       protected static final String DATES = "dates";\r
-       protected static final String GATHERING = "gathering";\r
-       protected static final String GENUS_ABBREVIATION = "genus abbreviation";\r
-       protected static final String FOOTNOTE = "footnote";\r
-       protected static final String FOOTNOTE_REF = "footnoteRef";\r
-       protected static final String FULL_NAME = "fullName";\r
-       protected static final String ITALICS = "italics";\r
-       protected static final String NUM = "num";\r
-       protected static final String NOTES = "notes";\r
-       protected static final String PUBLICATION = "publication";\r
-       protected static final String SPECIMEN_TYPE = "specimenType";\r
-       protected static final String STATUS = "status";\r
-       protected static final String SUB_HEADING = "subHeading";\r
-       protected static final String TYPE = "type";\r
-       protected static final String TYPE_STATUS = "typeStatus";\r
-\r
-       protected static final boolean CREATE_NEW = true;\r
-       protected static final boolean NO_IMAGE_GALLERY = false;\r
-       protected static final boolean IMAGE_GALLERY = true;\r
-\r
-       protected static final String ADDENDA = "addenda";\r
-       protected static final String BIBLIOGRAPHY = "bibliography";\r
-       protected static final String BIOGRAPHIES = "biographies";\r
-       protected static final String CHAR = "char";\r
-       protected static final String DEDICATION = "dedication";\r
-       protected static final String DEFAULT_MEDIA_URL = "defaultMediaUrl";\r
-       protected static final String DISTRIBUTION_LIST = "distributionList";\r
-       protected static final String DISTRIBUTION_LOCALITY = "distributionLocality";\r
-       protected static final String FEATURE = "feature";\r
-       protected static final String FIGURE = "figure";\r
-       protected static final String FIGURE_LEGEND = "figureLegend";\r
-       protected static final String FIGURE_PART = "figurePart";\r
-       protected static final String FIGURE_REF = "figureRef";\r
-       protected static final String FIGURE_TITLE = "figureTitle";\r
-       protected static final String FOOTNOTE_STRING = "footnoteString";\r
-       protected static final String FREQUENCY = "frequency";\r
-       protected static final String HEADING = "heading";\r
-       protected static final String HABITAT = "habitat";\r
-       protected static final String HABITAT_LIST = "habitatList";\r
-       protected static final String IS_FREETEXT = "isFreetext";\r
-       protected static final String ID = "id";\r
-       protected static final String KEY = "key";\r
-       protected static final String LIFE_CYCLE_PERIODS = "lifeCyclePeriods";\r
-       protected static final String META_DATA = "metaData";\r
-       protected static final String MODS = "mods";\r
-\r
-       protected static final String NOMENCLATURE = "nomenclature";\r
-       protected static final String QUOTE = "quote";\r
-       protected static final String RANK = "rank";\r
-       protected static final String REF = "ref";\r
-       protected static final String REF_NUM = "refNum";\r
-       protected static final String REFERENCE = "reference";\r
-       protected static final String REFERENCES = "references";\r
-       protected static final String SUB_CHAR = "subChar";\r
-       protected static final String TAXON = "taxon";\r
-       protected static final String TAXONTITLE = "taxontitle";\r
-       protected static final String TAXONTYPE = "taxontype";\r
-       protected static final String TEXT_SECTION = "textSection";\r
-       protected static final String TREATMENT = "treatment";\r
-       protected static final String SERIALS_ABBREVIATIONS = "serialsAbbreviations";\r
-       protected static final String STRING = "string";\r
-       protected static final String URL = "url";\r
-       protected static final String VERNACULAR_NAMES = "vernacularNames";\r
-       protected static final String WRITER = "writer";\r
-       \r
-       \r
-       //Nomenclature\r
-       protected static final String ACCEPTED = "accepted";\r
-       protected static final String ACCEPTED_NAME = "acceptedName";\r
-       protected static final String ALTERNATEPUBTITLE = "alternatepubtitle";\r
-       protected static final String AUTHOR = "author";\r
-       protected static final String DETAILS = "details";\r
-       protected static final String EDITION = "edition";\r
-       protected static final String EDITORS = "editors";\r
-       protected static final String HOMONYM = "homonym";\r
-       protected static final String HOMOTYPES = "homotypes";\r
-       protected static final String INFRANK = "infrank";\r
-       protected static final String INFRAUT = "infraut";\r
-       protected static final String INFRPARAUT = "infrparaut";\r
-       protected static final String ISSUE = "issue";\r
-       protected static final String NAME = "name";\r
-       protected static final String NAME_TYPE = "nameType";\r
-       protected static final String NOM = "nom";\r
-       protected static final String PAGES = "pages";\r
-       protected static final String PARAUT = "paraut";\r
-       protected static final String PUBFULLNAME = "pubfullname";\r
-       protected static final String PUBNAME = "pubname";\r
-       protected static final String PUBTITLE = "pubtitle";\r
-       protected static final String PUBTYPE = "pubtype";\r
-       protected static final String REF_PART = "refPart";\r
-       protected static final String SYNONYM = "synonym";\r
-       protected static final String USAGE = "usage";\r
-       protected static final String VOLUME = "volume";\r
-       protected static final String YEAR = "year";\r
-\r
-       \r
-       //keys\r
-       protected static final String COUPLET = "couplet";\r
-       protected static final String IS_SPOTCHARACTERS = "isSpotcharacters";\r
-       protected static final String ONLY_NUMBERED_TAXA_EXIST = "onlyNumberedTaxaExist";\r
-       protected static final String EXISTS = "exists";\r
-       protected static final String KEYNOTES = "keynotes";\r
-       protected static final String KEY_TITLE = "keyTitle";\r
-       protected static final String QUESTION = "question";\r
-       protected static final String TEXT = "text";\r
-       protected static final String TO_COUPLET = "toCouplet";\r
-       protected static final String TO_KEY = "toKey";\r
-       protected static final String TO_TAXON = "toTaxon";\r
-\r
-\r
-       protected MarkupDocumentImport docImport;\r
-\r
-       private IEditGeoService editGeoService;\r
-       \r
-       public MarkupImportBase(MarkupDocumentImport docImport) {\r
-               super();\r
-               this.docImport = docImport;\r
-               this.editGeoService = docImport.getEditGeoService();\r
-       }\r
-\r
-       private Stack<QName> unhandledElements = new Stack<QName>();\r
-       private Stack<QName> handledElements = new Stack<QName>();\r
-\r
-\r
-       protected <T extends CdmBase> void  save(Collection<T> collection, MarkupImportState state) {\r
-               if (state.isCheck() || collection.isEmpty()){\r
-                       return;\r
-               }\r
-               T example = collection.iterator().next();\r
-               if (example.isInstanceOf(TaxonBase.class)){\r
-                       Collection<TaxonBase> typedCollection = (Collection<TaxonBase>)collection;\r
-                       docImport.getTaxonService().saveOrUpdate(typedCollection);\r
-               }else if (example.isInstanceOf(Classification.class)){\r
-                       Collection<Classification> typedCollection = (Collection<Classification>)collection;\r
-                       docImport.getClassificationService().saveOrUpdate(typedCollection);\r
-               }else if (example.isInstanceOf(PolytomousKey.class)){\r
-                       Collection<PolytomousKey> typedCollection = (Collection<PolytomousKey>)collection;\r
-                       docImport.getPolytomousKeyService().saveOrUpdate(typedCollection);\r
-               }else if (example.isInstanceOf(DefinedTermBase.class)){\r
-                       Collection<DefinedTermBase> typedCollection = (Collection<DefinedTermBase>)collection;\r
-                       getTermService().saveOrUpdate(typedCollection);\r
-               }\r
-               \r
-       }\r
-       \r
-\r
-       //TODO move to service layer for all IdentifiableEntities       \r
-       protected void save(CdmBase cdmBase, MarkupImportState state) {\r
-               if (state.isCheck()){\r
-                       return;\r
-               }\r
-               cdmBase = CdmBase.deproxy(cdmBase, CdmBase.class);\r
-               if (cdmBase == null){\r
-                       String message = "Tried to save a null object.";\r
-                       fireWarningEvent(message, "--location ?? --", 6,1);\r
-               } else if (cdmBase.isInstanceOf(TaxonBase.class)){\r
-                       docImport.getTaxonService().saveOrUpdate((TaxonBase<?>)cdmBase);\r
-               }else if (cdmBase.isInstanceOf(Classification.class)){\r
-                       docImport.getClassificationService().saveOrUpdate((Classification)cdmBase);\r
-               }else if (cdmBase.isInstanceOf(PolytomousKey.class)){\r
-                       docImport.getPolytomousKeyService().saveOrUpdate((PolytomousKey)cdmBase);\r
-               }else if (cdmBase.isInstanceOf(DefinedTermBase.class)){\r
-                       docImport.getTermService().saveOrUpdate((DefinedTermBase<?>)cdmBase);\r
-               }else if (cdmBase.isInstanceOf(Media.class)){\r
-                       docImport.getMediaService().saveOrUpdate((Media)cdmBase);\r
-               }else if (cdmBase.isInstanceOf(SpecimenOrObservationBase.class)){\r
-                       docImport.getOccurrenceService().saveOrUpdate((SpecimenOrObservationBase<?>)cdmBase);\r
-               }else if (cdmBase.isInstanceOf(DescriptionElementBase.class)){\r
-                       docImport.getDescriptionService().saveDescriptionElement((DescriptionElementBase)cdmBase);\r
-               }else if (cdmBase.isInstanceOf(Reference.class)){\r
-                       docImport.getReferenceService().saveOrUpdate((Reference<?>)cdmBase);\r
-               }else{\r
-                       String message = "Unknown cdmBase type to save: " + cdmBase.getClass();\r
-                       fireWarningEvent(message, "Unknown location", 8);\r
-               }\r
-               //logger.warn("Saved " +  cdmBase);\r
-       }\r
-       \r
-       \r
-       protected ITermService getTermService() {\r
-               return docImport.getTermService();\r
-       }\r
-       \r
-       protected IClassificationService getClassificationService() {\r
-               return docImport.getClassificationService();\r
-       }\r
-\r
-//*********************** Attribute methods *************************************/\r
-\r
-       /**\r
-        * Returns a map for all attributes of an start element\r
-        * @param event\r
-        * @return\r
-        */\r
-       protected Map<String, Attribute> getAttributes(XMLEvent event) {\r
-               Map<String, Attribute> result = new HashMap<String, Attribute>();\r
-               if (!event.isStartElement()){\r
-                       fireWarningEvent("Event is not an startElement. Can't check attributes", makeLocationStr(event.getLocation()), 1, 1);\r
-                       return result;\r
-               }\r
-               StartElement element = event.asStartElement(); \r
-               Iterator<Attribute> attributes = element.getAttributes();\r
-               while (attributes.hasNext()){\r
-                       Attribute attribute = attributes.next();\r
-                       //TODO namespaces\r
-                       result.put(attribute.getName().getLocalPart(), attribute);\r
-               }\r
-               return result;\r
-       }\r
-\r
-       /**\r
-        * Throws an unexpected attributes event if the event has any attributes.\r
-        * @param event\r
-        */\r
-       protected void checkNoAttributes(Map<String, Attribute> attributes, XMLEvent event) {\r
-               String[] exceptions = new String[]{};\r
-               handleUnexpectedAttributes(event.getLocation(), attributes, 1, exceptions);\r
-       }\r
-       \r
-       \r
-       \r
-       /**\r
-        * Throws an unexpected attributes event if the event has any attributes.\r
-        * @param event\r
-        */\r
-       protected void checkNoAttributes(XMLEvent event) {\r
-               String[] exceptions = new String[]{};\r
-               checkNoAttributes(event, 1, exceptions); \r
-       }\r
-\r
-       /**\r
-        * Throws an unexpected attributes event if the event has any attributes except those mentioned in "exceptions".\r
-        * @param event\r
-        * @param exceptions\r
-        */\r
-       protected void checkNoAttributes(XMLEvent event, int stackDepth, String... exceptions) {\r
-               if (! event.isStartElement()){\r
-                       fireWarningEvent("Event is not an startElement. Can't check attributes", makeLocationStr(event.getLocation()), 1, 1);\r
-                       return;\r
-               }\r
-               StartElement startElement = event.asStartElement();\r
-               Map<String, Attribute> attributes = getAttributes(startElement);\r
-               handleUnexpectedAttributes(startElement.getLocation(), attributes, stackDepth+1, exceptions);\r
-       }\r
-       \r
-\r
-       /**\r
-        * Checks if the given attribute exists and has the given value.\r
-        * If yes, true is returned and the attribute is removed from the attributes map.\r
-        * Otherwise false is returned.\r
-        * @param attributes\r
-        * @param attrName\r
-        * @param value\r
-        * @return <code>true</code> if attribute has given value, <code>false</code> otherwise\r
-        */\r
-       protected boolean checkAndRemoveAttributeValue( Map<String, Attribute> attributes, String attrName, String value) {\r
-               Attribute attr = attributes.get(attrName);\r
-               if (attr == null ||value == null ){\r
-                       return false;\r
-               }else{\r
-                       if (value.equals(attr.getValue())){\r
-                               attributes.remove(attrName);\r
-                               return true;\r
-                       }else{\r
-                               return false;\r
-                       }\r
-               }\r
-       }\r
-\r
-\r
-       /**\r
-        * Returns the value of a given attribute name and removes the attribute from the attributes map. \r
-        * @param attributes\r
-        * @param attrName\r
-        * @return\r
-        */\r
-       protected String getAndRemoveAttributeValue(Map<String, Attribute> attributes, String attrName) {\r
-               return getAndRemoveAttributeValue(null, attributes, attrName, false, 1);\r
-       }\r
-       \r
-       /**\r
-        * Returns the value of a boolean attribute with the given name and removes the attribute from the attributes map. \r
-        * Returns <code>defaultValue</code> if the attribute does not exist. ALso returns <code>defaultValue</code> and throws a warning if the\r
-        * attribute has no boolean value (true, false).\r
-        * @param \r
-        * @param attributes the \r
-        * @param attrName the name of the attribute\r
-        * @param defaultValue the default value to return if attribute does not exist or can not be defined\r
-        * @return\r
-        */\r
-       protected Boolean getAndRemoveBooleanAttributeValue(XMLEvent event, Map<String, Attribute> attributes, String attrName, Boolean defaultValue) {\r
-               String value = getAndRemoveAttributeValue(null, attributes, attrName, false, 1);\r
-               Boolean result = defaultValue;\r
-               if (value != null){\r
-                       if (value.equalsIgnoreCase("true")){\r
-                               result = true;\r
-                       }else if (value.equalsIgnoreCase("false")){\r
-                               result = false;\r
-                       }else{\r
-                               String message = "Boolean attribute has no boolean value ('true', 'false') but '%s'";\r
-                               fireWarningEvent(String.format(message, value), makeLocationStr(event.getLocation()), 6, 1);\r
-                       }\r
-               }\r
-               return result;\r
-       }\r
-\r
-       \r
-       /**\r
-        * Returns the value of a given attribute name and returns the attribute from the attributes map.\r
-        * Fires a mandatory field is missing event if the attribute does not exist.\r
-        * @param xmlEvent\r
-        * @param attributes\r
-        * @param attrName\r
-        * @return\r
-        */\r
-       protected String getAndRemoveRequiredAttributeValue(XMLEvent xmlEvent, Map<String, Attribute> attributes, String attrName) {\r
-               return getAndRemoveAttributeValue(xmlEvent, attributes, attrName, true, 1);\r
-       }\r
-       \r
-       /**\r
-        * Returns the value of a given attribute name and returns the attribute from the attributes map.\r
-        * If required is <code>true</code> and the attribute does not exist a mandatory field is missing event is fired.\r
-        * @param xmlEvent\r
-        * @param attributes\r
-        * @param attrName\r
-        * @param isRequired\r
-        * @return\r
-        */\r
-       private String getAndRemoveAttributeValue(XMLEvent xmlEvent, Map<String, Attribute> attributes, String attrName, boolean isRequired, int stackDepth) {\r
-               Attribute attr = attributes.get(attrName);\r
-               if (attr == null ){\r
-                       if (isRequired){\r
-                               fireMandatoryElementIsMissing(xmlEvent, attrName, 8, stackDepth+1);\r
-                       }\r
-                       return null;\r
-               }else{\r
-                       attributes.remove(attrName);\r
-                       return attr.getValue();\r
-               }\r
-       }       \r
-\r
-       /**\r
-        * Fires an not yet implemented event if the given attribute exists in attributes.\r
-        * @param attributes\r
-        * @param attrName\r
-        */\r
-       protected void handleNotYetImplementedAttribute(Map<String, Attribute>  attributes, String attrName) {\r
-               Attribute attr = attributes.get(attrName);\r
-               if (attr != null){\r
-                       attributes.remove(attrName);\r
-                       QName qName = attr.getName();\r
-                       fireNotYetImplementedAttribute(attr.getLocation(), qName, 1);\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Fires an unhandled attributes event, if attributes exist in attributes map not covered by the exceptions.\r
-        * No event is fired if the unhandled elements stack is not empty.\r
-        * @param location\r
-        * @param attributes\r
-        * @param exceptions\r
-        */\r
-       protected void handleUnexpectedAttributes(Location location,Map<String, Attribute> attributes, String... exceptions) {\r
-               handleUnexpectedAttributes(location, attributes, 1, exceptions);\r
-       }\r
-               \r
-       /**\r
-        * see {@link #handleUnexpectedAttributes(Location, Map, String...)}\r
-     *\r
-        * @param location\r
-        * @param attributes\r
-        * @param stackDepth the stack trace depth\r
-        * @param exceptions\r
-        */\r
-       private void handleUnexpectedAttributes(Location location,Map<String, Attribute> attributes, int stackDepth, String... exceptions) {\r
-               if (attributes.size() > 0){\r
-                       if (this.unhandledElements.size() == 0 ){\r
-                               boolean hasUnhandledAttributes = false;\r
-                               for (String key : attributes.keySet()){\r
-                                       boolean isException = false;\r
-                                       for (String exception : exceptions){\r
-                                               if(key.equals(exception)){\r
-                                                       isException = true;\r
-                                               }\r
-                                       }\r
-                                       if (!isException){\r
-                                               hasUnhandledAttributes = true;\r
-                                       }\r
-                               }\r
-                               if (hasUnhandledAttributes){\r
-                                       fireUnexpectedAttributes(location, attributes, stackDepth+1);\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-\r
-       \r
-       private void fireUnexpectedAttributes(Location location, Map<String, Attribute> attributes, int stackDepth) {\r
-               String attributesString = "";\r
-               for (String key : attributes.keySet()){\r
-                       Attribute attribute = attributes.get(key);\r
-                       attributesString = CdmUtils.concat(",", attributesString, attribute.getName().getLocalPart() + ":" + attribute.getValue());\r
-               }\r
-               String message = "Unexpected attributes: %s";\r
-               IoProblemEvent event = makeProblemEvent(location, String.format(message, attributesString), 1 , stackDepth +1 );\r
-               fire(event);    \r
-       }\r
-       \r
-\r
-       protected void fireUnexpectedAttributeValue(XMLEvent parentEvent, String attrName, String attrValue) {\r
-               String message = "Unexpected attribute value %s='%s'";\r
-               message = String.format(message, attrName, attrValue);\r
-               IoProblemEvent event = makeProblemEvent(parentEvent.getLocation(), message, 1 , 1 );\r
-               fire(event);\r
-       }\r
-\r
-       protected void handleNotYetImplementedAttributeValue(XMLEvent xmlEvent, String attrName, String attrValue) {\r
-               String message = "Attribute %s not yet implemented for value '%s'";\r
-               message = String.format(message, attrName, attrValue);\r
-               IIoEvent event = makeProblemEvent(xmlEvent.getLocation(), message, 1, 1 );\r
-               fire(event);            \r
-       }\r
-       \r
-       protected void fireNotYetImplementedAttribute(Location location, QName qName, int stackDepth) {\r
-               String message = "Attribute not yet implemented: %s";\r
-               IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart()), 1, stackDepth+1 );\r
-               fire(event);            \r
-       }\r
-       \r
-\r
-       protected void fireUnexpectedEvent(XMLEvent xmlEvent, int stackDepth) {\r
-               Location location = xmlEvent.getLocation();\r
-               String message = "Unexpected event: %s";\r
-               IIoEvent event = makeProblemEvent(location, String.format(message, xmlEvent.toString()), 2, stackDepth +1);\r
-               fire(event);            \r
-       }\r
-\r
-       protected void fireUnexpectedStartElement(Location location, StartElement startElement, int stackDepth) {\r
-               QName qName = startElement.getName();\r
-               String message = "Unexpected start element: %s";\r
-               IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart()), 2, stackDepth +1);\r
-               fire(event);            \r
-       }\r
-       \r
-\r
-       protected void fireUnexpectedEndElement(Location location, EndElement endElement, int stackDepth) {\r
-               QName qName = endElement.getName();\r
-               String message = "Unexpected end element: %s";\r
-               IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart()), 16, stackDepth+1);\r
-               fire(event);            \r
-       }\r
-       \r
-       protected void fireNotYetImplementedElement(Location location, QName qName, int stackDepth) {\r
-               String message = "Element not yet implemented: %s";\r
-               IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart()), 1, stackDepth+1 );\r
-               fire(event);            \r
-       }\r
-\r
-       protected void fireNotYetImplementedCharacters(Location location, Characters chars, int stackDepth) {\r
-               String message = "Characters not yet handled: %s";\r
-               IIoEvent event = makeProblemEvent(location, String.format(message, chars.getData()), 1, stackDepth+1 );\r
-               fire(event);            \r
-       }\r
-\r
-       /**\r
-        * Creates a problem event.\r
-        * Be aware of the right depths of the stack trace !\r
-        * @param location \r
-        * @param message\r
-        * @param severity\r
-        * @return\r
-        */\r
-       private IoProblemEvent makeProblemEvent(Location location, String message, int severity, int stackDepth) {\r
-               stackDepth++;\r
-               StackTraceElement[] stackTrace = new Exception().getStackTrace();\r
-               int lineNumber = stackTrace[stackDepth].getLineNumber();\r
-               String methodName = stackTrace[stackDepth].getMethodName();\r
-               String locationStr = makeLocationStr(location);\r
-               String className = stackTrace[stackDepth].getClassName();\r
-               Class<?> declaringClass;\r
-               try {\r
-                       declaringClass = Class.forName(className);\r
-               } catch (ClassNotFoundException e) {\r
-                       declaringClass = this.getClass();\r
-               }\r
-               IoProblemEvent event = IoProblemEvent.NewInstance(declaringClass, message, \r
-                               locationStr, lineNumber, severity, methodName);\r
-               return event;\r
-       }\r
-\r
-       /**\r
-        * Creates a string from a location\r
-        * @param location\r
-        * @return\r
-        */\r
-       protected String makeLocationStr(Location location) {\r
-               String locationStr = location == null ? " - no location - " : "l." + location.getLineNumber() + "/c."+ location.getColumnNumber();\r
-               return locationStr;\r
-       }\r
-       \r
-\r
-       /**\r
-        * Fires an unexpected element event if the unhandled elements stack is empty.\r
-        * Otherwise adds the element to the stack.\r
-        * @param event\r
-        */\r
-       protected void handleUnexpectedStartElement(XMLEvent event) {\r
-               handleUnexpectedStartElement(event, 1);\r
-       }\r
-       \r
-       /**\r
-        * Fires an unexpected element event if the unhandled elements stack is empty.\r
-        * Otherwise adds the element to the stack.\r
-        * @param event\r
-        */\r
-       protected void handleUnexpectedStartElement(XMLEvent event, int stackDepth) {\r
-               QName qName = event.asStartElement().getName();\r
-               if (! unhandledElements.empty()){\r
-                       unhandledElements.push(qName);\r
-               }else{\r
-                       fireUnexpectedStartElement(event.getLocation(), event.asStartElement(), stackDepth + 1);\r
-               }       \r
-       }\r
-\r
-       \r
-       protected void handleUnexpectedEndElement(EndElement event) {\r
-               handleUnexpectedEndElement(event, 1);\r
-       }\r
-       \r
-       /**\r
-        * Fires an unexpected element event if the event is not the last on the stack.\r
-        * Otherwise removes last stack element.\r
-        * @param event\r
-        */\r
-       protected void handleUnexpectedEndElement(EndElement event, int stackDepth) {\r
-               QName qName = event.asEndElement().getName();\r
-               if (!unhandledElements.isEmpty() && unhandledElements.peek().equals(qName)){\r
-                       unhandledElements.pop();\r
-               }else{\r
-                       fireUnexpectedEndElement(event.getLocation(), event.asEndElement(), stackDepth + 1);\r
-               }\r
-       }\r
-       \r
-       /**\r
-        * \r
-        * @param endElement\r
-        */\r
-       protected void popUnimplemented(EndElement endElement) {\r
-               QName qName = endElement.asEndElement().getName();\r
-               if (unhandledElements.peek().equals(qName)){\r
-                       unhandledElements.pop();\r
-               }else{\r
-                       String message = "End element is not last on stack: %s";\r
-                       message = String.format(message, qName.getLocalPart());\r
-                       IIoEvent event = makeProblemEvent(endElement.getLocation(), message, 16, 1);\r
-                       fire(event);\r
-               }\r
-               \r
-       }\r
-       \r
-       \r
-       /**\r
-        * Fires an unexpected element event if the unhandled element stack is empty.\r
-        * @param event\r
-        */\r
-       protected void handleUnexpectedElement(XMLEvent event) {\r
-               if (event.isStartElement()){\r
-                       handleUnexpectedStartElement(event);\r
-               }else if (event.isEndElement()){\r
-                       handleUnexpectedEndElement(event.asEndElement());\r
-               }else if (event.getEventType() == XMLStreamConstants.COMMENT){\r
-                       //do nothing\r
-               }else if (! unhandledElements.empty()){\r
-                       //do nothing\r
-               }else{\r
-                       fireUnexpectedEvent(event, 1);\r
-               }       \r
-       }\r
-       \r
-       /**\r
-        * Fires an not yet implemented event and adds the element name to the unhandled elements stack.\r
-        * @param event\r
-        */\r
-       protected void handleNotYetImplementedCharacters(XMLEvent event) {\r
-               Characters chars = event.asCharacters();\r
-               fireNotYetImplementedCharacters(event.getLocation(), chars, 1);\r
-       }\r
-\r
-       /**\r
-        * Fires an not yet implemented event and adds the element name to the unhandled elements stack.\r
-        * @param event\r
-        */\r
-       protected void handleNotYetImplementedElement(XMLEvent event) {\r
-               QName qName = event.asStartElement().getName();\r
-               boolean isTopLevel = unhandledElements.isEmpty();\r
-               unhandledElements.push(qName);\r
-               if (isTopLevel){\r
-                       fireNotYetImplementedElement(event.getLocation(), qName, 1);\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Checks if a mandatory text is not empty or null.\r
-        * Returns true if text is given.\r
-        * Fires an mandatory element is missing event otherwise and returns <code>null</code>.\r
-        * @param text\r
-        * @param parentEvent\r
-        * @return\r
-        */\r
-       protected boolean checkMandatoryText(String text, XMLEvent parentEvent) {\r
-               if (! StringUtils.isNotBlank(text)){\r
-                       fireMandatoryElementIsMissing(parentEvent, "CData", 4, 1);\r
-                       return false;\r
-               }\r
-               return true;\r
-       }\r
-       \r
-       /**\r
-        * Fires an mandatory element is missing event if exists is <code>false</code>.\r
-        * @param hasMandatory\r
-        * @param parentEvent\r
-        * @param string\r
-        */\r
-       protected void checkMandatoryElement(boolean exists, StartElement parentEvent, String attrName) {\r
-               if (! exists){\r
-                       fireMandatoryElementIsMissing(parentEvent, attrName, 5, 1);\r
-               }\r
-       }\r
-\r
-       \r
-       /**\r
-        * Fires an element is missing event.\r
-        * @param xmlEvent\r
-        * @param string\r
-        * @param severity\r
-        * @param stackDepth\r
-        * @throws IllegalStateException if xmlEvent is not a StartElement and not an Attribute\r
-        */\r
-       private void fireMandatoryElementIsMissing(XMLEvent xmlEvent, String missingEventName, int severity, int stackDepth) throws IllegalStateException{\r
-               Location location = xmlEvent.getLocation();\r
-               String typeName;\r
-               QName qName;\r
-               if (xmlEvent.isAttribute()){\r
-                       Attribute attribute = ((Attribute)xmlEvent);\r
-                       typeName = "attribute";\r
-                       qName = attribute.getName();\r
-               }else if (xmlEvent.isStartElement()){\r
-                       typeName = "element";\r
-                       qName = xmlEvent.asStartElement().getName();\r
-               }else{\r
-                       throw new IllegalStateException("mandatory element only allowed for attributes and start tags in " + makeLocationStr(location));\r
-               }\r
-               String message = "Mandatory %s '%s' is missing in %s";\r
-               message = String.format(message, typeName , missingEventName, qName.getLocalPart());\r
-               IIoEvent event = makeProblemEvent(location, message, severity, stackDepth +1);\r
-               fire(event);            \r
-       }\r
-       \r
-\r
-\r
-\r
-       /**\r
-        * Returns true if the "next" event is the ending tag for the "parent" event.\r
-        * @param next end element to test, must not be null\r
-        * @param parentEvent start element to test\r
-        * @return true if the "next" event is the ending tag for the "parent" event.\r
-        * @throws XMLStreamException\r
-        */\r
-       protected boolean isMyEndingElement(XMLEvent next, XMLEvent parentEvent) throws XMLStreamException {\r
-               if (! parentEvent.isStartElement()){\r
-                       String message = "Parent event should be start tag";\r
-                       fireWarningEvent(message, makeLocationStr(next.getLocation()), 6);\r
-                       return false;\r
-               }\r
-               return isEndingElement(next, parentEvent.asStartElement().getName().getLocalPart());\r
-       }\r
-       \r
-       /**\r
-        * Trims the text and removes turns all whitespaces into single empty space.\r
-        * @param text\r
-        * @return\r
-        */\r
-       protected String normalize(String text) {\r
-               text = StringUtils.trimToEmpty(text);\r
-               text = text.replaceAll("\\s+", " ");\r
-               return text;\r
-       }\r
-       \r
-\r
-\r
-       /**\r
-        * Removes whitespaces at beginning and end and makes the first letter\r
-        * a capital letter and all other letters small letters.\r
-        * @param value\r
-        * @return\r
-        */\r
-       protected String toFirstCapital(String value) {\r
-               if (StringUtils.isBlank(value)){\r
-                       return value;\r
-               }else{\r
-                       String result = "";\r
-                       value = value.trim();\r
-                       result += value.trim().substring(0,1).toUpperCase();\r
-                       if (value.length()>1){\r
-                               result += value.substring(1).toLowerCase();\r
-                       }\r
-                       return result;\r
-               }\r
-       }\r
-       \r
-       /**\r
-        * Currently not used.\r
-        * @param str\r
-        * @param allowedNumberOfCharacters\r
-        * @param onlyFirstCapital\r
-        * @return\r
-        */\r
-       protected boolean isAbbreviation(String str, int allowedNumberOfCharacters, boolean onlyFirstCapital){\r
-               if (isBlank(str)){\r
-                       return false;\r
-               }\r
-               str = str.trim();\r
-               if (! str.endsWith(".")){\r
-                       return false;\r
-               }\r
-               str = str.substring(0, str.length() -1);\r
-               if (str.length() > allowedNumberOfCharacters){\r
-                       return false;\r
-               }\r
-               final String re = "^\\p{javaUpperCase}\\p{javaLowerCase}*$";\r
-               if (str.matches(re)){\r
-                       return true;\r
-               }else{\r
-                       return false;\r
-               }\r
-       }\r
-       \r
-       /**\r
-        * Checks if <code>abbrev</code> is the short form for the genus name (strGenusName).\r
-        * Usually this is the case if <code>abbrev</code> is the first letter (optional with ".") \r
-        * of strGenusName. But in older floras it may also be the first 2 or 3 letters (optional with dot).\r
-        * However, we allow only a maximum of 2 letters to be anambigous. In cases with 3 letters better \r
-        * change the original markup data.\r
-        * @param single\r
-        * @param strGenusName\r
-        * @return\r
-        */\r
-       protected boolean isGenusAbbrev(String abbrev, String strGenusName) {\r
-               if (! abbrev.matches("[A-Z][a-z]?\\.?")) {\r
-                       return false;\r
-               }else if (abbrev.length() == 0 || strGenusName == null || strGenusName.length() == 0){\r
-                       return false; \r
-               }else{\r
-                       abbrev = abbrev.replace(".", "");\r
-                       return strGenusName.startsWith(abbrev);\r
-//                     boolean result = true;\r
-//                     for (int i = 0 ; i < abbrev.length(); i++){\r
-//                             result &= ( abbrev.charAt(i) == strGenusName.charAt(i));\r
-//                     }\r
-//                     return result;\r
-               }\r
-       }\r
-\r
-       \r
-       /**\r
-        * Checks if all words in the given string start with a capital letter but do not have any further capital letter.\r
-        * @param word the string to be checekd. Usually should be a single word.\r
-        * @return true if the above is the case, false otherwise\r
-        */\r
-       protected boolean isFirstCapitalWord(String word) {\r
-               if (WordUtils.capitalizeFully(word).equals(word)){\r
-                       return true;\r
-               }else if (WordUtils.capitalizeFully(word,new char[]{'-'}).equals(word)){\r
-                       //for words like Le-Testui (which is a species epithet)\r
-                       return true;\r
-               }else{\r
-                       return false;\r
-               }\r
-       }\r
-       \r
-\r
-       /**\r
-        * Read next event. Ignore whitespace events.\r
-        * @param reader\r
-        * @return\r
-        * @throws XMLStreamException\r
-        */\r
-       protected XMLEvent readNoWhitespace(XMLEventReader reader) throws XMLStreamException {\r
-               XMLEvent event = reader.nextEvent();\r
-               while (event.isCharacters() && event.asCharacters().isWhiteSpace()){\r
-                       event = reader.nextEvent();\r
-               }\r
-               return event;\r
-       }\r
-       \r
-       /**\r
-        * Returns the REQUIRED "class" attribute for a given event and checks that it is the only attribute.\r
-        * @param parentEvent\r
-        * @return\r
-        */\r
-       protected String getClassOnlyAttribute(XMLEvent parentEvent) {\r
-               return getClassOnlyAttribute(parentEvent, true);\r
-       }\r
-\r
-\r
-       /**\r
-        * Returns the "class" attribute for a given event and checks that it is the only attribute.\r
-        * @param parentEvent\r
-        * @return\r
-        */\r
-       protected String getClassOnlyAttribute(XMLEvent parentEvent, boolean required) {\r
-               return getOnlyAttribute(parentEvent, CLASS, required);\r
-       }\r
-       \r
-       /**\r
-        * Returns the value for the only attribute for a given event and checks that it is the only attribute.\r
-        * @param parentEvent\r
-        * @return\r
-        */\r
-       protected String getOnlyAttribute(XMLEvent parentEvent, String attrName, boolean required) {\r
-               Map<String, Attribute> attributes = getAttributes(parentEvent);\r
-               String classValue =getAndRemoveAttributeValue(parentEvent, attributes, attrName, required, 1);\r
-               checkNoAttributes(attributes, parentEvent);\r
-               return classValue;\r
-       }\r
-       \r
-       \r
-       protected void fireWarningEvent(String message, String locationStr, Integer severity, Integer depth) {\r
-               docImport.fireWarningEvent(message, locationStr, severity, depth);\r
-       }\r
-       \r
-       protected void fireWarningEvent(String message, XMLEvent event, Integer severity) {\r
-               docImport.fireWarningEvent(message, makeLocationStr(event.getLocation()), severity, 1);\r
-       }\r
-       \r
-       protected void fireSchemaConflictEventExpectedStartTag(String elName, XMLEventReader reader) throws XMLStreamException {\r
-               docImport.fireSchemaConflictEventExpectedStartTag(elName, reader);\r
-       }\r
-\r
-       \r
-       protected void fireWarningEvent(String message, String locationStr, int severity) {\r
-               docImport.fireWarningEvent(message, locationStr, severity, 1);  \r
-       }\r
-       \r
-       protected void fire(IIoEvent event) {\r
-               docImport.fire(event);\r
-       }\r
-       \r
-       protected boolean isNotBlank(String str){\r
-               return StringUtils.isNotBlank(str);\r
-       }\r
-       \r
-       protected boolean isBlank(String str){\r
-               return StringUtils.isBlank(str);\r
-       }\r
-\r
-       protected TaxonDescription getTaxonDescription(Taxon taxon, Reference<?> ref, boolean isImageGallery, boolean createNewIfNotExists) {\r
-               return docImport.getTaxonDescription(taxon, isImageGallery, createNewIfNotExists);      \r
-       }       \r
-       \r
-\r
-       /**\r
-        * Returns the default language defined in the state. If no default language is defined in the state,\r
-        * the CDM default language is returned.\r
-        * @param state\r
-        * @return\r
-        */\r
-       protected Language getDefaultLanguage(MarkupImportState state) {\r
-               Language result = state.getDefaultLanguage();\r
-               if (result == null){\r
-                       result = Language.DEFAULT();\r
-               }\r
-               return result;\r
-       }\r
-\r
-\r
-//*********************** FROM XML IMPORT BASE ****************************************\r
-       protected boolean isEndingElement(XMLEvent event, String elName) throws XMLStreamException {\r
-               return docImport.isEndingElement(event, elName);\r
-       }\r
-       \r
-       protected boolean isStartingElement(XMLEvent event, String elName) throws XMLStreamException {\r
-               return docImport.isStartingElement(event, elName);\r
-       }\r
-       \r
-\r
-       protected void fillMissingEpithetsForTaxa(Taxon parentTaxon, Taxon childTaxon) {\r
-               docImport.fillMissingEpithetsForTaxa(parentTaxon, childTaxon);  \r
-       }\r
-       \r
-       protected Feature getFeature(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<Feature> voc){\r
-               return docImport.getFeature(state, uuid, label, text, labelAbbrev, voc);\r
-       }\r
-       \r
-       protected ExtensionType getExtensionType(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev){\r
-               return docImport.getExtensionType(state, uuid, label, text, labelAbbrev);\r
-       }\r
-       \r
-       protected AnnotationType getAnnotationType(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<AnnotationType> voc){\r
-               return docImport.getAnnotationType(state, uuid, label, text, labelAbbrev, voc);\r
-       }\r
-       \r
-       protected NamedAreaLevel getNamedAreaLevel(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<NamedAreaLevel> voc){\r
-               return docImport.getNamedAreaLevel(state, uuid, label, text, labelAbbrev, voc);\r
-       }\r
-       \r
-       protected NamedArea getNamedArea(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, NamedAreaType areaType, NamedAreaLevel level, TermVocabulary voc, TermMatchMode matchMode){\r
-               return docImport.getNamedArea(state, uuid, label, text, labelAbbrev, areaType, level, voc, matchMode);\r
-       }\r
-       \r
-       protected Language getLanguage(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<?> voc){\r
-               return docImport.getLanguage(state, uuid, label, text, labelAbbrev, voc);\r
-       }\r
-       \r
-// *************************************** Concrete methods **********************************************/\r
-\r
-\r
-       /**\r
-        * @param state\r
-        * @param classValue\r
-        * @param byAbbrev\r
-        * @return\r
-        */\r
-       protected Rank makeRank(MarkupImportState state, String value, boolean byAbbrev) {\r
-               Rank rank = null;\r
-               if (StringUtils.isBlank(value)) {\r
-                       return null;\r
-               }\r
-               try {\r
-                       boolean useUnknown = true;\r
-                       NomenclaturalCode nc = makeNomenclaturalCode(state);\r
-                       if (value.equals(GENUS_ABBREVIATION)){\r
-                               rank = Rank.GENUS();\r
-                       }else if (byAbbrev) {\r
-                               rank = Rank.getRankByIdInVoc(value, nc, useUnknown);\r
-                       } else {\r
-                               rank = Rank.getRankByEnglishName(value, nc, useUnknown);\r
-                       }\r
-                       if (rank.equals(Rank.UNKNOWN_RANK())) {\r
-                               rank = null;\r
-                       }\r
-               } catch (UnknownCdmTypeException e) {\r
-                       // doNothing\r
-               }\r
-               return rank;\r
-       }\r
-\r
-\r
-\r
-       protected TeamOrPersonBase<?> createAuthor(String authorTitle) {\r
-               // TODO atomize and also use by name creation\r
-               TeamOrPersonBase<?> result = Team.NewTitledInstance(authorTitle, authorTitle);\r
-               return result;\r
-       }\r
-       \r
-       protected String getAndRemoveMapKey(Map<String, String> map, String key) {\r
-               String result = map.get(key);\r
-               map.remove(key);\r
-               if (result != null) {\r
-                       result = normalize(result);\r
-               }\r
-               return StringUtils.stripToNull(result);\r
-       }\r
-\r
-\r
-       /**\r
-        * Creates a {@link NonViralName} object depending on the defined {@link NomenclaturalCode}\r
-        * and the given parameters.\r
-        * @param state\r
-        * @param rank\r
-        * @return\r
-        */\r
-       protected NonViralName<?> createNameByCode(MarkupImportState state, Rank rank) {\r
-               NonViralName<?> name;\r
-               NomenclaturalCode nc = makeNomenclaturalCode(state);\r
-               name = (NonViralName<?>) nc.getNewTaxonNameInstance(rank);\r
-               return name;\r
-       }\r
-       \r
-\r
-       /**\r
-        * Returns the {@link NomenclaturalCode} for this import. Default is {@link NomenclaturalCode#ICBN} if\r
-        * no code is defined.\r
-        * @param state\r
-        * @return\r
-        */\r
-       protected NomenclaturalCode makeNomenclaturalCode(MarkupImportState state) {\r
-               NomenclaturalCode nc = state.getConfig().getNomenclaturalCode();\r
-               if (nc == null) {\r
-                       nc = NomenclaturalCode.ICNAFP; // default;\r
-               }\r
-               return nc;\r
-       }\r
-\r
-\r
-       /**\r
-        * @param state\r
-        * @param levelString\r
-        * @param next\r
-        * @return\r
-        */\r
-       protected NamedAreaLevel makeNamedAreaLevel(MarkupImportState state, String levelString, XMLEvent next) {\r
-               NamedAreaLevel level;\r
-               try {\r
-                       level = state.getTransformer().getNamedAreaLevelByKey(levelString);\r
-                       if (level == null) {\r
-                               UUID levelUuid = state.getTransformer().getNamedAreaLevelUuid(levelString);\r
-                               if (levelUuid == null) {\r
-                                       String message = "Unknown distribution locality class (named area level): %s. Create new level instead.";\r
-                                       message = String.format(message, levelString);\r
-                                       fireWarningEvent(message, next, 6);\r
-                               }\r
-                               level = getNamedAreaLevel(state, levelUuid, levelString, levelString, levelString, null);\r
-                       }\r
-               } catch (UndefinedTransformerMethodException e) {\r
-                       throw new RuntimeException(e);\r
-               }\r
-               return level;\r
-       }\r
-       \r
-\r
-       /**\r
-        * @param state\r
-        * @param areaName\r
-        * @param level\r
-        * @return \r
-        */\r
-       protected NamedArea makeArea(MarkupImportState state, String areaName, NamedAreaLevel level) {\r
-               \r
-               //TODO FM vocabulary\r
-               TermVocabulary<NamedArea> voc = null; \r
-               NamedAreaType areaType = null;\r
-               \r
-               NamedArea area = null;\r
-               try {\r
-                       area = state.getTransformer().getNamedAreaByKey(areaName);\r
-               } catch (UndefinedTransformerMethodException e) {\r
-                       throw new RuntimeException(e);\r
-               }\r
-               if (area == null){\r
-                       boolean isNewInState = false;\r
-                       UUID uuid = state.getAreaUuid(areaName);\r
-                       if (uuid == null){\r
-                               isNewInState = true;\r
-                               \r
-                               \r
-                               try {\r
-                                       uuid = state.getTransformer().getNamedAreaUuid(areaName);\r
-                               } catch (UndefinedTransformerMethodException e) {\r
-                                       throw new RuntimeException(e);\r
-                               }\r
-                       }\r
-                       \r
-                       CdmImportBase.TermMatchMode matchMode = CdmImportBase.TermMatchMode.UUID_LABEL;\r
-                       area = getNamedArea(state, uuid, areaName, areaName, areaName, areaType, level, voc, matchMode);\r
-                       if (isNewInState){\r
-                               state.putAreaUuid(areaName, area.getUuid());\r
-                               \r
-                               //TODO just for testing -> make generic and move to better place\r
-                               String geoServiceLayer="vmap0_as_bnd_political_boundary_a";\r
-                               String layerFieldName ="nam";\r
-                               \r
-                               if ("Bangka".equals(areaName)){\r
-                                       String areaValue = "PULAU BANGKA#SUMATERA SELATAN";\r
-                                       GeoServiceArea geoServiceArea = new GeoServiceArea();\r
-                                       geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);\r
-                                       this.editGeoService.setMapping(area, geoServiceArea);\r
-//                                     save(area, state);\r
-                               }\r
-                               if ("Luzon".equals(areaName)){\r
-                                       GeoServiceArea geoServiceArea = new GeoServiceArea();\r
-                                       \r
-                                       List<String> list = Arrays.asList("HERMANA MAYOR ISLAND#CENTRAL LUZON",\r
-                                                       "HERMANA MENOR ISLAND#CENTRAL LUZON",\r
-                                                       "CENTRAL LUZON");\r
-                                       for (String areaValue : list){\r
-                                               geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);\r
-                                       }\r
-                                       \r
-                                       this.editGeoService.setMapping(area, geoServiceArea);\r
-//                                     save(area, state);\r
-                               }\r
-                               if ("Mindanao".equals(areaName)){\r
-                                       GeoServiceArea geoServiceArea = new GeoServiceArea();\r
-                                       \r
-                                       List<String> list = Arrays.asList("NORTHERN MINDANAO",\r
-                                                       "SOUTHERN MINDANAO",\r
-                                                       "WESTERN MINDANAO");\r
-                                       //TODO to be continued\r
-                                       for (String areaValue : list){\r
-                                               geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);\r
-                                       }\r
-                                       \r
-                                       this.editGeoService.setMapping(area, geoServiceArea);\r
-//                                     save(area, state);\r
-                               }\r
-                               if ("Palawan".equals(areaName)){\r
-                                       GeoServiceArea geoServiceArea = new GeoServiceArea();\r
-                                       \r
-                                       List<String> list = Arrays.asList("PALAWAN#SOUTHERN TAGALOG");\r
-                                       for (String areaValue : list){\r
-                                               geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);\r
-                                       }\r
-                                       \r
-                                       this.editGeoService.setMapping(area, geoServiceArea);\r
-//                                     save(area, state);\r
-                               }\r
-\r
-                       }\r
-               }\r
-               return area;\r
-       }\r
-\r
-       \r
-       \r
-       /**\r
-        * Reads character data. Any element other than character data or the ending\r
-        * tag will fire an unexpected element event.\r
-     *\r
-        * @see #getCData(MarkupImportState, XMLEventReader, XMLEvent, boolean)\r
-        * @param state\r
-        * @param reader\r
-        * @param next\r
-        * @return\r
-        * @throws XMLStreamException\r
-        */\r
-       protected String getCData(MarkupImportState state, XMLEventReader reader, XMLEvent next) throws XMLStreamException {\r
-               return getCData(state, reader, next, true);\r
-       }\r
-               \r
-       /**\r
-        * Reads character data. Any element other than character data or the ending\r
-        * tag will fire an unexpected element event.\r
-        * \r
-        * @param state\r
-        * @param reader\r
-        * @param next\r
-        * @param inlineMarkup map for inline markup, this is used for e.g. the locality markup within a subheading\r
-        * The map will be filled by the markup element name as key. The value may be a String, a CdmBase or any other object.\r
-        * If null any markup text will be neglected but a warning will be fired if they exist.\r
-        * @param removeInlineMarkupText if true the markedup text will be removed from the returned String \r
-        * @param checkAttributes\r
-        * @return\r
-        * @throws XMLStreamException\r
-        */\r
-       protected String getCData(MarkupImportState state, XMLEventReader reader, XMLEvent parent, /*Map<String, Object> inlineMarkup, *boolean removeInlineMarkupText,*/ boolean checkAttributes) throws XMLStreamException {\r
-               if (checkAttributes){\r
-                       checkNoAttributes(parent);\r
-               }\r
-\r
-               String text = "";\r
-               while (reader.hasNext()) {\r
-                       XMLEvent next = readNoWhitespace(reader);\r
-                       if (isMyEndingElement(next, parent)) {\r
-                               return text;\r
-                       } else if (next.isCharacters()) {\r
-                               text += next.asCharacters().getData();\r
-                       } else if (isStartingElement(next, FOOTNOTE_REF)){\r
-                               handleNotYetImplementedElement(next);\r
-//                     } else if (isStartingElement(next, LOCALITY)){\r
-//                             handleCDataLocality(state, reader, parent);\r
-                       } else {\r
-                               handleUnexpectedElement(next);\r
-                       }\r
-               }\r
-               throw new IllegalStateException("Event has no closing tag");\r
-\r
-       }\r
-       \r
-//     private void handleCDataLocality(MarkupImportState state, XMLEventReader reader, XMLEvent parent) {\r
-//             checkAndRemoveAttributeValue(attributes, attrName, value)\r
-//             \r
-//     }\r
-\r
-\r
-\r
-       /**\r
-        * For it returns a pure CData annotation string. This behaviour may change in future. More complex annotations\r
-        * should be handled differently.\r
-        * @param state\r
-        * @param reader\r
-        * @param parentEvent\r
-        * @return\r
-        * @throws XMLStreamException\r
-        */\r
-       protected String handleSimpleAnnotation(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {\r
-               String annotation = getCData(state, reader, parentEvent);\r
-               return annotation;\r
-       }\r
-       \r
-       /**\r
-        * True if text is single "." oder "," or ";" or ":"\r
-        * @param text\r
-        * @return\r
-        */\r
-       protected boolean isPunctuation(String text) {\r
-               return text == null ? false : text.trim().matches("^[\\.,;:]$");\r
-       }\r
-\r
-       \r
-       protected String getXmlTag(XMLEvent event) {\r
-               String result;\r
-               if (event.isStartElement()) {\r
-                       result = "<" + event.asStartElement().getName().getLocalPart()\r
-                                       + ">";\r
-               } else if (event.isEndElement()) {\r
-                       result = "</" + event.asEndElement().getName().getLocalPart() + ">";\r
-               } else {\r
-                       String message = "Only start or end elements are allowed as Html tags";\r
-                       throw new IllegalStateException(message);\r
-               }\r
-               return result;\r
-       }\r
-\r
-       protected WriterDataHolder handleWriter(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {\r
-               String text = "";\r
-               checkNoAttributes(parentEvent);\r
-               WriterDataHolder dataHolder = new WriterDataHolder();\r
-               List<FootnoteDataHolder> footnotes = new ArrayList<FootnoteDataHolder>();\r
-\r
-               // TODO handle attributes\r
-               while (reader.hasNext()) {\r
-                       XMLEvent next = readNoWhitespace(reader);\r
-                       if (isMyEndingElement(next, parentEvent)) {\r
-                               text = CdmUtils.removeBrackets(text);\r
-                               if (checkMandatoryText(text, parentEvent)) {\r
-                                       text = normalize(text);\r
-                                       dataHolder.writer = text;\r
-                                       dataHolder.footnotes = footnotes;\r
-\r
-                                       // Extension\r
-                                       UUID uuidWriterExtension = MarkupTransformer.uuidWriterExtension;\r
-                                       ExtensionType writerExtensionType = this\r
-                                                       .getExtensionType(state, uuidWriterExtension,\r
-                                                                       "Writer", "writer", "writer");\r
-                                       Extension extension = Extension.NewInstance();\r
-                                       extension.setType(writerExtensionType);\r
-                                       extension.setValue(text);\r
-                                       dataHolder.extension = extension;\r
-\r
-                                       // Annotation\r
-                                       UUID uuidWriterAnnotation = MarkupTransformer.uuidWriterAnnotation;\r
-                                       AnnotationType writerAnnotationType = this.getAnnotationType(state, uuidWriterAnnotation, "Writer", "writer", "writer", null);\r
-                                       Annotation annotation = Annotation.NewInstance(text, writerAnnotationType, getDefaultLanguage(state));\r
-                                       dataHolder.annotation = annotation;\r
-\r
-                                       return dataHolder;\r
-                               } else {\r
-                                       return null;\r
-                               }\r
-                       } else if (isStartingElement(next, FOOTNOTE_REF)) {\r
-                               FootnoteDataHolder footNote = handleFootnoteRef(state, reader, next);\r
-                               if (footNote.isRef()) {\r
-                                       footnotes.add(footNote);\r
-                               } else {\r
-                                       logger.warn("Non ref footnotes not yet impelemnted");\r
-                               }\r
-                       } else if (next.isCharacters()) {\r
-                               text += next.asCharacters().getData();\r
-\r
-                       } else {\r
-                               handleUnexpectedElement(next);\r
-                               state.setUnsuccessfull();\r
-                       }\r
-               }\r
-               throw new IllegalStateException("<writer> has no end tag");\r
-       }\r
-       \r
-\r
-       protected void registerFootnotes(MarkupImportState state, AnnotatableEntity entity, List<FootnoteDataHolder> footnotes) {\r
-               for (FootnoteDataHolder footNote : footnotes) {\r
-                       registerFootnoteDemand(state, entity, footNote);\r
-               }\r
-       }\r
-       \r
-\r
-       private void registerFootnoteDemand(MarkupImportState state, AnnotatableEntity entity, FootnoteDataHolder footnote) {\r
-               FootnoteDataHolder existingFootnote = state.getFootnote(footnote.ref);\r
-               if (existingFootnote != null) {\r
-                       attachFootnote(state, entity, existingFootnote);\r
-               } else {\r
-                       Set<AnnotatableEntity> demands = state.getFootnoteDemands(footnote.ref);\r
-                       if (demands == null) {\r
-                               demands = new HashSet<AnnotatableEntity>();\r
-                               state.putFootnoteDemands(footnote.ref, demands);\r
-                       }\r
-                       demands.add(entity);\r
-               }\r
-       }\r
-       \r
-\r
-       protected void attachFootnote(MarkupImportState state, AnnotatableEntity entity, FootnoteDataHolder footnote) {\r
-               AnnotationType annotationType = this.getAnnotationType(state, MarkupTransformer.uuidFootnote, "Footnote", "An e-flora footnote", "fn", null);\r
-               Annotation annotation = Annotation.NewInstance(footnote.string, annotationType, getDefaultLanguage(state));\r
-               // TODO transient objects\r
-               entity.addAnnotation(annotation);\r
-               save(entity, state);\r
-       }\r
-       \r
-\r
-       protected void attachFigure(MarkupImportState state, XMLEvent next, AnnotatableEntity entity, Media figure) {\r
-               // IdentifiableEntity<?> toSave;\r
-               if (entity.isInstanceOf(TextData.class)) {\r
-                       TextData deb = CdmBase.deproxy(entity, TextData.class);\r
-                       deb.addMedia(figure);\r
-                       // toSave = ((TaxonDescription)deb.getInDescription()).getTaxon();\r
-               } else if (entity.isInstanceOf(SpecimenOrObservationBase.class)) {\r
-                       String message = "figures for specimen should be handled as Textdata";\r
-                       fireWarningEvent(message, next, 4);\r
-                       // toSave = ime;\r
-               } else if (entity.isInstanceOf(IdentifiableMediaEntity.class)) {\r
-                       IdentifiableMediaEntity<?> ime = CdmBase.deproxy(entity, IdentifiableMediaEntity.class);\r
-                       ime.addMedia(figure);\r
-                       // toSave = ime;\r
-               } else {\r
-                       String message = "Unsupported entity to attach media: %s";\r
-                       message = String.format(message, entity.getClass().getName());\r
-                       // toSave = null;\r
-               }\r
-               save(entity, state);\r
-       }\r
-       \r
-\r
-       protected void registerGivenFootnote(MarkupImportState state, FootnoteDataHolder footnote) {\r
-               state.registerFootnote(footnote);\r
-               Set<AnnotatableEntity> demands = state.getFootnoteDemands(footnote.id);\r
-               if (demands != null) {\r
-                       for (AnnotatableEntity entity : demands) {\r
-                               attachFootnote(state, entity, footnote);\r
-                       }\r
-               }\r
-       }\r
-       \r
-\r
-       protected FootnoteDataHolder handleFootnote(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, \r
-                       MarkupSpecimenImport specimenImport, MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {\r
-               FootnoteDataHolder result = new FootnoteDataHolder();\r
-               Map<String, Attribute> attributes = getAttributes(parentEvent);\r
-               result.id = getAndRemoveAttributeValue(attributes, ID);\r
-               // result.ref = getAndRemoveAttributeValue(attributes, REF);\r
-               checkNoAttributes(attributes, parentEvent);\r
-\r
-               while (reader.hasNext()) {\r
-                       XMLEvent next = readNoWhitespace(reader);\r
-                       if (isStartingElement(next, FOOTNOTE_STRING)) {\r
-                               String string = handleFootnoteString(state, reader, next, specimenImport, nomenclatureImport);\r
-                               result.string = string;\r
-                       } else if (isMyEndingElement(next, parentEvent)) {\r
-                               return result;\r
-                       } else {\r
-                               fireUnexpectedEvent(next, 0);\r
-                       }\r
-               }\r
-               return result;\r
-       }\r
-       \r
-\r
-       protected Media handleFigure(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, \r
-                       MarkupSpecimenImport specimenImport, MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {\r
-               // FigureDataHolder result = new FigureDataHolder();\r
-\r
-               Map<String, Attribute> attributes = getAttributes(parentEvent);\r
-               String id = getAndRemoveAttributeValue(attributes, ID);\r
-               String type = getAndRemoveAttributeValue(attributes, TYPE);\r
-               String urlAttr = getAndRemoveAttributeValue(attributes, URL);\r
-               checkNoAttributes(attributes, parentEvent);\r
-\r
-               String urlString = null;\r
-               String legendString = null;\r
-               String titleString = null;\r
-               String numString = null;\r
-               String text = null;\r
-               if (isNotBlank(urlAttr)){\r
-                       urlString = CdmUtils.Nz(state.getBaseMediaUrl()) + urlAttr;\r
-               }\r
-               while (reader.hasNext()) {\r
-                       XMLEvent next = readNoWhitespace(reader);\r
-                       if (isMyEndingElement(next, parentEvent)) {\r
-                               if (isNotBlank(text)){\r
-                                       fireWarningEvent("Text not yet handled for figures: " + text, next, 4);\r
-                               }\r
-                               Media media = makeFigure(state, id, type, urlString, legendString, titleString, numString, next);\r
-                               return media;\r
-                       } else if (isStartingElement(next, FIGURE_LEGEND)) {\r
-                               // TODO same as figure string ?\r
-                               legendString = handleFootnoteString(state, reader, next, specimenImport, nomenclatureImport);\r
-                       } else if (isStartingElement(next, FIGURE_TITLE)) {\r
-                               titleString = getCData(state, reader, next);\r
-                       } else if (isStartingElement(next, URL)) {\r
-                               String localUrl = getCData(state, reader, next);\r
-                               String url = CdmUtils.Nz(state.getBaseMediaUrl()) + localUrl;\r
-                               if (isBlank(urlString)){\r
-                                       urlString = url;\r
-                               }\r
-                               if (! url.equals(urlString)){\r
-                                       String message = "URL attribute and URL element differ. Attribute: %s, Element: %s";\r
-                                       fireWarningEvent(String.format(message, urlString, url), next, 2);\r
-                               }\r
-                       } else if (isStartingElement(next, NUM)) {\r
-                               numString = getCData(state, reader, next);\r
-                       } else if (next.isCharacters()) {\r
-                               text += CdmUtils.concat("", text, next.asCharacters().getData());\r
-                       } else {\r
-                               fireUnexpectedEvent(next, 0);\r
-                       }\r
-               }\r
-               throw new IllegalStateException("<figure> has no end tag");\r
-       }\r
-\r
-\r
-       /**\r
-        * @param state\r
-        * @param id\r
-        * @param type\r
-        * @param urlString\r
-        * @param legendString\r
-        * @param titleString\r
-        * @param numString\r
-        * @param next\r
-        */\r
-       private Media makeFigure(MarkupImportState state, String id, String type, String urlString, \r
-                       String legendString, String titleString, String numString, XMLEvent next) {\r
-               Media media = null;\r
-               boolean isFigure = false;\r
-               try {\r
-                       //TODO maybe everything is a figure as it is all taken from a book\r
-                       if ("lineart".equals(type)) {\r
-                               isFigure = true;\r
-//                             media = Figure.NewInstance(url.toURI(), null, null,     null);\r
-                       } else if (type == null || "photo".equals(type)\r
-                                       || "signature".equals(type)\r
-                                       || "others".equals(type)) {\r
-                               //TODO\r
-                       } else {\r
-                               String message = "Unknown figure type '%s'";\r
-                               message = String.format(message, type);\r
-                               fireWarningEvent(message, next, 2);\r
-                       }\r
-                       media = docImport.getImageMedia(urlString, docImport.getReadMediaData(), isFigure);\r
-                       \r
-                       if (media != null){\r
-                               // title\r
-                               if (StringUtils.isNotBlank(titleString)) {\r
-                                       media.putTitle(getDefaultLanguage(state), titleString);\r
-                               }\r
-                               // legend\r
-                               if (StringUtils.isNotBlank(legendString)) {\r
-                                       media.putDescription(getDefaultLanguage(state), legendString);\r
-                               }\r
-                               if (StringUtils.isNotBlank(numString)) {\r
-                                       // TODO use concrete source (e.g. DAPHNIPHYLLACEAE in FM\r
-                                       // vol.13)\r
-                                       Reference<?> citation = state.getConfig().getSourceReference();\r
-                                       media.addSource(OriginalSourceType.Import, numString, "num", citation, null);\r
-                                       // TODO name used in source if available\r
-                               }\r
-                               // TODO which citation\r
-                               if (StringUtils.isNotBlank(id)) {\r
-                                       media.addSource(OriginalSourceType.Import, id, null, state.getConfig().getSourceReference(), null);\r
-                               } else {\r
-                                       String message = "Figure id should never be empty or null";\r
-                                       fireWarningEvent(message, next, 6);\r
-                               }\r
-\r
-                               // text\r
-                               // do nothing\r
-                               registerGivenFigure(state, next, id, media);\r
-                               \r
-                       }else{\r
-                               String message = "No media found: ";\r
-                               fireWarningEvent(message, next, 4);\r
-                       }\r
-               } catch (MalformedURLException e) {\r
-                       String message = "Media uri has incorrect syntax: %s";\r
-                       message = String.format(message, urlString);\r
-                       fireWarningEvent(message, next, 4);\r
-//             } catch (URISyntaxException e) {\r
-//                     String message = "Media uri has incorrect syntax: %s";\r
-//                     message = String.format(message, urlString);\r
-//                     fireWarningEvent(message, next, 4);\r
-               }\r
-\r
-               return media;\r
-       }\r
-       \r
-\r
-       private void registerGivenFigure(MarkupImportState state, XMLEvent next, String id, Media figure) {\r
-               state.registerFigure(id, figure);\r
-               Set<AnnotatableEntity> demands = state.getFigureDemands(id);\r
-               if (demands != null) {\r
-                       for (AnnotatableEntity entity : demands) {\r
-                               attachFigure(state, next, entity, figure);\r
-                       }\r
-               }\r
-               save(figure, state);\r
-       }\r
-       \r
-\r
-       private FootnoteDataHolder handleFootnoteRef(MarkupImportState state,\r
-                       XMLEventReader reader, XMLEvent parentEvent)\r
-                       throws XMLStreamException {\r
-               FootnoteDataHolder result = new FootnoteDataHolder();\r
-               Map<String, Attribute> attributes = getAttributes(parentEvent);\r
-               result.ref = getAndRemoveAttributeValue(attributes, REF);\r
-               checkNoAttributes(attributes, parentEvent);\r
-\r
-               // text is not handled, needed only for debugging purposes\r
-               String text = "";\r
-               while (reader.hasNext()) {\r
-                       XMLEvent next = readNoWhitespace(reader);\r
-                       // if (isStartingElement(next, FOOTNOTE_STRING)){\r
-                       // String string = handleFootnoteString(state, reader, next);\r
-                       // result.string = string;\r
-                       // }else\r
-                       if (isMyEndingElement(next, parentEvent)) {\r
-                               return result;\r
-                       } else if (next.isCharacters()) {\r
-                               text += next.asCharacters().getData();\r
-\r
-                       } else {\r
-                               fireUnexpectedEvent(next, 0);\r
-                       }\r
-               }\r
-               return result;\r
-       }\r
-\r
-\r
-\r
-       private String handleFootnoteString(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, MarkupSpecimenImport specimenImport, MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {\r
-               boolean isTextMode = true;\r
-               String text = "";\r
-               while (reader.hasNext()) {\r
-                       XMLEvent next = readNoWhitespace(reader);\r
-                       if (isMyEndingElement(next, parentEvent)) {\r
-                               return text;\r
-                       } else if (next.isEndElement()) {\r
-                               if (isEndingElement(next, FULL_NAME)) {\r
-                                       popUnimplemented(next.asEndElement());\r
-                               } else if (isEndingElement(next, BR)) {\r
-                                       isTextMode = true;\r
-                               } else if (isHtml(next)) {\r
-                                       text += getXmlTag(next);\r
-                               } else {\r
-                                       handleUnexpectedEndElement(next.asEndElement());\r
-                               }\r
-                       } else if (next.isStartElement()) {\r
-                               if (isStartingElement(next, FULL_NAME)) {\r
-                                       handleNotYetImplementedElement(next);\r
-                               } else if (isStartingElement(next, GATHERING)) {\r
-                                       text += specimenImport.handleInLineGathering(state, reader, next);\r
-                               } else if (isStartingElement(next, REFERENCES)) {\r
-                                       text += " " + handleInLineReferences(state, reader, next, nomenclatureImport)+ " ";\r
-                               } else if (isStartingElement(next, BR)) {\r
-                                       text += "<br/>";\r
-                                       isTextMode = false;\r
-                               } else if (isStartingElement(next, NOMENCLATURE)) {\r
-                                       handleNotYetImplementedElement(next);\r
-                               } else if (isHtml(next)) {\r
-                                       text += getXmlTag(next);\r
-                               } else {\r
-                                       handleUnexpectedStartElement(next.asStartElement());\r
-                               }\r
-                       } else if (next.isCharacters()) {\r
-                               if (!isTextMode) {\r
-                                       String message = "footnoteString is not in text mode";\r
-                                       fireWarningEvent(message, next, 6);\r
-                               } else {\r
-                                       text += next.asCharacters().getData().trim(); \r
-                                       // getCData(state, reader, next); does not work as we have inner tags like <references>\r
-                               }\r
-                       } else {\r
-                               handleUnexpectedEndElement(next.asEndElement());\r
-                       }\r
-               }\r
-               throw new IllegalStateException("<footnoteString> has no closing tag");\r
-\r
-       }\r
-\r
-       private static final List<String> htmlList = Arrays.asList("sub", "sup",\r
-                       "ol", "ul", "li", "i", "b", "table", "br","tr","td");\r
-\r
-       protected boolean isHtml(XMLEvent event) {\r
-               if (event.isStartElement()) {\r
-                       String tag = event.asStartElement().getName().getLocalPart();\r
-                       return htmlList.contains(tag);\r
-               } else if (event.isEndElement()) {\r
-                       String tag = event.asEndElement().getName().getLocalPart();\r
-                       return htmlList.contains(tag);\r
-               } else {\r
-                       return false;\r
-               }\r
-\r
-       }\r
-       \r
-\r
-       private String handleInLineReferences(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent, MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {\r
-               checkNoAttributes(parentEvent);\r
-\r
-               boolean hasReference = false;\r
-               String text = "";\r
-               while (reader.hasNext()) {\r
-                       XMLEvent next = readNoWhitespace(reader);\r
-                       if (isMyEndingElement(next, parentEvent)) {\r
-                               checkMandatoryElement(hasReference, parentEvent.asStartElement(), REFERENCE);\r
-                               return text;\r
-                       } else if (isStartingElement(next, REFERENCE)) {\r
-                               text += handleInLineReference(state, reader, next, nomenclatureImport);\r
-                               hasReference = true;\r
-                       } else {\r
-                               handleUnexpectedElement(next);\r
-                       }\r
-               }\r
-               throw new IllegalStateException("<References> has no closing tag");\r
-       }\r
-\r
-       private String handleInLineReference(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent, MarkupNomenclatureImport nomenclatureImport)throws XMLStreamException {\r
-               Reference<?> reference = nomenclatureImport.handleReference(state, reader, parentEvent);\r
-               String result = "<cdm:ref uuid='%s'>%s</ref>";\r
-               result = String.format(result, reference.getUuid(), reference.getTitleCache());\r
-               save(reference, state);\r
-               return result;\r
-       }\r
-       \r
-\r
-       /**\r
-        * Handle string\r
-        * @param state\r
-        * @param reader\r
-        * @param parentEvent\r
-        * @param feature only needed for distributionLocalities\r
-        * @return\r
-        * @throws XMLStreamException\r
-        */\r
-       protected Map<String, String> handleString(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, Feature feature)throws XMLStreamException {\r
-               // attributes\r
-               String classValue = getClassOnlyAttribute(parentEvent, false);\r
-               if (StringUtils.isNotBlank(classValue)) {\r
-                       String message = "class attribute for <string> not yet implemented";\r
-                       fireWarningEvent(message, parentEvent, 2);\r
-               }\r
-\r
-               // subheadings\r
-               Map<String, String> subHeadingMap = new HashMap<String, String>();\r
-               String currentSubheading = null;\r
-\r
-               boolean isTextMode = true;\r
-               String text = "";\r
-               while (reader.hasNext()) {\r
-                       XMLEvent next = readNoWhitespace(reader);\r
-                       if (isMyEndingElement(next, parentEvent)) {\r
-                               putCurrentSubheading(subHeadingMap, currentSubheading, text);\r
-                               return subHeadingMap;\r
-                       } else if (isStartingElement(next, BR)) {\r
-                               text += "<br/>";\r
-                               isTextMode = false;\r
-                       } else if (isEndingElement(next, BR)) {\r
-                               isTextMode = true;\r
-                       } else if (isHtml(next)) {\r
-                               text += getXmlTag(next);\r
-                       } else if (isStartingElement(next, SUB_HEADING)) {\r
-                               text = putCurrentSubheading(subHeadingMap,currentSubheading, text);\r
-                               // TODO footnotes\r
-                               currentSubheading = getCData(state, reader, next).trim();\r
-                       } else if (isStartingElement(next, DISTRIBUTION_LOCALITY)) {\r
-                               if (feature != null && !feature.equals(Feature.DISTRIBUTION())) {\r
-                                       String message = "Distribution locality only allowed for feature of type 'distribution'";\r
-                                       fireWarningEvent(message, next, 4);\r
-                               }\r
-                               text += handleDistributionLocality(state, reader, next);\r
-                       } else if (next.isCharacters()) {\r
-                               if (! isTextMode) {\r
-                                       String message = "String is not in text mode";\r
-                                       fireWarningEvent(message, next, 6);\r
-                               } else {\r
-                                       text += next.asCharacters().getData();\r
-                               }\r
-                       } else if (isStartingElement(next, HEADING)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, VERNACULAR_NAMES)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, QUOTE)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, DEDICATION)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, TAXONTYPE)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, FULL_NAME)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       }else if (isStartingElement(next, REFERENCES)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, GATHERING)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, ANNOTATION)) {\r
-                               //TODO  //TODO test handleSimpleAnnotation\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, HABITAT)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, FIGURE_REF)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, FIGURE)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, FOOTNOTE_REF)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, FOOTNOTE)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, WRITER)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, DATES)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       } else {\r
-                               handleUnexpectedElement(next);\r
-                       }\r
-               }\r
-               throw new IllegalStateException("<String> has no closing tag");\r
-       }\r
-       \r
-\r
-       /**\r
-        * @param subHeadingMap\r
-        * @param currentSubheading\r
-        * @param text\r
-        * @return\r
-        */\r
-       private String putCurrentSubheading(Map<String, String> subHeadingMap, String currentSubheading, String text) {\r
-               if (StringUtils.isNotBlank(text)) {\r
-                       text = removeStartingMinus(text);\r
-                       subHeadingMap.put(currentSubheading, text.trim());\r
-               }\r
-               return "";\r
-       }\r
-\r
-       private String removeStartingMinus(String string) {\r
-               string = replaceStart(string, "-");\r
-               string = replaceStart(string, "\u002d");\r
-               string = replaceStart(string, "\u2013");\r
-               string = replaceStart(string, "\u2014");\r
-               string = replaceStart(string, "--");\r
-               return string;\r
-       }\r
-       \r
-       \r
-       /**\r
-        * @param value\r
-        * @param replacementString\r
-        */\r
-       private String replaceStart(String value, String replacementString) {\r
-               if (value.startsWith(replacementString) ){\r
-                       value = value.substring(replacementString.length()).trim();\r
-               }\r
-               while (value.startsWith("-") || value.startsWith("\u2014") ){\r
-                       value = value.substring("-".length()).trim();\r
-               }\r
-               return value;\r
-       }\r
-       \r
-\r
-       private String handleDistributionLocality(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent)throws XMLStreamException {\r
-               Map<String, Attribute> attributes = getAttributes(parentEvent);\r
-               String classValue = getAndRemoveRequiredAttributeValue(parentEvent, attributes, CLASS);\r
-               String statusValue =getAndRemoveAttributeValue(attributes, STATUS);\r
-               String frequencyValue =getAndRemoveAttributeValue(attributes, FREQUENCY);\r
-               \r
-\r
-               Taxon taxon = state.getCurrentTaxon();\r
-               // TODO which ref to take?\r
-               Reference<?> ref = state.getConfig().getSourceReference();\r
-\r
-               String text = "";\r
-               while (reader.hasNext()) {\r
-                       XMLEvent next = readNoWhitespace(reader);\r
-                       if (isMyEndingElement(next, parentEvent)) {\r
-                               if (StringUtils.isNotBlank(text)) {\r
-                                       String label = CdmUtils.removeTrailingDot(normalize(text));\r
-                                       TaxonDescription description = getTaxonDescription(taxon, ref, false, true);\r
-                                       NamedAreaLevel level = makeNamedAreaLevel(state,classValue, next);\r
-                                       \r
-                                       //status\r
-                                       PresenceAbsenceTermBase<?> status = null;\r
-                                       if (isNotBlank(statusValue)){\r
-                                               try {\r
-                                                       status = state.getTransformer().getPresenceTermByKey(statusValue);\r
-                                                       if (status == null){\r
-                                                               //TODO\r
-                                                               String message = "The presence/absence status '%s' could not be transformed to an CDM status";                                                          \r
-                                                               fireWarningEvent(String.format(message, statusValue), next, 4);\r
-                                                       }\r
-                                               } catch (UndefinedTransformerMethodException e) {\r
-                                                       throw new RuntimeException(e);\r
-                                               }\r
-                                       }else{\r
-                                               status = PresenceTerm.PRESENT();\r
-                                       }\r
-                                       //frequency\r
-                                       if (isNotBlank(frequencyValue)){\r
-                                               String message = "The frequency attribute is currently not yet available in CDM";\r
-                                               fireWarningEvent(message, parentEvent, 6);\r
-                                       }\r
-                                       \r
-                                       NamedArea higherArea = null;\r
-                                       List<NamedArea> areas = new ArrayList<NamedArea>(); \r
-                                       \r
-                                       String patSingleArea = "([^,\\(]{3,})";\r
-                                       String patSeparator = "(,|\\sand\\s)";\r
-                                       String hierarchiePattern = String.format("%s\\((%s(%s%s)*)\\)",patSingleArea, patSingleArea, patSeparator, patSingleArea);\r
-                                       Pattern patHierarchie = Pattern.compile(hierarchiePattern, Pattern.CASE_INSENSITIVE);\r
-                                       Matcher matcher = patHierarchie.matcher(label); \r
-                                       if (matcher.matches()){\r
-                                               String higherAreaStr = matcher.group(1).trim();\r
-                                               higherArea =  makeArea(state, higherAreaStr, level);\r
-                                               String[] innerAreas = matcher.group(2).split(patSeparator);\r
-                                               for (String innerArea : innerAreas){\r
-                                                       if (isNotBlank(innerArea)){\r
-                                                               NamedArea singleArea = makeArea(state, innerArea.trim(), level);\r
-                                                               areas.add(singleArea);\r
-                                                               NamedArea partOf = singleArea.getPartOf();\r
-//                                                             if (partOf == null){\r
-//                                                                     singleArea.setPartOf(higherArea);\r
-//                                                             }\r
-                                                       }\r
-                                               }\r
-                                       }else{\r
-                                               NamedArea singleArea = makeArea(state, label, level);\r
-                                               areas.add(singleArea);\r
-                                       }\r
-                                       \r
-                                       for (NamedArea area : areas){\r
-                                               //create distribution\r
-                                               Distribution distribution = Distribution.NewInstance(area,status);\r
-                                               description.addElement(distribution);\r
-                                       }\r
-                               } else {\r
-                                       String message = "Empty distribution locality";\r
-                                       fireWarningEvent(message, next, 4);\r
-                               }\r
-                               return text;\r
-                       } else if (isStartingElement(next, COORDINATES)) {\r
-                               //TODO\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isEndingElement(next, COORDINATES)) {\r
-                               //TODO\r
-                               popUnimplemented(next.asEndElement());\r
-                       } else if (next.isCharacters()) {\r
-                               text += next.asCharacters().getData();\r
-                       } else {\r
-                               handleUnexpectedElement(next);\r
-                       }\r
-               }\r
-               throw new IllegalStateException("<DistributionLocality> has no closing tag");\r
-       }       \r
-\r
-       \r
-//********************************************** OLD *************************************     \r
-\r
-//     protected boolean testAdditionalElements(Element parentElement, List<String> excludeList){\r
-//             boolean result = true;\r
-//             List<Element> list = parentElement.getChildren();\r
-//             for (Element element : list){\r
-//                     if (! excludeList.contains(element.getName())){\r
-//                             logger.warn("Unknown element (" + element.getName() + ") in parent element (" + parentElement.getName() + ")");\r
-//                             result = false;\r
-//                     }\r
-//             }\r
-//             return result;\r
-//     }\r
-//     \r
-//     \r
-//     protected <T extends IdentifiableEntity> T makeReferenceType(Element element, Class<? extends T> clazz, MapWrapper<? extends T> objectMap, ResultWrapper<Boolean> success){\r
-//             T result = null;\r
-//             String linkType = element.getAttributeValue("linkType");\r
-//             String ref = element.getAttributeValue("ref");\r
-//             if(ref == null && linkType == null){\r
-//                     result = getInstance(clazz);\r
-//                     if (result != null){\r
-//                             String title = element.getTextNormalize();\r
-//                             result.setTitleCache(title, true);\r
-//                     }\r
-//             }else if (linkType == null || linkType.equals("local")){\r
-//                     //TODO\r
-//                     result = objectMap.get(ref);\r
-//                     if (result == null){\r
-//                             logger.warn("Object (ref = " + ref + ")could not be found in WrapperMap");\r
-//                     }\r
-//             }else if(linkType.equals("external")){\r
-//                     logger.warn("External link types not yet implemented");\r
-//             }else if(linkType.equals("other")){\r
-//                     logger.warn("Other link types not yet implemented");\r
-//             }else{\r
-//                     logger.warn("Unknown link type or missing ref");\r
-//             }\r
-//             if (result == null){\r
-//                     success.setValue(false);\r
-//             }\r
-//             return result;\r
-//     }\r
-//     \r
-//     \r
-//     protected Reference makeAccordingTo(Element elAccordingTo, MapWrapper<Reference> referenceMap, ResultWrapper<Boolean> success){\r
-//             Reference result = null;\r
-//             if (elAccordingTo != null){\r
-//                     String childName = "AccordingToDetailed";\r
-//                     boolean obligatory = false;\r
-//                     Element elAccordingToDetailed = XmlHelp.getSingleChildElement(success, elAccordingTo, childName, elAccordingTo.getNamespace(), obligatory);\r
-//\r
-//                     childName = "Simple";\r
-//                     obligatory = true;\r
-//                     Element elSimple = XmlHelp.getSingleChildElement(success, elAccordingTo, childName, elAccordingTo.getNamespace(), obligatory);\r
-//                     \r
-//                     if (elAccordingToDetailed != null){\r
-//                             result = makeAccordingToDetailed(elAccordingToDetailed, referenceMap, success);\r
-//                     }else{\r
-//                             result = ReferenceFactory.newGeneric();\r
-//                             String title = elSimple.getTextNormalize();\r
-//                             result.setTitleCache(title, true);\r
-//                     }\r
-//             }\r
-//             return result;\r
-//     }\r
-//     \r
-//     \r
-//     private Reference makeAccordingToDetailed(Element elAccordingToDetailed, MapWrapper<Reference> referenceMap, ResultWrapper<Boolean> success){\r
-//             Reference result = null;\r
-//             Namespace tcsNamespace = elAccordingToDetailed.getNamespace();\r
-//             if (elAccordingToDetailed != null){\r
-//                     //AuthorTeam\r
-//                     String childName = "AuthorTeam";\r
-//                     boolean obligatory = false;\r
-//                     Element elAuthorTeam = XmlHelp.getSingleChildElement(success, elAccordingToDetailed, childName, tcsNamespace, obligatory);\r
-//                     makeAccordingToAuthorTeam(elAuthorTeam, success);\r
-//                     \r
-//                     //PublishedIn\r
-//                     childName = "PublishedIn";\r
-//                     obligatory = false;\r
-//                     Element elPublishedIn = XmlHelp.getSingleChildElement(success, elAccordingToDetailed, childName, tcsNamespace, obligatory);\r
-//                     result = makeReferenceType(elPublishedIn, Reference.class, referenceMap, success);\r
-//                     \r
-//                     //MicroReference\r
-//                     childName = "MicroReference";\r
-//                     obligatory = false;\r
-//                     Element elMicroReference = XmlHelp.getSingleChildElement(success, elAccordingToDetailed, childName, tcsNamespace, obligatory);\r
-//                     String microReference = elMicroReference.getTextNormalize();\r
-//                     if (CdmUtils.Nz(microReference).equals("")){\r
-//                             //TODO\r
-//                             logger.warn("MicroReference not yet implemented for AccordingToDetailed");      \r
-//                     }\r
-//             }\r
-//             return result;\r
-//     }\r
-//\r
-//     private Team makeAccordingToAuthorTeam(Element elAuthorTeam, ResultWrapper<Boolean> succes){\r
-//             Team result = null;\r
-//             if (elAuthorTeam != null){\r
-//                     //TODO\r
-//                     logger.warn("AuthorTeam not yet implemented for AccordingToDetailed");\r
-//             }\r
-//             return result;\r
-//     }\r
-\r
-\r
-\r
-}\r
+/**
+* Copyright (C) 2007 EDIT
+* European Distributed Institute of Taxonomy
+* http://www.e-taxonomy.eu
+*
+* The contents of this file are subject to the Mozilla Public License Version 1.1
+* See LICENSE.TXT at the top of this package for the full license terms.
+*/
+
+package eu.etaxonomy.cdm.io.markup;
+
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.Location;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.WordUtils;
+import org.apache.log4j.Logger;
+
+import eu.etaxonomy.cdm.api.service.IClassificationService;
+import eu.etaxonomy.cdm.api.service.ITermService;
+import eu.etaxonomy.cdm.common.CdmUtils;
+import eu.etaxonomy.cdm.ext.geo.GeoServiceArea;
+import eu.etaxonomy.cdm.ext.geo.IEditGeoService;
+import eu.etaxonomy.cdm.io.common.CdmImportBase;
+import eu.etaxonomy.cdm.io.common.CdmImportBase.TermMatchMode;
+import eu.etaxonomy.cdm.io.common.events.IIoEvent;
+import eu.etaxonomy.cdm.io.common.events.IoProblemEvent;
+import eu.etaxonomy.cdm.io.common.mapping.UndefinedTransformerMethodException;
+import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
+import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
+import eu.etaxonomy.cdm.model.common.Annotation;
+import eu.etaxonomy.cdm.model.common.AnnotationType;
+import eu.etaxonomy.cdm.model.common.CdmBase;
+import eu.etaxonomy.cdm.model.common.DefinedTerm;
+import eu.etaxonomy.cdm.model.common.DefinedTermBase;
+import eu.etaxonomy.cdm.model.common.Extension;
+import eu.etaxonomy.cdm.model.common.ExtensionType;
+import eu.etaxonomy.cdm.model.common.IntextReference;
+import eu.etaxonomy.cdm.model.common.Language;
+import eu.etaxonomy.cdm.model.common.MarkerType;
+import eu.etaxonomy.cdm.model.common.OriginalSourceType;
+import eu.etaxonomy.cdm.model.common.TermVocabulary;
+import eu.etaxonomy.cdm.model.common.TimePeriod;
+import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
+import eu.etaxonomy.cdm.model.description.Distribution;
+import eu.etaxonomy.cdm.model.description.Feature;
+import eu.etaxonomy.cdm.model.description.PolytomousKey;
+import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
+import eu.etaxonomy.cdm.model.description.TaxonDescription;
+import eu.etaxonomy.cdm.model.description.TextData;
+import eu.etaxonomy.cdm.model.location.NamedArea;
+import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
+import eu.etaxonomy.cdm.model.location.NamedAreaType;
+import eu.etaxonomy.cdm.model.media.IdentifiableMediaEntity;
+import eu.etaxonomy.cdm.model.media.Media;
+import eu.etaxonomy.cdm.model.name.INonViralName;
+import eu.etaxonomy.cdm.model.name.NomenclaturalCode;
+import eu.etaxonomy.cdm.model.name.NonViralName;
+import eu.etaxonomy.cdm.model.name.Rank;
+import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
+import eu.etaxonomy.cdm.model.reference.IArticle;
+import eu.etaxonomy.cdm.model.reference.IBook;
+import eu.etaxonomy.cdm.model.reference.IBookSection;
+import eu.etaxonomy.cdm.model.reference.IJournal;
+import eu.etaxonomy.cdm.model.reference.Reference;
+import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
+import eu.etaxonomy.cdm.model.reference.ReferenceType;
+import eu.etaxonomy.cdm.model.taxon.Classification;
+import eu.etaxonomy.cdm.model.taxon.Taxon;
+import eu.etaxonomy.cdm.model.taxon.TaxonBase;
+import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
+import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl;
+import eu.etaxonomy.cdm.strategy.parser.TimePeriodParser;
+
+/**
+ * @author a.mueller
+ * @created 04.08.2008
+ */
+public abstract class MarkupImportBase  {
+       private static final Logger logger = Logger.getLogger(MarkupImportBase.class);
+
+       //Base
+       protected static final String ALTITUDE = "altitude";
+       protected static final String ANNOTATION = "annotation";
+       protected static final String BOLD = "bold";
+       protected static final String BR = "br";
+       protected static final String DOUBTFUL = "doubtful";
+       protected static final String CITATION = "citation";
+       protected static final String CLASS = "class";
+       protected static final String COORDINATES = "coordinates";
+       protected static final String DATES = "dates";
+       protected static final String GATHERING = "gathering";
+       protected static final String GATHERING_GROUP = "gatheringGroup";
+       protected static final String GENUS_ABBREVIATION = "genus abbreviation";
+       protected static final String FOOTNOTE = "footnote";
+       protected static final String FOOTNOTE_REF = "footnoteRef";
+       protected static final String FULL_NAME = "fullName";
+       protected static final String ITALICS = "italics";
+       protected static final String NUM = "num";
+       protected static final String NOTES = "notes";
+       protected static final String PUBLICATION = "publication";
+       protected static final String SPECIMEN_TYPE = "specimenType";
+       protected static final String STATUS = "status";
+       protected static final String SUB_HEADING = "subHeading";
+       protected static final String TYPE = "type";
+       protected static final String TYPE_STATUS = "typeStatus";
+       protected static final String UNKNOWN = "unknown";
+
+
+       protected static final boolean CREATE_NEW = true;
+       protected static final boolean NO_IMAGE_GALLERY = false;
+       protected static final boolean IMAGE_GALLERY = true;
+
+       protected static final String ADDENDA = "addenda";
+       protected static final String BIBLIOGRAPHY = "bibliography";
+       protected static final String BIOGRAPHIES = "biographies";
+       protected static final String CHAR = "char";
+       protected static final String DEDICATION = "dedication";
+       protected static final String DEFAULT_MEDIA_URL = "defaultMediaUrl";
+       protected static final String DISTRIBUTION_LIST = "distributionList";
+       protected static final String DISTRIBUTION_LOCALITY = "distributionLocality";
+       protected static final String FEATURE = "feature";
+       protected static final String FIGURE = "figure";
+       protected static final String FIGURE_LEGEND = "figureLegend";
+       protected static final String FIGURE_PART = "figurePart";
+       protected static final String FIGURE_REF = "figureRef";
+       protected static final String FIGURE_TITLE = "figureTitle";
+       protected static final String FOOTNOTE_STRING = "footnoteString";
+       protected static final String FREQUENCY = "frequency";
+       protected static final String HEADING = "heading";
+       protected static final String HABITAT = "habitat";
+       protected static final String HABITAT_LIST = "habitatList";
+       protected static final String IS_FREETEXT = "isFreetext";
+       protected static final String ID = "id";
+       protected static final String KEY = "key";
+       protected static final String LIFE_CYCLE_PERIODS = "lifeCyclePeriods";
+       protected static final String META_DATA = "metaData";
+       protected static final String MODS = "mods";
+
+       protected static final String NOMENCLATURE = "nomenclature";
+       protected static final String QUOTE = "quote";
+       protected static final String RANK = "rank";
+       protected static final String REF = "ref";
+       protected static final String REF_NUM = "refNum";
+       protected static final String REFERENCE = "reference";
+       protected static final String REFERENCES = "references";
+       protected static final String SUB_CHAR = "subChar";
+       protected static final String TAXON = "taxon";
+       protected static final String TAXONTITLE = "taxontitle";
+       protected static final String TAXONTYPE = "taxontype";
+       protected static final String TEXT_SECTION = "textSection";
+       protected static final String TREATMENT = "treatment";
+       protected static final String SERIALS_ABBREVIATIONS = "serialsAbbreviations";
+       protected static final String STRING = "string";
+       protected static final String URL = "url";
+       protected static final String WRITER = "writer";
+
+       protected static final String LOCALITY = "locality";
+
+
+
+       //Nomenclature
+       protected static final String ACCEPTED = "accepted";
+       protected static final String ACCEPTED_NAME = "acceptedName";
+       protected static final String ALTERNATEPUBTITLE = "alternatepubtitle";
+       protected static final String APPENDIX = "appendix";
+       protected static final String AUTHOR = "author";
+       protected static final String DETAILS = "details";
+       protected static final String EDITION = "edition";
+       protected static final String EDITORS = "editors";
+       protected static final String HOMONYM = "homonym";
+       protected static final String HOMOTYPES = "homotypes";
+       protected static final String NOMENCLATURAL_NOTES = "nomenclaturalNotes";
+       protected static final String INFRANK = "infrank";
+       protected static final String INFRAUT = "infraut";
+       protected static final String INFRPARAUT = "infrparaut";
+       protected static final String ISSUE = "issue";
+       protected static final String NAME_STATUS = "namestatus";
+       protected static final String NAME = "name";
+       protected static final String NAME_TYPE = "nameType";
+       protected static final String NOM = "nom";
+       protected static final String PAGES = "pages";
+       protected static final String PARAUT = "paraut";
+       protected static final String PUBFULLNAME = "pubfullname";
+       protected static final String PUBLOCATION = "publocation";
+       protected static final String PUBLISHER = "publisher";
+       protected static final String PUBNAME = "pubname";
+       protected static final String PUBTITLE = "pubtitle";
+       protected static final String PUBTYPE = "pubtype";
+       protected static final String REF_PART = "refPart";
+       protected static final String SYNONYM = "synonym";
+       protected static final String USAGE = "usage";
+       protected static final String VOLUME = "volume";
+       protected static final String YEAR = "year";
+
+
+       //keys
+       protected static final String COUPLET = "couplet";
+       protected static final String IS_SPOTCHARACTERS = "isSpotcharacters";
+       protected static final String ONLY_NUMBERED_TAXA_EXIST = "onlyNumberedTaxaExist";
+       protected static final String EXISTS = "exists";
+       protected static final String KEYNOTES = "keynotes";
+       protected static final String KEY_TITLE = "keyTitle";
+       protected static final String QUESTION = "question";
+       protected static final String TEXT = "text";
+       protected static final String TO_COUPLET = "toCouplet";
+       protected static final String TO_KEY = "toKey";
+       protected static final String TO_TAXON = "toTaxon";
+
+
+       //Feature
+       protected static final String VERNACULAR_NAMES = "vernacularNames";
+       protected static final String VERNACULAR_NAME = "vernacularName";
+       protected static final String TRANSLATION = "translation";
+       protected static final String LOCAL_LANGUAGE = "localLanguage";
+
+
+
+       protected MarkupDocumentImport docImport;
+
+       private final IEditGeoService editGeoService;
+       protected MarkupFeatureImport featureImport;
+
+       public MarkupImportBase(MarkupDocumentImport docImport) {
+               super();
+               this.docImport = docImport;
+               this.editGeoService = docImport.getEditGeoService();
+       }
+
+       private final Stack<QName> unhandledElements = new Stack<QName>();
+       private final Stack<QName> handledElements = new Stack<QName>();
+
+
+       protected <T extends CdmBase> void  save(Collection<T> collection, MarkupImportState state) {
+               if (state.isCheck() || collection.isEmpty()){
+                       return;
+               }
+               T example = collection.iterator().next();
+               if (example.isInstanceOf(TaxonBase.class)){
+                       Collection<TaxonBase> typedCollection = (Collection<TaxonBase>)collection;
+                       docImport.getTaxonService().saveOrUpdate(typedCollection);
+               }else if (example.isInstanceOf(Classification.class)){
+                       Collection<Classification> typedCollection = (Collection<Classification>)collection;
+                       docImport.getClassificationService().saveOrUpdate(typedCollection);
+               }else if (example.isInstanceOf(PolytomousKey.class)){
+                       Collection<PolytomousKey> typedCollection = (Collection<PolytomousKey>)collection;
+                       docImport.getPolytomousKeyService().saveOrUpdate(typedCollection);
+               }else if (example.isInstanceOf(DefinedTermBase.class)){
+                       Collection<DefinedTermBase> typedCollection = (Collection<DefinedTermBase>)collection;
+                       getTermService().saveOrUpdate(typedCollection);
+               }
+
+       }
+
+
+       //TODO move to service layer for all IdentifiableEntities
+       protected void save(CdmBase cdmBase, MarkupImportState state) {
+               if (state.isCheck()){
+                       return;
+               }
+               cdmBase = CdmBase.deproxy(cdmBase, CdmBase.class);
+               if (cdmBase == null){
+                       String message = "Tried to save a null object.";
+                       fireWarningEvent(message, "--location ?? --", 6,1);
+               } else if (cdmBase.isInstanceOf(TaxonBase.class)){
+                       docImport.getTaxonService().saveOrUpdate((TaxonBase<?>)cdmBase);
+               }else if (cdmBase.isInstanceOf(Classification.class)){
+                       docImport.getClassificationService().saveOrUpdate((Classification)cdmBase);
+               }else if (cdmBase.isInstanceOf(PolytomousKey.class)){
+                       docImport.getPolytomousKeyService().saveOrUpdate((PolytomousKey)cdmBase);
+               }else if (cdmBase.isInstanceOf(DefinedTermBase.class)){
+                       docImport.getTermService().saveOrUpdate((DefinedTermBase<?>)cdmBase);
+               }else if (cdmBase.isInstanceOf(Media.class)){
+                       docImport.getMediaService().saveOrUpdate((Media)cdmBase);
+               }else if (cdmBase.isInstanceOf(SpecimenOrObservationBase.class)){
+                       docImport.getOccurrenceService().saveOrUpdate((SpecimenOrObservationBase<?>)cdmBase);
+               }else if (cdmBase.isInstanceOf(DescriptionElementBase.class)){
+                       docImport.getDescriptionService().saveDescriptionElement((DescriptionElementBase)cdmBase);
+               }else if (cdmBase.isInstanceOf(Reference.class)){
+                       docImport.getReferenceService().saveOrUpdate((Reference)cdmBase);
+               }else{
+                       String message = "Unknown cdmBase type to save: " + cdmBase.getClass();
+                       fireWarningEvent(message, "Unknown location", 8);
+               }
+               //logger.warn("Saved " +  cdmBase);
+       }
+
+
+       protected ITermService getTermService() {
+               return docImport.getTermService();
+       }
+
+       protected IClassificationService getClassificationService() {
+               return docImport.getClassificationService();
+       }
+
+//*********************** Attribute methods *************************************/
+
+       /**
+        * Returns a map for all attributes of an start element
+        * @param event
+        * @return
+        */
+       protected Map<String, Attribute> getAttributes(XMLEvent event) {
+               Map<String, Attribute> result = new HashMap<>();
+               if (!event.isStartElement()){
+                       fireWarningEvent("Event is not an startElement. Can't check attributes", makeLocationStr(event.getLocation()), 1, 1);
+                       return result;
+               }
+               StartElement element = event.asStartElement();
+               @SuppressWarnings("unchecked")
+        Iterator<Attribute> attributes = element.getAttributes();
+               while (attributes.hasNext()){
+                       Attribute attribute = attributes.next();
+                       //TODO namespaces
+                       result.put(attribute.getName().getLocalPart(), attribute);
+               }
+               return result;
+       }
+
+       /**
+        * Throws an unexpected attributes event if the event has any attributes.
+        * @param event
+        */
+       protected void checkNoAttributes(Map<String, Attribute> attributes, XMLEvent event) {
+               String[] exceptions = new String[]{};
+               handleUnexpectedAttributes(event.getLocation(), attributes, 1, exceptions);
+       }
+
+
+
+       /**
+        * Throws an unexpected attributes event if the event has any attributes.
+        * @param event
+        */
+       protected void checkNoAttributes(XMLEvent event) {
+               String[] exceptions = new String[]{};
+               checkNoAttributes(event, 1, exceptions);
+       }
+
+       /**
+        * Throws an unexpected attributes event if the event has any attributes except those mentioned in "exceptions".
+        * @param event
+        * @param exceptions
+        */
+       protected void checkNoAttributes(XMLEvent event, int stackDepth, String... exceptions) {
+               if (! event.isStartElement()){
+                       fireWarningEvent("Event is not an startElement. Can't check attributes", makeLocationStr(event.getLocation()), 1, 1);
+                       return;
+               }
+               StartElement startElement = event.asStartElement();
+               Map<String, Attribute> attributes = getAttributes(startElement);
+               handleUnexpectedAttributes(startElement.getLocation(), attributes, stackDepth+1, exceptions);
+       }
+
+
+       /**
+        * Checks if the given attribute exists and has the given value.
+        * If yes, true is returned and the attribute is removed from the attributes map.
+        * Otherwise false is returned.
+        * @param attributes
+        * @param attrName
+        * @param value
+        * @return <code>true</code> if attribute has given value, <code>false</code> otherwise
+        */
+       protected boolean checkAndRemoveAttributeValue( Map<String, Attribute> attributes, String attrName, String value) {
+               Attribute attr = attributes.get(attrName);
+               if (attr == null ||value == null ){
+                       return false;
+               }else{
+                       if (value.equals(attr.getValue())){
+                               attributes.remove(attrName);
+                               return true;
+                       }else{
+                               return false;
+                       }
+               }
+       }
+
+
+       /**
+        * Returns the value of a given attribute name and removes the attribute from the attributes map.
+        * Returns <code>null</code> if attribute does not exist.
+        * @param attributes the list of all attributes
+        * @param attrName the requested attribute name
+        * @return the value for the attribute
+        */
+       protected String getAndRemoveAttributeValue(Map<String, Attribute> attributes, String attrName) {
+               return getAndRemoveAttributeValue(null, attributes, attrName, false, 1);
+       }
+
+       /**
+        * Returns the value of a boolean attribute with the given name and removes the attribute from the attributes map.
+        * Returns <code>defaultValue</code> if the attribute does not exist. ALso returns <code>defaultValue</code> and throws a warning if the
+        * attribute has no boolean value (true, false).
+        * @param
+        * @param attributes the
+        * @param attrName the name of the attribute
+        * @param defaultValue the default value to return if attribute does not exist or can not be defined
+        * @return
+        */
+       protected Boolean getAndRemoveBooleanAttributeValue(XMLEvent event, Map<String, Attribute> attributes, String attrName, Boolean defaultValue) {
+               String value = getAndRemoveAttributeValue(null, attributes, attrName, false, 1);
+               Boolean result = defaultValue;
+               if (value != null){
+                       if (value.equalsIgnoreCase("true")){
+                               result = true;
+                       }else if (value.equalsIgnoreCase("false")){
+                               result = false;
+                       }else{
+                               String message = "Boolean attribute has no boolean value ('true', 'false') but '%s'";
+                               fireWarningEvent(String.format(message, value), makeLocationStr(event.getLocation()), 6, 1);
+                       }
+               }
+               return result;
+       }
+
+
+       /**
+        * Returns the value of a given attribute name and returns the attribute from the attributes map.
+        * Fires a mandatory field is missing event if the attribute does not exist.
+        * @param xmlEvent
+        * @param attributes
+        * @param attrName
+        * @return
+        */
+       protected String getAndRemoveRequiredAttributeValue(XMLEvent xmlEvent, Map<String, Attribute> attributes, String attrName) {
+               return getAndRemoveAttributeValue(xmlEvent, attributes, attrName, true, 1);
+       }
+
+       /**
+        * Returns the value of a given attribute name and returns the attribute from the attributes map.
+        * If required is <code>true</code> and the attribute does not exist a mandatory field is missing event is fired.
+        * @param xmlEvent
+        * @param attributes
+        * @param attrName
+        * @param isRequired
+        * @return
+        */
+       private String getAndRemoveAttributeValue(XMLEvent xmlEvent, Map<String, Attribute> attributes, String attrName, boolean isRequired, int stackDepth) {
+               Attribute attr = attributes.get(attrName);
+               if (attr == null ){
+                       if (isRequired){
+                               fireMandatoryElementIsMissing(xmlEvent, attrName, 8, stackDepth+1);
+                       }
+                       return null;
+               }else{
+                       attributes.remove(attrName);
+                       return attr.getValue();
+               }
+       }
+
+       /**
+        * Fires an not yet implemented event if the given attribute exists in attributes.
+        * @param attributes
+        * @param attrName
+        * @param event
+        */
+       protected void handleNotYetImplementedAttribute(Map<String, Attribute>  attributes,
+               String attrName, XMLEvent event) {
+               Attribute attr = attributes.get(attrName);
+               if (attr != null){
+                       attributes.remove(attrName);
+                       QName qName = attr.getName();
+                       fireNotYetImplementedAttribute(event.getLocation(), qName, attr.getValue(), 1);
+               }
+       }
+
+       /**
+        * Fires an unhandled attributes event, if attributes exist in attributes map not covered by the exceptions.
+        * No event is fired if the unhandled elements stack is not empty.
+        * @param location
+        * @param attributes
+        * @param exceptions
+        */
+       protected void handleUnexpectedAttributes(Location location,Map<String, Attribute> attributes, String... exceptions) {
+               handleUnexpectedAttributes(location, attributes, 1, exceptions);
+       }
+
+       /**
+        * see {@link #handleUnexpectedAttributes(Location, Map, String...)}
+     *
+        * @param location
+        * @param attributes
+        * @param stackDepth the stack trace depth
+        * @param exceptions
+        */
+       private void handleUnexpectedAttributes(Location location,Map<String, Attribute> attributes, int stackDepth, String... exceptions) {
+               if (attributes.size() > 0){
+                       if (this.unhandledElements.size() == 0 ){
+                               boolean hasUnhandledAttributes = false;
+                               for (String key : attributes.keySet()){
+                                       boolean isException = false;
+                                       for (String exception : exceptions){
+                                               if(key.equals(exception)){
+                                                       isException = true;
+                                               }
+                                       }
+                                       if (!isException){
+                                               hasUnhandledAttributes = true;
+                                       }
+                               }
+                               if (hasUnhandledAttributes){
+                                       fireUnexpectedAttributes(location, attributes, stackDepth+1);
+                               }
+                       }
+               }
+       }
+
+
+       private void fireUnexpectedAttributes(Location location, Map<String, Attribute> attributes, int stackDepth) {
+               String attributesString = "";
+               for (String key : attributes.keySet()){
+                       Attribute attribute = attributes.get(key);
+                       attributesString = CdmUtils.concat(",", attributesString, attribute.getName().getLocalPart() + ":" + attribute.getValue());
+               }
+               String message = "Unexpected attributes: %s";
+               IoProblemEvent event = makeProblemEvent(location, String.format(message, attributesString), 1 , stackDepth +1 );
+               fire(event);
+       }
+
+
+       protected void fireUnexpectedAttributeValue(XMLEvent parentEvent, String attrName, String attrValue) {
+               String message = "Unexpected attribute value %s='%s'";
+               message = String.format(message, attrName, attrValue);
+               IoProblemEvent event = makeProblemEvent(parentEvent.getLocation(), message, 1 , 1 );
+               fire(event);
+       }
+
+       protected void handleNotYetImplementedAttributeValue(XMLEvent xmlEvent, String attrName, String attrValue) {
+               String message = "Attribute %s not yet implemented for value '%s'";
+               message = String.format(message, attrName, attrValue);
+               IIoEvent event = makeProblemEvent(xmlEvent.getLocation(), message, 1, 1 );
+               fire(event);
+       }
+
+       protected void fireNotYetImplementedAttribute(Location location, QName qName,
+               String value, int stackDepth) {
+               String message = "Attribute not yet implemented: %s (%s)";
+               IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart(), value), 1, stackDepth+1 );
+               fire(event);
+       }
+
+
+       protected void fireUnexpectedEvent(XMLEvent xmlEvent, int stackDepth) {
+               Location location = xmlEvent.getLocation();
+               String message = "Unexpected event: %s";
+               IIoEvent event = makeProblemEvent(location, String.format(message, xmlEvent.toString()), 2, stackDepth +1);
+               fire(event);
+       }
+
+       protected void fireUnexpectedStartElement(Location location, StartElement startElement, int stackDepth) {
+               QName qName = startElement.getName();
+               String message = "Unexpected start element: %s";
+               IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart()), 2, stackDepth +1);
+               fire(event);
+       }
+
+
+       protected void fireUnexpectedEndElement(Location location, EndElement endElement, int stackDepth) {
+               QName qName = endElement.getName();
+               String message = "Unexpected end element: %s";
+               IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart()), 16, stackDepth+1);
+               fire(event);
+       }
+
+       protected void fireNotYetImplementedElement(Location location, QName qName, int stackDepth) {
+               String message = "Element not yet implemented: %s";
+               IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart()), 1, stackDepth+1 );
+               fire(event);
+       }
+
+       protected void fireNotYetImplementedCharacters(Location location, Characters chars, int stackDepth) {
+               String message = "Characters not yet handled: %s";
+               IIoEvent event = makeProblemEvent(location, String.format(message, chars.getData()), 1, stackDepth+1 );
+               fire(event);
+       }
+
+       /**
+        * Creates a problem event.
+        * Be aware of the right depths of the stack trace !
+        * @param location
+        * @param message
+        * @param severity
+        * @return
+        */
+       private IoProblemEvent makeProblemEvent(Location location, String message, int severity, int stackDepth) {
+               stackDepth++;
+               StackTraceElement[] stackTrace = new Exception().getStackTrace();
+               int lineNumber = stackTrace[stackDepth].getLineNumber();
+               String methodName = stackTrace[stackDepth].getMethodName();
+               String locationStr = makeLocationStr(location);
+               String className = stackTrace[stackDepth].getClassName();
+               Class<?> declaringClass;
+               try {
+                       declaringClass = Class.forName(className);
+               } catch (ClassNotFoundException e) {
+                       declaringClass = this.getClass();
+               }
+               IoProblemEvent event = IoProblemEvent.NewInstance(declaringClass, message,
+                               locationStr, lineNumber, severity, methodName);
+               return event;
+       }
+
+       /**
+        * Creates a string from a location
+        * @param location
+        * @return
+        */
+       protected String makeLocationStr(Location location) {
+               String locationStr = location == null ? " - no location - " : "l." + location.getLineNumber() + "/c."+ location.getColumnNumber();
+               return locationStr;
+       }
+
+
+       /**
+        * Fires an unexpected element event if the unhandled elements stack is empty.
+        * Otherwise adds the element to the stack.
+        * @param event
+        */
+       protected void handleUnexpectedStartElement(XMLEvent event) {
+               handleUnexpectedStartElement(event, 1);
+       }
+
+       /**
+        * Fires an unexpected element event if the unhandled elements stack is empty.
+        * Otherwise adds the element to the stack.
+        * @param event
+        */
+       protected void handleUnexpectedStartElement(XMLEvent event, int stackDepth) {
+               QName qName = event.asStartElement().getName();
+               if (! unhandledElements.empty()){
+                       unhandledElements.push(qName);
+               }else{
+                       fireUnexpectedStartElement(event.getLocation(), event.asStartElement(), stackDepth + 1);
+               }
+       }
+
+
+       protected void handleUnexpectedEndElement(EndElement event) {
+               handleUnexpectedEndElement(event, 1);
+       }
+
+       /**
+        * Fires an unexpected element event if the event is not the last on the stack.
+        * Otherwise removes last stack element.
+        * @param event
+        */
+       protected void handleUnexpectedEndElement(EndElement event, int stackDepth) {
+               QName qName = event.asEndElement().getName();
+               if (!unhandledElements.isEmpty() && unhandledElements.peek().equals(qName)){
+                       unhandledElements.pop();
+               }else{
+                       fireUnexpectedEndElement(event.getLocation(), event.asEndElement(), stackDepth + 1);
+               }
+       }
+
+       /**
+        *
+        * @param endElement
+        */
+       protected void popUnimplemented(EndElement endElement) {
+               QName qName = endElement.asEndElement().getName();
+               if (unhandledElements.peek().equals(qName)){
+                       unhandledElements.pop();
+               }else{
+                       String message = "End element is not last on stack: %s";
+                       message = String.format(message, qName.getLocalPart());
+                       IIoEvent event = makeProblemEvent(endElement.getLocation(), message, 16, 1);
+                       fire(event);
+               }
+
+       }
+
+
+       /**
+        * Fires an unexpected element event if the unhandled element stack is empty.
+        * @param event
+        */
+       protected void handleUnexpectedElement(XMLEvent event) {
+               if (event.isStartElement()){
+                       handleUnexpectedStartElement(event, 2);
+               }else if (event.isEndElement()){
+                       handleUnexpectedEndElement(event.asEndElement(), 2);
+               }else if (event.getEventType() == XMLStreamConstants.COMMENT){
+                       //do nothing
+               }else if (! unhandledElements.empty()){
+                       //do nothing
+               }else{
+                       fireUnexpectedEvent(event, 1);
+               }
+       }
+
+       /**
+        * Fires an not yet implemented event and adds the element name to the unhandled elements stack.
+        * @param event
+        */
+       protected void handleNotYetImplementedCharacters(XMLEvent event) {
+               Characters chars = event.asCharacters();
+               fireNotYetImplementedCharacters(event.getLocation(), chars, 1);
+       }
+
+       /**
+        * Fires an not yet implemented event and adds the element name to the unhandled elements stack.
+        * @param event
+        */
+       protected void handleNotYetImplementedElement(XMLEvent event) {
+               QName qName = event.asStartElement().getName();
+               boolean isTopLevel = unhandledElements.isEmpty();
+               unhandledElements.push(qName);
+               if (isTopLevel){
+                       fireNotYetImplementedElement(event.getLocation(), qName, 1);
+               }
+       }
+
+       /**
+        * Fires an not yet implemented event and adds the element name to the unhandled elements stack.
+        * @param event
+        */
+       protected void handleIgnoreElement(XMLEvent event) {
+               QName qName = event.asStartElement().getName();
+               unhandledElements.push(qName);
+       }
+
+       protected void handleAmbigousManually(MarkupImportState state,
+                       XMLEventReader reader, StartElement startElement) {
+               QName qName = startElement.getName();
+               unhandledElements.push(qName);
+               fireWarningEvent(
+                               "Handle manually: " + qName.getLocalPart() + " is ambigous and should therefore be handled manually",
+                               makeLocationStr(startElement.getLocation()), 2, 2);
+       }
+
+       /**
+        * Checks if a mandatory text is not empty or null.
+        * Returns true if text is given.
+        * Fires an mandatory element is missing event otherwise and returns <code>null</code>.
+        * @param text
+        * @param parentEvent
+        * @return
+        */
+       protected boolean checkMandatoryText(String text, XMLEvent parentEvent) {
+               if (! StringUtils.isNotBlank(text)){
+                       fireMandatoryElementIsMissing(parentEvent, "CData", 4, 1);
+                       return false;
+               }
+               return true;
+       }
+
+       /**
+        * Fires an mandatory element is missing event if exists is <code>false</code>.
+        * @param hasMandatory
+        * @param parentEvent
+        * @param string
+        */
+       protected void checkMandatoryElement(boolean exists, StartElement parentEvent, String attrName) {
+               if (! exists){
+                       fireMandatoryElementIsMissing(parentEvent, attrName, 5, 1);
+               }
+       }
+
+
+       /**
+        * Fires an element is missing event.
+        * @param xmlEvent
+        * @param string
+        * @param severity
+        * @param stackDepth
+        * @throws IllegalStateException if xmlEvent is not a StartElement and not an Attribute
+        */
+       private void fireMandatoryElementIsMissing(XMLEvent xmlEvent, String missingEventName, int severity, int stackDepth) throws IllegalStateException{
+               Location location = xmlEvent.getLocation();
+               String typeName;
+               QName qName;
+               if (xmlEvent.isAttribute()){
+                       Attribute attribute = ((Attribute)xmlEvent);
+                       typeName = "attribute";
+                       qName = attribute.getName();
+               }else if (xmlEvent.isStartElement()){
+                       typeName = "element";
+                       qName = xmlEvent.asStartElement().getName();
+               }else{
+                       throw new IllegalStateException("mandatory element only allowed for attributes and start tags in " + makeLocationStr(location));
+               }
+               String message = "Mandatory %s '%s' is missing in %s";
+               message = String.format(message, typeName , missingEventName, qName.getLocalPart());
+               IIoEvent event = makeProblemEvent(location, message, severity, stackDepth +1);
+               fire(event);
+       }
+
+
+
+
+       /**
+        * Returns <code>true</code> if the "next" event is the ending tag for the "parent" event.
+        * @param next end element to test, must not be null
+        * @param parentEvent start element to test
+        * @return true if the "next" event is the ending tag for the "parent" event.
+        * @throws XMLStreamException
+        */
+       protected boolean isMyEndingElement(XMLEvent next, XMLEvent parentEvent) throws XMLStreamException {
+               if (! parentEvent.isStartElement()){
+                       String message = "Parent event should be start tag";
+                       fireWarningEvent(message, makeLocationStr(next.getLocation()), 6);
+                       return false;
+               }
+               return isEndingElement(next, parentEvent.asStartElement().getName().getLocalPart());
+       }
+
+       /**
+        * Trims the text and removes turns all whitespaces into single empty space.
+        * @param text
+        * @return
+        */
+       protected String normalize(String text) {
+               text = StringUtils.trimToEmpty(text);
+               text = text.replaceAll("\\s+", " ");
+               return text;
+       }
+
+
+
+       /**
+        * Removes whitespaces at beginning and end and makes the first letter
+        * a capital letter and all other letters small letters.
+        * @param value
+        * @return
+        */
+       protected String toFirstCapital(String value) {
+               if (StringUtils.isBlank(value)){
+                       return value;
+               }else{
+                       String result = "";
+                       value = value.trim();
+                       result += value.trim().substring(0,1).toUpperCase();
+                       if (value.length()>1){
+                               result += value.substring(1).toLowerCase();
+                       }
+                       return result;
+               }
+       }
+
+       /**
+        * Currently not used.
+        * @param str
+        * @param allowedNumberOfCharacters
+        * @param onlyFirstCapital
+        * @return
+        */
+       protected boolean isAbbreviation(String str, int allowedNumberOfCharacters, boolean onlyFirstCapital){
+               if (isBlank(str)){
+                       return false;
+               }
+               str = str.trim();
+               if (! str.endsWith(".")){
+                       return false;
+               }
+               str = str.substring(0, str.length() -1);
+               if (str.length() > allowedNumberOfCharacters){
+                       return false;
+               }
+               final String re = "^\\p{javaUpperCase}\\p{javaLowerCase}*$";
+               if (str.matches(re)){
+                       return true;
+               }else{
+                       return false;
+               }
+       }
+
+       /**
+        * Checks if <code>abbrev</code> is the short form for the genus name (strGenusName).
+        * Usually this is the case if <code>abbrev</code> is the first letter (optional with ".")
+        * of strGenusName. But in older floras it may also be the first 2 or 3 letters (optional with dot).
+        * However, we allow only a maximum of 2 letters to be anambigous. In cases with 3 letters better
+        * change the original markup data.
+        * @param single
+        * @param strGenusName
+        * @return
+        */
+       protected boolean isGenusAbbrev(String abbrev, String strGenusName) {
+               if (! abbrev.matches("[A-Z][a-z]?\\.?")) {
+                       return false;
+               }else if (abbrev.length() == 0 || strGenusName == null || strGenusName.length() == 0){
+                       return false;
+               }else{
+                       abbrev = abbrev.replace(".", "");
+                       return strGenusName.startsWith(abbrev);
+//                     boolean result = true;
+//                     for (int i = 0 ; i < abbrev.length(); i++){
+//                             result &= ( abbrev.charAt(i) == strGenusName.charAt(i));
+//                     }
+//                     return result;
+               }
+       }
+
+
+       /**
+        * Checks if all words in the given string start with a capital letter but do not have any further capital letter.
+        * @param word the string to be checekd. Usually should be a single word.
+        * @return true if the above is the case, false otherwise
+        */
+       protected boolean isFirstCapitalWord(String word) {
+               if (WordUtils.capitalizeFully(word).equals(word)){
+                       return true;
+               }else if (WordUtils.capitalizeFully(word,new char[]{'-'}).equals(word)){
+                       //for words like Le-Testui (which is a species epithet)
+                       return true;
+               }else{
+                       return false;
+               }
+       }
+
+
+       /**
+        * Read next event. Ignore whitespace events.
+        * @param reader
+        * @return
+        * @throws XMLStreamException
+        */
+       protected XMLEvent readNoWhitespace(XMLEventReader reader) throws XMLStreamException {
+               XMLEvent event = reader.nextEvent();
+               while (!unhandledElements.isEmpty()){
+                       if (event.isStartElement()){
+                               handleNotYetImplementedElement(event);
+                       }else if (event.isEndElement()){
+                               popUnimplemented(event.asEndElement());
+                       }
+                       event = reader.nextEvent();
+               }
+               while (event.isCharacters() && event.asCharacters().isWhiteSpace()){
+                       event = reader.nextEvent();
+               }
+               return event;
+       }
+
+       /**
+        * Returns the REQUIRED "class" attribute for a given event and checks that it is the only attribute.
+        * @param parentEvent
+        * @return
+        */
+       protected String getClassOnlyAttribute(XMLEvent parentEvent) {
+               return getClassOnlyAttribute(parentEvent, true);
+       }
+
+
+       /**
+        * Returns the "class" attribute for a given event and checks that it is the only attribute.
+        * @param parentEvent
+        * @return
+        */
+       protected String getClassOnlyAttribute(XMLEvent parentEvent, boolean required) {
+               return getOnlyAttribute(parentEvent, CLASS, required);
+       }
+
+       /**
+        * Returns the value for the only attribute for a given event and checks that it is the only attribute.
+        * @param parentEvent
+        * @return
+        */
+       protected String getOnlyAttribute(XMLEvent parentEvent, String attrName, boolean required) {
+               Map<String, Attribute> attributes = getAttributes(parentEvent);
+               String classValue =getAndRemoveAttributeValue(parentEvent, attributes, attrName, required, 1);
+               checkNoAttributes(attributes, parentEvent);
+               return classValue;
+       }
+
+
+       protected void fireWarningEvent(String message, String locationStr, Integer severity, Integer depth) {
+               docImport.fireWarningEvent(message, locationStr, severity, depth);
+       }
+
+       protected void fireWarningEvent(String message, XMLEvent event, Integer severity) {
+               docImport.fireWarningEvent(message, makeLocationStr(event.getLocation()), severity, 1);
+       }
+
+       protected void fireSchemaConflictEventExpectedStartTag(String elName, XMLEventReader reader) throws XMLStreamException {
+               docImport.fireSchemaConflictEventExpectedStartTag(elName, reader);
+       }
+
+
+       protected void fireWarningEvent(String message, String locationStr, int severity) {
+               docImport.fireWarningEvent(message, locationStr, severity, 1);
+       }
+
+       protected void fire(IIoEvent event) {
+               docImport.fire(event);
+       }
+
+       protected boolean isNotBlank(String str){
+               return StringUtils.isNotBlank(str);
+       }
+
+       protected boolean isBlank(String str){
+               return StringUtils.isBlank(str);
+       }
+
+       protected TaxonDescription getTaxonDescription(Taxon taxon, Reference ref, boolean isImageGallery, boolean createNewIfNotExists) {
+               return docImport.getTaxonDescription(taxon, ref, isImageGallery, createNewIfNotExists);
+       }
+
+    protected TaxonDescription getDefaultTaxonDescription(Taxon taxon, boolean isImageGallery, boolean createNewIfNotExists, Reference source) {
+        return docImport.getDefaultTaxonDescription(taxon, isImageGallery, createNewIfNotExists, source);
+    }
+
+    /**
+     * Returns the taxon description with marked as <code>true</code> with the given marker type.
+     * If createNewIfNotExists a new description is created if it does not yet exist.
+     * For the new description the source and the title are set if not <code>null</code>.
+     * @param taxon
+     * @param markerType
+     * @param isImageGallery
+     * @param createNewIfNotExists
+     * @param source
+     * @param title
+     * @return the existing or new taxon description
+     */
+    protected TaxonDescription getMarkedTaxonDescription(Taxon taxon, MarkerType markerType, boolean isImageGallery, boolean createNewIfNotExists, Reference source, String title) {
+        return docImport.getMarkedTaxonDescription(taxon, markerType, isImageGallery, createNewIfNotExists, source, title);
+    }
+
+
+       /**
+        * Returns the default language defined in the state. If no default language is defined in the state,
+        * the CDM default language is returned.
+        * @param state
+        * @return
+        */
+       protected Language getDefaultLanguage(MarkupImportState state) {
+               Language result = state.getDefaultLanguage();
+               if (result == null){
+                       result = Language.DEFAULT();
+               }
+               return result;
+       }
+
+
+//*********************** FROM XML IMPORT BASE ****************************************
+       protected boolean isEndingElement(XMLEvent event, String elName) throws XMLStreamException {
+               return docImport.isEndingElement(event, elName);
+       }
+
+       protected boolean isStartingElement(XMLEvent event, String elName) throws XMLStreamException {
+               return docImport.isStartingElement(event, elName);
+       }
+
+
+       protected void fillMissingEpithetsForTaxa(Taxon parentTaxon, Taxon childTaxon) {
+               docImport.fillMissingEpithetsForTaxa(parentTaxon, childTaxon);
+       }
+
+       protected Feature getFeature(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<Feature> voc){
+               return docImport.getFeature(state, uuid, label, text, labelAbbrev, voc);
+       }
+
+    protected PresenceAbsenceTerm getPresenceAbsenceTerm(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, boolean isAbsenceTerm, TermVocabulary<PresenceAbsenceTerm> voc){
+        return docImport.getPresenceTerm(state, uuid, label, text, labelAbbrev, isAbsenceTerm, voc);
+    }
+
+       protected ExtensionType getExtensionType(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev){
+               return docImport.getExtensionType(state, uuid, label, text, labelAbbrev);
+       }
+
+       protected DefinedTerm getIdentifierType(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<DefinedTerm> voc){
+               return docImport.getIdentifierType(state, uuid, label, text, labelAbbrev, voc);
+       }
+
+       protected AnnotationType getAnnotationType(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<AnnotationType> voc){
+               return docImport.getAnnotationType(state, uuid, label, text, labelAbbrev, voc);
+       }
+
+       protected MarkerType getMarkerType(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<MarkerType> voc){
+               return docImport.getMarkerType(state, uuid, label, text, labelAbbrev, voc);
+       }
+
+       protected NamedAreaLevel getNamedAreaLevel(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<NamedAreaLevel> voc){
+               return docImport.getNamedAreaLevel(state, uuid, label, text, labelAbbrev, voc);
+       }
+
+       protected NamedArea getNamedArea(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, NamedAreaType areaType, NamedAreaLevel level, TermVocabulary voc, TermMatchMode matchMode){
+               return docImport.getNamedArea(state, uuid, label, text, labelAbbrev, areaType, level, voc, matchMode);
+       }
+
+       protected Language getLanguage(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<?> voc){
+               return docImport.getLanguage(state, uuid, label, text, labelAbbrev, voc);
+       }
+
+// *************************************** Concrete methods **********************************************/
+
+
+       /**
+        * @param state
+        * @param classValue
+        * @param byAbbrev
+        * @return
+        */
+       protected Rank makeRank(MarkupImportState state, String value, boolean byAbbrev) {
+               Rank rank = null;
+               if (StringUtils.isBlank(value)) {
+                       return null;
+               }
+               try {
+                       boolean useUnknown = true;
+                       NomenclaturalCode nc = makeNomenclaturalCode(state);
+                       if (value.equals(GENUS_ABBREVIATION)){
+                               rank = Rank.GENUS();
+                       }else if (byAbbrev) {
+                               rank = Rank.getRankByIdInVoc(value.toLowerCase(), nc, useUnknown);
+                               if (value.equalsIgnoreCase("forma")){
+                                   return Rank.FORM();
+                               }else if (value.toLowerCase().matches("(sub)?(section|genus|series|tribe)")){
+                                   return Rank.getRankByEnglishName(value, nc, useUnknown);
+                               }else if (value.equals("§")){
+                    return Rank.SECTION_BOTANY();  //Special case in Flora Malesiana
+                               }
+                       } else {
+                               rank = Rank.getRankByEnglishName(value, nc, useUnknown);
+                       }
+                       if (rank.equals(Rank.UNKNOWN_RANK())) {
+                               rank = null;
+                       }
+                       if (rank == null && "sous-genre".equalsIgnoreCase(value)){
+                               rank = Rank.SUBGENUS();
+                       }
+               } catch (UnknownCdmTypeException e) {
+                       // doNothing
+               }
+               return rank;
+       }
+
+       NonViralNameParserImpl parser = NonViralNameParserImpl.NewInstance();
+    protected TeamOrPersonBase<?> createAuthor(MarkupImportState state, String authorTitle) {
+               TeamOrPersonBase<?> result = parser.author(authorTitle);
+               return state.getDeduplicationHelper(docImport).getExistingAuthor(state, result);
+       }
+
+       protected String getAndRemoveMapKey(Map<String, String> map, String key) {
+               String result = map.get(key);
+               map.remove(key);
+               if (result != null) {
+                       result = normalize(result);
+               }
+               return StringUtils.stripToNull(result);
+       }
+
+
+       /**
+        * Creates a {@link NonViralName} object depending on the defined {@link NomenclaturalCode}
+        * and the given parameters.
+        * @param state
+        * @param rank
+        * @return
+        */
+       protected INonViralName createNameByCode(MarkupImportState state, Rank rank) {
+               NomenclaturalCode nc = makeNomenclaturalCode(state);
+               INonViralName name = nc.getNewTaxonNameInstance(rank);
+               return name;
+       }
+
+       protected void handleFullName(MarkupImportState state, XMLEventReader reader,
+                       INonViralName name, XMLEvent event) throws XMLStreamException {
+               String fullNameStr;
+               Map<String, Attribute> attrs = getAttributes(event);
+               String rankStr = getAndRemoveRequiredAttributeValue(event, attrs, "rank");
+               String hybridClass = getAndRemoveAttributeValue(attrs, "hybridClass");
+
+               Rank rank = makeRank(state, rankStr, false);
+               name.setRank(rank);
+               if (rank == null) {
+                       String message = "Rank was computed as null. This must not be.";
+                       fireWarningEvent(message, event, 6);
+                       name.setRank(Rank.UNKNOWN_RANK());
+               }
+               if (!attrs.isEmpty()) {
+                       handleUnexpectedAttributes(event.getLocation(), attrs);
+               }
+               fullNameStr = getCData(state, reader, event, false);
+               NonViralNameParserImpl.NewInstance().parseFullName(name, fullNameStr, rank, false);
+               if (hybridClass != null ){
+                   if ("hybrid formula".equals(hybridClass)){
+                       if (!name.isHybridFormula()){
+                           fireWarningEvent("Hybrid formula is not set though requested: " + fullNameStr, event, 4);
+                       }
+                   }else if ("hybrid".equals(hybridClass)){
+                if (!name.isHybridName()){
+                    fireWarningEvent("Hybrid name is recognized: " + fullNameStr, event, 4);
+                }
+            }else{
+                handleNotYetImplementedAttributeValue(event, "hybridClass", hybridClass);
+            }
+               }
+       }
+
+
+       /**
+        * Returns the {@link NomenclaturalCode} for this import. Default is {@link NomenclaturalCode#ICBN} if
+        * no code is defined.
+        * @param state
+        * @return
+        */
+       protected NomenclaturalCode makeNomenclaturalCode(MarkupImportState state) {
+               NomenclaturalCode nc = state.getConfig().getNomenclaturalCode();
+               if (nc == null) {
+                       nc = NomenclaturalCode.ICNAFP; // default;
+               }
+               return nc;
+       }
+
+
+       /**
+        * @param state
+        * @param levelString
+        * @param next
+        * @return
+        */
+       protected NamedAreaLevel makeNamedAreaLevel(MarkupImportState state, String levelString, XMLEvent next) {
+               NamedAreaLevel level;
+               try {
+                       level = state.getTransformer().getNamedAreaLevelByKey(levelString);
+                       if (level == null) {
+                               UUID levelUuid = state.getTransformer().getNamedAreaLevelUuid(levelString);
+                               if (levelUuid == null) {
+                                       String message = "Unknown distribution locality class (named area level): %s. Create new level instead.";
+                                       message = String.format(message, levelString);
+                                       fireWarningEvent(message, next, 6);
+                               }
+                               level = getNamedAreaLevel(state, levelUuid, levelString, levelString, levelString, null);
+                       }
+               } catch (UndefinedTransformerMethodException e) {
+                       throw new RuntimeException(e);
+               }
+               return level;
+       }
+
+
+       /**
+        * @param state
+        * @param areaName
+        * @param level
+        * @return
+        */
+       protected NamedArea makeArea(MarkupImportState state, String areaName, NamedAreaLevel level) {
+
+               //TODO FM vocabulary
+               TermVocabulary<NamedArea> voc = null;
+               NamedAreaType areaType = null;
+
+               NamedArea area = null;
+               try {
+                       area = state.getTransformer().getNamedAreaByKey(areaName);
+               } catch (UndefinedTransformerMethodException e) {
+                       throw new RuntimeException(e);
+               }
+               if (area == null){
+                       boolean isNewInState = false;
+                       UUID uuid = state.getAreaUuid(areaName);
+                       if (uuid == null){
+                               isNewInState = true;
+                               try {
+                                       uuid = state.getTransformer().getNamedAreaUuid(areaName);
+                                       if (uuid == null){
+                                           uuid = UUID.randomUUID();
+                                           state.putAreaUuid(areaName, uuid);
+                                       }
+                               } catch (UndefinedTransformerMethodException e) {
+                                       throw new RuntimeException(e);
+                               }
+                       }
+
+                       CdmImportBase.TermMatchMode matchMode = CdmImportBase.TermMatchMode.UUID_LABEL;
+                       area = getNamedArea(state, uuid, areaName, areaName, areaName, areaType, level, voc, matchMode);
+                       if (isNewInState){
+                               state.putAreaUuid(areaName, area.getUuid());
+
+                               //TODO just for testing -> make generic and move to better place
+                               String geoServiceLayer="vmap0_as_bnd_political_boundary_a";
+                               String layerFieldName ="nam";
+
+                               if ("Bangka".equals(areaName)){
+                                       String areaValue = "PULAU BANGKA#SUMATERA SELATAN";
+                                       GeoServiceArea geoServiceArea = new GeoServiceArea();
+                                       geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);
+                                       this.editGeoService.setMapping(area, geoServiceArea);
+//                                     save(area, state);
+                               }
+                               if ("Luzon".equals(areaName)){
+                                       GeoServiceArea geoServiceArea = new GeoServiceArea();
+
+                                       List<String> list = Arrays.asList("HERMANA MAYOR ISLAND#CENTRAL LUZON",
+                                                       "HERMANA MENOR ISLAND#CENTRAL LUZON",
+                                                       "CENTRAL LUZON");
+                                       for (String areaValue : list){
+                                               geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);
+                                       }
+
+                                       this.editGeoService.setMapping(area, geoServiceArea);
+//                                     save(area, state);
+                               }
+                               if ("Mindanao".equals(areaName)){
+                                       GeoServiceArea geoServiceArea = new GeoServiceArea();
+
+                                       List<String> list = Arrays.asList("NORTHERN MINDANAO",
+                                                       "SOUTHERN MINDANAO",
+                                                       "WESTERN MINDANAO");
+                                       //TODO to be continued
+                                       for (String areaValue : list){
+                                               geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);
+                                       }
+
+                                       this.editGeoService.setMapping(area, geoServiceArea);
+//                                     save(area, state);
+                               }
+                               if ("Palawan".equals(areaName)){
+                                       GeoServiceArea geoServiceArea = new GeoServiceArea();
+
+                                       List<String> list = Arrays.asList("PALAWAN#SOUTHERN TAGALOG");
+                                       for (String areaValue : list){
+                                               geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);
+                                       }
+
+                                       this.editGeoService.setMapping(area, geoServiceArea);
+//                                     save(area, state);
+                               }
+
+                       }
+               }
+               return area;
+       }
+
+
+
+       /**
+        * Reads character data. Any element other than character data or the ending
+        * tag will fire an unexpected element event.
+     *
+        * @see #getCData(MarkupImportState, XMLEventReader, XMLEvent, boolean)
+        * @param state
+        * @param reader
+        * @param next
+        * @return
+        * @throws XMLStreamException
+        */
+       protected String getCData(MarkupImportState state, XMLEventReader reader, XMLEvent next) throws XMLStreamException {
+               return getCData(state, reader, next, true);
+       }
+
+       /**
+        * Reads character data. Any element other than character data or the ending
+        * tag will fire an unexpected element event.
+        *
+        * @param state
+        * @param reader
+        * @param next
+        * @param inlineMarkup map for inline markup, this is used for e.g. the locality markup within a subheading
+        * The map will be filled by the markup element name as key. The value may be a String, a CdmBase or any other object.
+        * If null any markup text will be neglected but a warning will be fired if they exist.
+        * @param removeInlineMarkupText if true the markedup text will be removed from the returned String
+        * @param checkAttributes
+        * @return
+        * @throws XMLStreamException
+        */
+       protected String getCData(MarkupImportState state, XMLEventReader reader, XMLEvent parent, /*Map<String, Object> inlineMarkup, *boolean removeInlineMarkupText,*/ boolean checkAttributes) throws XMLStreamException {
+               if (checkAttributes){
+                       checkNoAttributes(parent);
+               }
+
+               String text = "";
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       if (isMyEndingElement(next, parent)) {
+                               return text;
+                       } else if (next.isCharacters()) {
+                               text += next.asCharacters().getData();
+                       } else if (isStartingElement(next, FOOTNOTE_REF)){
+                               handleNotYetImplementedElement(next);
+//                     } else if (isStartingElement(next, LOCALITY)){
+//                             handleCDataLocality(state, reader, parent);
+                       } else {
+                               handleUnexpectedElement(next);
+                       }
+               }
+               throw new IllegalStateException("Event has no closing tag");
+
+       }
+
+//     private void handleCDataLocality(MarkupImportState state, XMLEventReader reader, XMLEvent parent) {
+//             checkAndRemoveAttributeValue(attributes, attrName, value)
+//
+//     }
+
+
+
+       /**
+        * For it returns a pure CData annotation string. This behaviour may change in future. More complex annotations
+        * should be handled differently.
+        * @param state
+        * @param reader
+        * @param parentEvent
+        * @return
+        * @throws XMLStreamException
+        */
+       protected String handleSimpleAnnotation(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
+               String annotation = getCData(state, reader, parentEvent);
+               return annotation;
+       }
+
+       /**
+        * True if text is single "." oder "," or ";" or ":"
+        * @param text
+        * @return
+        */
+       protected boolean isPunctuation(String text) {
+               return text == null ? false : text.trim().matches("^[\\.,;:]$");
+       }
+
+
+       /**
+        * Text indicating that type information is following but no information about the type of the type
+        * @param text
+        * @return
+        */
+       protected boolean charIsSimpleType(String text) {
+               return text.matches("(?i)Type:");
+       }
+
+       protected String getXmlTag(XMLEvent event) {
+               String result;
+               if (event.isStartElement()) {
+                       result = "<" + event.asStartElement().getName().getLocalPart()
+                                       + ">";
+               } else if (event.isEndElement()) {
+                       result = "</" + event.asEndElement().getName().getLocalPart() + ">";
+               } else {
+                       String message = "Only start or end elements are allowed as Html tags";
+                       throw new IllegalStateException(message);
+               }
+               return result;
+       }
+
+       protected WriterDataHolder handleWriter(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
+               String text = "";
+               checkNoAttributes(parentEvent);
+               WriterDataHolder dataHolder = new WriterDataHolder();
+               List<FootnoteDataHolder> footnotes = new ArrayList<>();
+
+               // TODO handle attributes
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       if (isMyEndingElement(next, parentEvent)) {
+                               text = CdmUtils.removeBrackets(text);
+                               if (checkMandatoryText(text, parentEvent)) {
+                                       text = normalize(text);
+                                       dataHolder.writer = text;
+                                       dataHolder.footnotes = footnotes;
+
+                                       // Extension
+                                       UUID uuidWriterExtension = MarkupTransformer.uuidWriterExtension;
+                                       ExtensionType writerExtensionType =
+                                                       this.getExtensionType(state, uuidWriterExtension,"Writer", "writer", "writer");
+                                       Extension extension = Extension.NewInstance();
+                                       extension.setType(writerExtensionType);
+                                       extension.setValue(text);
+                                       dataHolder.extension = extension;
+
+                                       // Annotation
+                                       UUID uuidWriterAnnotation = MarkupTransformer.uuidWriterAnnotation;
+                                       AnnotationType writerAnnotationType = this.getAnnotationType(state, uuidWriterAnnotation, "Writer", "writer", "writer", null);
+                                       Annotation annotation = Annotation.NewInstance(text, writerAnnotationType, getDefaultLanguage(state));
+                                       dataHolder.annotation = annotation;
+
+                                       return dataHolder;
+                               } else {
+                                       return null;
+                               }
+                       } else if (isStartingElement(next, FOOTNOTE_REF)) {
+                               FootnoteDataHolder footNote = handleFootnoteRef(state, reader, next);
+                               if (footNote.isRef()) {
+                                       footnotes.add(footNote);
+                               } else {
+                                       logger.warn("Non ref footnotes not yet impelemnted");
+                               }
+                       } else if (next.isCharacters()) {
+                               text += next.asCharacters().getData();
+
+                       } else {
+                               handleUnexpectedElement(next);
+                               state.setUnsuccessfull();
+                       }
+               }
+               throw new IllegalStateException("<writer> has no end tag");
+       }
+
+
+       protected void registerFootnotes(MarkupImportState state, AnnotatableEntity entity, List<FootnoteDataHolder> footnotes) {
+               for (FootnoteDataHolder footNote : footnotes) {
+                       registerFootnoteDemand(state, entity, footNote);
+               }
+       }
+
+
+       private void registerFootnoteDemand(MarkupImportState state, AnnotatableEntity entity, FootnoteDataHolder footnote) {
+               FootnoteDataHolder existingFootnote = state.getFootnote(footnote.ref);
+               if (existingFootnote != null) {
+                       attachFootnote(state, entity, existingFootnote);
+               } else {
+                       Set<AnnotatableEntity> demands = state.getFootnoteDemands(footnote.ref);
+                       if (demands == null) {
+                               demands = new HashSet<>();
+                               state.putFootnoteDemands(footnote.ref, demands);
+                       }
+                       demands.add(entity);
+               }
+       }
+
+
+       protected void attachFootnote(MarkupImportState state, AnnotatableEntity entity, FootnoteDataHolder footnote) {
+               AnnotationType annotationType = this.getAnnotationType(state, MarkupTransformer.uuidFootnote, "Footnote", "An e-flora footnote", "fn", null);
+               Annotation annotation = Annotation.NewInstance(footnote.string, annotationType, getDefaultLanguage(state));
+               // TODO transient objects
+               entity.addAnnotation(annotation);
+               save(entity, state);
+       }
+
+
+       protected void attachFigure(MarkupImportState state, XMLEvent next, AnnotatableEntity entity, Media figure) {
+               // IdentifiableEntity<?> toSave;
+               if (entity.isInstanceOf(TextData.class)) {
+                       TextData deb = CdmBase.deproxy(entity, TextData.class);
+                       deb.addMedia(figure);
+                       // toSave = ((TaxonDescription)deb.getInDescription()).getTaxon();
+               } else if (entity.isInstanceOf(SpecimenOrObservationBase.class)) {
+                       String message = "figures for specimen should be handled as Textdata";
+                       fireWarningEvent(message, next, 4);
+                       // toSave = ime;
+               } else if (entity.isInstanceOf(IdentifiableMediaEntity.class)) {
+                       IdentifiableMediaEntity<?> ime = CdmBase.deproxy(entity, IdentifiableMediaEntity.class);
+                       ime.addMedia(figure);
+                       // toSave = ime;
+               } else {
+                       String message = "Unsupported entity to attach media: %s";
+                       message = String.format(message, entity.getClass().getName());
+                       // toSave = null;
+               }
+               save(entity, state);
+       }
+
+
+       protected void registerGivenFootnote(MarkupImportState state, FootnoteDataHolder footnote) {
+               state.registerFootnote(footnote);
+               Set<AnnotatableEntity> demands = state.getFootnoteDemands(footnote.id);
+               if (demands != null) {
+                       for (AnnotatableEntity entity : demands) {
+                               attachFootnote(state, entity, footnote);
+                       }
+               }
+       }
+
+
+       protected FootnoteDataHolder handleFootnote(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent,
+                       MarkupSpecimenImport specimenImport, MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {
+               FootnoteDataHolder result = new FootnoteDataHolder();
+               Map<String, Attribute> attributes = getAttributes(parentEvent);
+               result.id = getAndRemoveAttributeValue(attributes, ID);
+               // result.ref = getAndRemoveAttributeValue(attributes, REF);
+               checkNoAttributes(attributes, parentEvent);
+
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       if (isStartingElement(next, FOOTNOTE_STRING)) {
+                               String string = handleFootnoteString(state, reader, next, specimenImport, nomenclatureImport);
+                               result.string = string;
+                       } else if (isMyEndingElement(next, parentEvent)) {
+                               return result;
+                       } else {
+                               fireUnexpectedEvent(next, 0);
+                       }
+               }
+               return result;
+       }
+
+
+       protected Media handleFigure(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent,
+                       MarkupSpecimenImport specimenImport, MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {
+               // FigureDataHolder result = new FigureDataHolder();
+
+               Map<String, Attribute> attributes = getAttributes(parentEvent);
+               String id = getAndRemoveAttributeValue(attributes, ID);
+               String type = getAndRemoveAttributeValue(attributes, TYPE);
+               String urlAttr = getAndRemoveAttributeValue(attributes, URL);
+               checkNoAttributes(attributes, parentEvent);
+
+               String urlString = null;
+               String legendString = null;
+               String titleString = null;
+               String numString = null;
+               String text = null;
+               if (isNotBlank(urlAttr)){
+                       urlString = CdmUtils.Nz(state.getBaseMediaUrl()) + urlAttr;
+               }
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       if (isMyEndingElement(next, parentEvent)) {
+                               if (isNotBlank(text)){
+                                   if (isNeglectableFigureText(text)){
+                                       fireWarningEvent("Text not yet handled for figures: " + text, next, 4);
+                                   }
+                               }
+                               Media media = makeFigure(state, id, type, urlString, legendString, titleString, numString, next);
+                               return media;
+                       } else if (isStartingElement(next, FIGURE_LEGEND)) {
+                               // TODO same as figure string ?
+                               legendString = handleFootnoteString(state, reader, next, specimenImport, nomenclatureImport);
+                       } else if (isStartingElement(next, FIGURE_TITLE)) {
+                               titleString = getCData(state, reader, next);
+                       } else if (isStartingElement(next, URL)) {
+                               String localUrl = getCData(state, reader, next);
+                               String url = CdmUtils.Nz(state.getBaseMediaUrl()) + localUrl;
+                               if (isBlank(urlString)){
+                                       urlString = url;
+                               }
+                               if (! url.equals(urlString)){
+                                       String message = "URL attribute and URL element differ. Attribute: %s, Element: %s";
+                                       fireWarningEvent(String.format(message, urlString, url), next, 2);
+                               }
+                       } else if (isStartingElement(next, NUM)) {
+                               numString = getCData(state, reader, next);
+                       } else if (next.isCharacters()) {
+                               text = CdmUtils.concat("", text, next.asCharacters().getData());
+                       } else {
+                               fireUnexpectedEvent(next, 0);
+                       }
+               }
+               throw new IllegalStateException("<figure> has no end tag");
+       }
+
+
+       /**
+     * @param text2
+     * @return
+     */
+    private boolean isNeglectableFigureText(String text) {
+        if (text.matches("Fig\\.*")){
+            return true;
+        }else{
+            return false;
+        }
+    }
+
+
+    /**
+        * @param state
+        * @param id
+        * @param type
+        * @param urlString
+        * @param legendString
+        * @param titleString
+        * @param numString
+        * @param next
+        */
+       private Media makeFigure(MarkupImportState state, String id, String type, String urlString,
+                       String legendString, String titleString, String numString, XMLEvent next) {
+               Media media = null;
+//             boolean isFigure = false;  //no difference between figure and media since v3.3
+               try {
+                       //TODO maybe everything is a figure as it is all taken from a book
+                       if ("lineart".equals(type)) {
+//                             isFigure = true;
+//                             media = Figure.NewInstance(url.toURI(), null, null,     null);
+                       } else if (type == null || "photo".equals(type)
+                                       || "signature".equals(type)
+                                       || "others".equals(type)) {
+                               //TODO
+                       } else {
+                               String message = "Unknown figure type '%s'";
+                               message = String.format(message, type);
+                               fireWarningEvent(message, next, 2);
+                       }
+                       media = docImport.getImageMedia(urlString, docImport.getReadMediaData());
+
+                       if (media != null){
+                               // title
+                               if (StringUtils.isNotBlank(titleString)) {
+                                       media.putTitle(getDefaultLanguage(state), titleString);
+                               }
+                               // legend
+                               if (StringUtils.isNotBlank(legendString)) {
+                                       media.putDescription(getDefaultLanguage(state), legendString);
+                               }
+                               if (StringUtils.isNotBlank(numString)) {
+                                       // TODO use concrete source (e.g. DAPHNIPHYLLACEAE in FM
+                                       // vol.13)
+                                       Reference citation = state.getConfig().getSourceReference();
+                                       media.addSource(OriginalSourceType.Import, numString, "num", citation, null);
+                                       // TODO name used in source if available
+                               }
+                               // TODO which citation
+                               if (StringUtils.isNotBlank(id)) {
+                                       media.addSource(OriginalSourceType.Import, id, null, state.getConfig().getSourceReference(), null);
+                               } else {
+                                       String message = "Figure id should never be empty or null";
+                                       fireWarningEvent(message, next, 6);
+                               }
+
+                               // text
+                               // do nothing
+                               registerGivenFigure(state, next, id, media);
+
+                       }else{
+                               String message = "No media found: ";
+                               fireWarningEvent(message, next, 4);
+                       }
+               } catch (MalformedURLException e) {
+                       String message = "Media uri has incorrect syntax: %s";
+                       message = String.format(message, urlString);
+                       fireWarningEvent(message, next, 4);
+//             } catch (URISyntaxException e) {
+//                     String message = "Media uri has incorrect syntax: %s";
+//                     message = String.format(message, urlString);
+//                     fireWarningEvent(message, next, 4);
+               }
+
+               return media;
+       }
+
+
+       private void registerGivenFigure(MarkupImportState state, XMLEvent next, String id, Media figure) {
+               state.registerFigure(id, figure);
+               Set<AnnotatableEntity> demands = state.getFigureDemands(id);
+               if (demands != null) {
+                       for (AnnotatableEntity entity : demands) {
+                               attachFigure(state, next, entity, figure);
+                       }
+               }
+               save(figure, state);
+       }
+
+
+       private FootnoteDataHolder handleFootnoteRef(MarkupImportState state,
+                       XMLEventReader reader, XMLEvent parentEvent)
+                       throws XMLStreamException {
+               FootnoteDataHolder result = new FootnoteDataHolder();
+               Map<String, Attribute> attributes = getAttributes(parentEvent);
+               result.ref = getAndRemoveAttributeValue(attributes, REF);
+               checkNoAttributes(attributes, parentEvent);
+
+               // text is not handled, needed only for debugging purposes
+               String text = "";
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       // if (isStartingElement(next, FOOTNOTE_STRING)){
+                       // String string = handleFootnoteString(state, reader, next);
+                       // result.string = string;
+                       // }else
+                       if (isMyEndingElement(next, parentEvent)) {
+                               if (StringUtils.isNotBlank(text)){
+                                       fireWarningEvent("text is not empty but not handled during import", parentEvent, 4);
+                               }
+                               return result;
+                       } else if (next.isCharacters() && unhandledElements.isEmpty()) {
+                               text += next.asCharacters().getData();
+                       } else if (isStartingElement(next, NUM)) {
+                               //ignore numbering of footnotes as they are numbered differently in the CDM
+                               handleIgnoreElement(next);
+                       } else {
+                               handleUnexpectedElement(next);
+                       }
+               }
+               return result;
+       }
+
+
+
+       private String handleFootnoteString(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, MarkupSpecimenImport specimenImport, MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {
+               boolean isTextMode = true;
+               String text = "";
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       if (isMyEndingElement(next, parentEvent)) {
+                               return text;
+                       } else if (next.isEndElement()) {
+                               if (isEndingElement(next, FULL_NAME)) {
+                                       popUnimplemented(next.asEndElement());
+                               } else if (isEndingElement(next, BR)) {
+                                       isTextMode = true;
+                               } else if (isHtml(next)) {
+                                       text += getXmlTag(next);
+                               } else {
+                                       handleUnexpectedEndElement(next.asEndElement());
+                               }
+                       } else if (next.isStartElement()) {
+                               if (isStartingElement(next, FULL_NAME)) {
+                                       handleNotYetImplementedElement(next);
+                               } else if (isStartingElement(next, GATHERING)) {
+                                       text += specimenImport.handleInLineGathering(state, reader, next);
+                               } else if (isStartingElement(next, REFERENCES)) {
+                                       text += " " + handleInLineReferences(state, reader, next, nomenclatureImport) + " ";
+                               } else if (isStartingElement(next, BR)) {
+                                       text += "<br/>";
+                                       isTextMode = false;
+                               } else if (isStartingElement(next, NOMENCLATURE)) {
+                                       handleNotYetImplementedElement(next);
+                               } else if (isHtml(next)) {
+                                       text += getXmlTag(next);
+                               } else {
+                                       handleUnexpectedStartElement(next.asStartElement());
+                               }
+                       } else if (next.isCharacters()) {
+                               if (!isTextMode) {
+                                       String message = "footnoteString is not in text mode";
+                                       fireWarningEvent(message, next, 6);
+                               } else {
+                                       text += next.asCharacters().getData().trim();
+                                       // getCData(state, reader, next); does not work as we have inner tags like <references>
+                               }
+                       } else {
+                               handleUnexpectedEndElement(next.asEndElement());
+                       }
+               }
+               throw new IllegalStateException("<footnoteString> has no closing tag");
+
+       }
+
+       private static final List<String> htmlList = Arrays.asList("sub", "sup",
+                       "ol", "ul", "li", "i", "b", "table", "br","tr","td","th");
+
+       protected boolean isHtml(XMLEvent event) {
+               if (event.isStartElement()) {
+                       String tag = event.asStartElement().getName().getLocalPart();
+                       return htmlList.contains(tag);
+               } else if (event.isEndElement()) {
+                       String tag = event.asEndElement().getName().getLocalPart();
+                       return htmlList.contains(tag);
+               } else {
+                       return false;
+               }
+
+       }
+
+
+       private String handleInLineReferences(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent,
+               MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {
+               checkNoAttributes(parentEvent);
+
+               boolean hasReference = false;
+               String text = "";
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       if (isMyEndingElement(next, parentEvent)) {
+                               checkMandatoryElement(hasReference, parentEvent.asStartElement(), REFERENCE);
+                               return text;
+                       } else if (isStartingElement(next, REFERENCE)) {
+                               text += handleInLineReference(state, reader, next, nomenclatureImport);
+                               hasReference = true;
+                       } else {
+                               handleUnexpectedElement(next);
+                       }
+               }
+               throw new IllegalStateException("<References> has no closing tag");
+       }
+
+       private String handleInLineReference(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent, MarkupNomenclatureImport nomenclatureImport)throws XMLStreamException {
+               Reference reference = nomenclatureImport.handleReference(state, reader, parentEvent);
+               fireWarningEvent("Check correct usage of inline reference", parentEvent, 3);
+               IntextReference intext = IntextReference.NewInstance(reference, null, 0, 0);
+               save(reference, state);
+               return intext.toInlineString(reference.getTitleCache());
+       }
+
+       protected class SubheadingResult{
+           String text;
+           StringReferences references;
+        List<IntextReference> inlineReferences;
+       }
+
+       /**
+        * Handle < string > .
+        * @param state
+        * @param reader
+        * @param parentEvent
+        * @param feature only needed for distributionLocalities
+        * @return
+        * @throws XMLStreamException
+        */
+       protected Map<String, SubheadingResult> handleString(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, Feature feature)throws XMLStreamException {
+               // attributes
+               String classValue = getClassOnlyAttribute(parentEvent, false);
+               if (StringUtils.isNotBlank(classValue)) {
+                       String message = "class attribute for <string> not yet implemented";
+                       fireWarningEvent(message, parentEvent, 2);
+               }
+               boolean isHabitat = false;
+
+               // subheadings
+               Map<String, SubheadingResult> subHeadingMap = new HashMap<>();
+               String currentSubheading = null;
+
+               boolean isTextMode = true;
+               String text = "";
+               StringReferences currentReferences = null;
+               List<IntextReference> inlineReferences = new ArrayList<>();
+               boolean lastWasReference = false;
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       if (isMyEndingElement(next, parentEvent)) {
+                               putCurrentSubheading(subHeadingMap, currentSubheading, text, currentReferences, inlineReferences);
+                               return subHeadingMap;
+                       }
+                       //check if last event was reference
+                       if (lastWasReference && !isStartingElement(next, BR) && !isEndingElement(next, BR)
+                               && !isStartingElement(next, SUB_HEADING)){
+                           for (LabeledReference labeledRef : currentReferences.content){
+                               if (labeledRef.ref != null){
+                                   IntextReference intext = IntextReference.NewInstance(labeledRef.ref, null, 0, 0);
+                                   inlineReferences.add(intext);
+                                   text += intext.toInlineString(labeledRef.label);
+                               }else{
+                                   text += labeledRef.label;
+                               }
+                           }
+                           lastWasReference = false;
+                       }
+                       if (isStartingElement(next, BR)) {
+                               text += "<br/>";
+                               isTextMode = false;
+                       } else if (isEndingElement(next, BR)) {
+                               isTextMode = true;
+                       } else if (isHtml(next)) {
+                               text += getXmlTag(next);
+                       } else if (isStartingElement(next, SUB_HEADING)) {
+                               text = putCurrentSubheading(subHeadingMap, currentSubheading, text, currentReferences, inlineReferences);
+                               currentReferences = null;
+                               inlineReferences = new ArrayList<>();
+                               lastWasReference = false;
+                               // TODO footnotes
+                               currentSubheading = getCData(state, reader, next).trim();
+                       } else if (isStartingElement(next, DISTRIBUTION_LOCALITY)) {
+                               if (feature != null && !feature.equals(Feature.DISTRIBUTION())) {
+                                       String message = "Distribution locality only allowed for feature of type 'distribution'";
+                                       fireWarningEvent(message, next, 4);
+                               }
+                               text += handleDistributionLocality(state, reader, next);
+                       } else if (next.isCharacters()) {
+                               if (! isTextMode) {
+                                       String message = "String is not in text mode";
+                                       fireWarningEvent(message, next, 6);
+                               } else {
+                                       text += next.asCharacters().getData();
+                               }
+                       } else if (isStartingElement(next, HEADING)) {
+                               //TODO
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, VERNACULAR_NAMES)) {
+                               //TODO
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, QUOTE)) {
+                               //TODO
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, DEDICATION)) {
+                               //TODO
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, TAXONTYPE)) {
+                               //TODO
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, FULL_NAME)) {
+                               //TODO
+                               handleNotYetImplementedElement(next);
+                       }else if (isStartingElement(next, REFERENCES)) {
+                               if (currentReferences != null){
+                                   fireWarningEvent("References do already exist", next, 2);
+                               }
+                           currentReferences = handleStringReferences(state, reader, next);
+                           lastWasReference = true;
+                       }else if (isStartingElement(next, REFERENCE)) {
+                //TODO
+                handleNotYetImplementedElement(next);
+            } else if (isStartingElement(next, GATHERING)) {
+                               //TODO
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, ANNOTATION)) {
+                               //TODO  //TODO test handleSimpleAnnotation
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, HABITAT)) {
+                           text += featureImport.handleHabitat(state, reader, next);
+                           isHabitat = true;
+                       } else if (isStartingElement(next, FIGURE_REF)) {
+                               //TODO
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, FIGURE)) {
+                               //TODO
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, FOOTNOTE_REF)) {
+                               //TODO
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, FOOTNOTE)) {
+                               //TODO
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, WRITER)) {
+                               //TODO
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, DATES)) {
+                               //TODO
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, TO_KEY)) {
+                           handleNotYetImplementedElement(next);
+                       } else {
+                               handleUnexpectedElement(next);
+                       }
+               }
+               throw new IllegalStateException("<String> has no closing tag");
+       }
+
+
+       /**
+        * container class more or less representing a list of labeled references
+        */
+       protected class StringReferences{
+           String subheading;
+           List<LabeledReference> content = new ArrayList<>() ; //either String or LabeledReference
+           @Override
+        public String toString(){
+               String result = null;
+               for (LabeledReference labRef : content){
+                   result = CdmUtils.concat("", labRef.label);
+               }
+               return result;
+           }
+        public List<LabeledReference> getReferences() {
+            List<LabeledReference> result = new ArrayList<>();
+            for (LabeledReference labRef : content){
+                if (labRef.ref != null){
+                    result.add(labRef);
+                }
+            }
+            return result;
+        }
+       }
+
+       protected class LabeledReference{
+           public LabeledReference(Reference ref, String detail, String label) {
+            this.ref = ref; this.detail = detail; this.label = label;
+        }
+           protected Reference ref;  //if null, this LabeledReference represents only a string in between references
+           protected String detail; //micro reference
+           protected String label;
+       }
+
+    private StringReferences handleStringReferences(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
+        checkNoAttributes(parentEvent);
+        StringReferences result = new StringReferences();
+        while (reader.hasNext()) {
+            XMLEvent next = readNoWhitespace(reader);
+            if (isMyEndingElement(next, parentEvent)) {
+                return result;
+            } else if (isStartingElement(next, SUB_HEADING)) {
+                String subheading = getCData(state, reader, next);
+                if (!subheading.matches("(References?|Literature):?")){
+                    fireWarningEvent("Subheading for references not recognized: " + subheading, next, 4);
+                }
+                result.subheading = subheading;
+            } else if (isStartingElement(next, REFERENCE)) {
+                handleInlineReference(state, reader, next, result);
+            } else {
+                handleUnexpectedElement(next);
+            }
+        }
+        throw new IllegalStateException("<References> has no closing tag");
+    }
+
+    private void handleInlineReference(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent,
+            StringReferences result) throws XMLStreamException {
+        checkNoAttributes(parentEvent);
+        boolean hasRefPart = false;
+        Map<String, String> refMap = new HashMap<>();
+        String label = "";
+        while (reader.hasNext()) {
+            XMLEvent next = readNoWhitespace(reader);
+            if (isMyEndingElement(next, parentEvent)) {
+                checkMandatoryElement(hasRefPart, parentEvent.asStartElement(), REF_PART);
+                String details = refMap.get(DETAILS);
+//              String label = makeLabel(state, refMap, next);
+                Reference ref = createReference(state, refMap, next);
+                ref = state.getDeduplicationHelper(docImport).getExistingReference(state, ref);
+
+                String label2 = ref.getTitleCache(); //TODO preliminary for debugging and testing
+                result.content.add(new LabeledReference(ref, details, label));
+                return;
+            } else if (isStartingElement(next, REF_PART)) {
+                String classValue = handleRefPart(state, reader, next, refMap);
+                String text = refMap.get(classValue);
+                if (classValue.equals(YEAR)){
+                    text = "("+text+")";
+                }
+                hasRefPart = true;
+                label = CdmUtils.concat(" ", label, text);
+            } else {
+                handleUnexpectedElement(next);
+            }
+        }
+        throw new IllegalStateException("<References> has no closing tag");
+
+    }
+
+
+//    this is more or less a duplicate Nomenclature import, maybe merge later
+    private Reference createReference(MarkupImportState state,
+            Map<String, String> refMap, XMLEvent parentEvent) {
+
+        Reference reference;
+
+        String type = getAndRemoveMapKey(refMap, PUBTYPE);
+        String authorStr = getAndRemoveMapKey(refMap, AUTHOR);
+        String titleStr = getAndRemoveMapKey(refMap, PUBTITLE);
+        String titleCache = getAndRemoveMapKey(refMap, PUBFULLNAME);
+        String volume = getAndRemoveMapKey(refMap, VOLUME);
+        String edition = getAndRemoveMapKey(refMap, EDITION);
+        String editors = getAndRemoveMapKey(refMap, EDITORS);
+        String year = getAndRemoveMapKey(refMap, YEAR);
+        String pubName = getAndRemoveMapKey(refMap, PUBNAME);
+        String pages = getAndRemoveMapKey(refMap, PAGES);
+        String publication = getAndRemoveMapKey(refMap, PUBLOCATION);
+        String publisher = getAndRemoveMapKey(refMap, PUBLISHER);
+        String appendix = getAndRemoveMapKey(refMap, APPENDIX);
+        String issue = getAndRemoveMapKey(refMap, ISSUE);
+
+        reference = handleNonCitationSpecific(state, type, authorStr, titleStr,
+                    titleCache, volume, issue, edition, editors, pubName, appendix, pages, parentEvent);
+
+        //year
+        TimePeriod timeperiod = TimePeriodParser.parseString(year);
+        if (reference.getType().equals(ReferenceType.BookSection)){
+            reference.getInBook().setDatePublished(timeperiod);
+        }
+        reference.setDatePublished(timeperiod);
+
+        //Quickfix for these 2 attributes (publication, publisher) used in feature.references
+        Reference inRef = reference.getInReference() == null ? reference : reference.getInReference();
+        //publication
+        if (isNotBlank(publisher)){
+            inRef.setPublisher(publisher);
+        }
+
+        //publisher
+        if (isNotBlank(publication)){
+            inRef.setPlacePublished(publication);
+        }
+
+        // TODO
+        String[] unhandledList = new String[] { ALTERNATEPUBTITLE, NOTES, STATUS };
+        for (String unhandled : unhandledList) {
+            String value = getAndRemoveMapKey(refMap, unhandled);
+            if (isNotBlank(value)) {
+                this.handleNotYetImplementedAttributeValue(parentEvent, CLASS, unhandled);
+            }
+        }
+
+        for (String key : refMap.keySet()) {
+            if (!DETAILS.equalsIgnoreCase(key)) {
+                this.fireUnexpectedAttributeValue(parentEvent, CLASS, key);
+            }
+        }
+
+        return reference;
+    }
+
+
+    /**
+     * Create reference for non nomenclatural references
+     * @return
+     */
+    protected Reference handleNonCitationSpecific(MarkupImportState state, String type, String authorStr,
+            String titleStr, String titleCache, String volume, String issue, String edition,
+            String editors, String pubName, String appendix, String pages, XMLEvent parentEvent) {
+
+        Reference reference;
+
+        //volume / issue
+        if (isBlank(volume) && isNotBlank(issue)){
+            String message = "Issue ('"+issue+"') exists but no volume";
+            fireWarningEvent(message, parentEvent, 4);
+            volume = issue;
+        }else if (isNotBlank(issue)){
+            volume = volume + "("+ issue + ")";
+        }
+
+        //pubName / appendix
+        if (isNotBlank(appendix)){
+            pubName = pubName == null ?  appendix : (pubName + " " + appendix).replaceAll("  ", " ");
+        }
+
+        if (isArticleNonCitation(type, pubName, volume, editors)) {
+            IArticle article = ReferenceFactory.newArticle();
+            if (pubName != null) {
+                IJournal journal = ReferenceFactory.newJournal();
+                journal.setTitle(pubName);
+                article.setInJournal(journal);
+            }else{
+                fireWarningEvent("Article has no journal", parentEvent, 4);
+            }
+            reference = (Reference) article;
+        } else {
+            if (isBookSection(type, authorStr, titleStr, editors, pubName, volume)){
+                IBookSection bookSection = ReferenceFactory.newBookSection();
+                if (pubName != null) {
+                    IBook book = ReferenceFactory.newBook();
+                    book.setTitle(pubName);
+                    bookSection.setInBook(book);
+                }
+                reference = (Reference)bookSection;
+            }else{
+                //??
+                Reference bookOrPartOf = ReferenceFactory.newGeneric();
+                if (pubName != null && titleStr != null) {
+                    Reference inReference = ReferenceFactory.newGeneric();
+                    inReference.setTitle(pubName);
+                    bookOrPartOf.setInReference(inReference);
+                }
+                reference = bookOrPartOf;
+            }
+        }
+
+        //author
+        TeamOrPersonBase<?> author = createAuthor(state, authorStr);
+        reference.setAuthorship(author);
+
+        //title
+        reference.setTitle(titleStr);
+        if (StringUtils.isNotBlank(titleCache)) {
+            reference.setTitleCache(titleCache, true);
+        }
+
+        //edition
+        if(reference.getInReference() != null){
+            reference.getInReference().setEdition(edition);
+            reference.getInReference().setEditor(editors);
+        }else{
+            //edition
+            reference.setEdition(edition);
+            reference.setEditor(editors);
+        }
+
+        //volume
+        reference.setVolume(volume);
+
+        //pages
+        reference.setPages(pages);
+
+        return reference;
+    }
+
+    private boolean isBookSection(String type, String authorStr, String pubTitle,
+            String editors, String pubName, String volume) {
+        //type not yet handled
+        if (authorStr != null && editors != null
+                && pubTitle != null && pubName != null){
+            return true;
+        }else if (pubTitle != null && pubName != null && volume == null){
+            return true;
+        }else{
+            return false;
+        }
+    }
+
+
+    private boolean isArticleNonCitation(String type, String pubName, String volume, String editors) {
+        if ("journal".equalsIgnoreCase(type)){
+            return true;
+        }else if (volume != null && editors == null){
+            if (pubName != null && IJournal.guessIsJournalName(pubName)){
+                return true;
+            }else{
+                return false;  //unclear
+            }
+        }else{
+            return false;
+        }
+    }
+
+    protected String handleRefPart(MarkupImportState state, XMLEventReader reader,
+            XMLEvent parentEvent, Map<String, String> refMap)
+            throws XMLStreamException {
+        String classValue = getClassOnlyAttribute(parentEvent);
+
+        String text = "";
+        while (reader.hasNext()) {
+            XMLEvent next = readNoWhitespace(reader);
+            if (isMyEndingElement(next, parentEvent)) {
+                refMap.put(classValue, text);
+                return classValue;
+            } else if (next.isStartElement()) {
+                if (isStartingElement(next, ANNOTATION)) {
+                    handleNotYetImplementedElement(next); // TODO test handleSimpleAnnotation
+                } else if (isStartingElement(next, ITALICS)) {
+                    handleNotYetImplementedElement(next);
+                } else if (isStartingElement(next, BOLD)) {
+                    handleNotYetImplementedElement(next);
+                } else {
+                    handleUnexpectedStartElement(next.asStartElement());
+                }
+            } else if (next.isCharacters()) {
+                text += next.asCharacters().getData();
+            } else {
+                handleUnexpectedEndElement(next.asEndElement());
+            }
+        }
+        throw new IllegalStateException("RefPart has no closing tag");
+    }
+
+
+    private boolean isBlankOrPunctuation(String text) {
+        if (text == null){
+            return true;
+        } else {
+            return text.matches("^[\\s\\.,;:]*$");
+        }
+    }
+
+
+    /**
+     *Is heading an "habitat" type heading
+     * @param heading
+     * @return true if heading matches something like Eco(logy), Habitat(s) or Habitat & Ecology
+     */
+    private boolean isHabitatHeading(String heading) {
+        return heading.trim().matches("(Ecol(ogy)?|Habitat|Habitat\\s&\\sEcology)\\.?");
+    }
+
+
+       private String putCurrentSubheading(Map<String, SubheadingResult> subHeadingMap, String currentSubheading,
+               String text, StringReferences fullReferences, List<IntextReference> inlineReferences) {
+               if (isNotBlank(text) || (fullReferences != null && isNotEmptyCollection(fullReferences.content))
+                       ||isNotEmptyCollection(inlineReferences)) {
+                       SubheadingResult result = new SubheadingResult();
+                       text = removeStartingMinus(text);
+                       result.text = text.trim();
+                       result.references = fullReferences == null ? new StringReferences() : fullReferences;
+                       result.inlineReferences = inlineReferences;
+            subHeadingMap.put(currentSubheading, result);
+               }
+               return "";
+       }
+
+       /**
+     * @param references2
+     * @return
+     */
+    protected boolean isNotEmptyCollection(Collection<?> list) {
+        return list != null && !list.isEmpty();
+    }
+
+
+    private String removeStartingMinus(String string) {
+               string = replaceStart(string, "-");
+               string = replaceStart(string, "\u002d");
+               string = replaceStart(string, "\u2013");
+               string = replaceStart(string, "\u2014");
+               string = replaceStart(string, "--");
+               return string;
+       }
+
+
+       /**
+        * @param value
+        * @param replacementString
+        */
+       private String replaceStart(String value, String replacementString) {
+               if (value.startsWith(replacementString) ){
+                       value = value.substring(replacementString.length()).trim();
+               }
+               while (value.startsWith("-") || value.startsWith("\u2014") ){
+                       value = value.substring("-".length()).trim();
+               }
+               return value;
+       }
+
+
+       private String handleDistributionLocality(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent)throws XMLStreamException {
+               Map<String, Attribute> attributes = getAttributes(parentEvent);
+               String classValue = getAndRemoveRequiredAttributeValue(parentEvent, attributes, CLASS);
+               String statusValue =getAndRemoveAttributeValue(attributes, STATUS);
+               String frequencyValue =getAndRemoveAttributeValue(attributes, FREQUENCY);
+
+               Taxon taxon = state.getCurrentTaxon();
+               // TODO which ref to take?
+               Reference sourceReference = state.getConfig().getSourceReference();
+
+               String text = "";
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       if (isMyEndingElement(next, parentEvent)) {
+                               if (StringUtils.isNotBlank(text)) {
+                                       String label = CdmUtils.removeTrailingDot(normalize(text));
+                                       TaxonDescription description = getExtractedMarkupMarkedDescription(state, taxon, sourceReference);
+                                       NamedAreaLevel level = makeNamedAreaLevel(state,classValue, next);
+
+                                       //status
+                                       PresenceAbsenceTerm status = null;
+                                       if (isNotBlank(statusValue)){
+                                               try {
+                                                       status = state.getTransformer().getPresenceTermByKey(statusValue);
+                                                       if (status == null){
+                                                           UUID uuid = state.getTransformer().getPresenceTermUuid(statusValue);
+                                                           if (uuid != null){
+                                                               status = this.getPresenceAbsenceTerm(state, uuid, statusValue, statusValue, statusValue, false, null);
+                                                           }
+                                                       }
+                                                       if (status == null){
+                                                               //TODO
+                                                               String message = "The presence/absence status '%s' could not be transformed to an CDM status";
+                                                               fireWarningEvent(String.format(message, statusValue), next, 4);
+                                                       }
+                                               } catch (UndefinedTransformerMethodException e) {
+                                                       throw new RuntimeException(e);
+                                               }
+                                       }else{
+                                               status = PresenceAbsenceTerm.PRESENT();
+                                       }
+                                       //frequency
+                                       if (isNotBlank(frequencyValue)){
+                                               if (frequencyValue.equalsIgnoreCase("absent") && PresenceAbsenceTerm.PRESENT().equals(status)){ //to be on the safe side that not real status has been defined yet.
+                                                   status = PresenceAbsenceTerm.ABSENT();
+                                               }else{
+                                                   String message = "The frequency attribute is currently not yet available in CDM";
+                                                   fireWarningEvent(message, parentEvent, 6);
+                                               }
+                                       }
+
+                                       NamedArea higherArea = null;
+                                       List<NamedArea> areas = new ArrayList<>();
+
+                                       String patSingleArea = "([^,\\(]{3,})";
+                                       String patSeparator = "(,|\\sand\\s)";
+                                       String hierarchiePattern = String.format("%s\\((%s(%s%s)*)\\)", patSingleArea, patSingleArea, patSeparator, patSingleArea);
+                                       Pattern patHierarchie = Pattern.compile(hierarchiePattern, Pattern.CASE_INSENSITIVE);
+                                       Matcher matcher = patHierarchie.matcher(label);
+                                       if (matcher.matches()){
+                                               String higherAreaStr = matcher.group(1).trim();
+                                               higherArea =  makeArea(state, higherAreaStr, level);
+                                               String[] innerAreas = matcher.group(2).split(patSeparator);
+                                               for (String innerArea : innerAreas){
+                                                       if (isNotBlank(innerArea)){
+                                                               NamedArea singleArea = makeArea(state, innerArea.trim(), level);
+                                                               areas.add(singleArea);
+                                                               NamedArea partOf = singleArea.getPartOf();
+//                                                             if (partOf == null){
+//                                                                     singleArea.setPartOf(higherArea);
+//                                                             }
+                                                       }
+                                               }
+                                       }else{
+                                               NamedArea singleArea = makeArea(state, label, level);
+                                               areas.add(singleArea);
+                                       }
+
+                                       for (NamedArea area : areas){
+                                               //create distribution
+                                               Distribution distribution = Distribution.NewInstance(area,status);
+                                               distribution.addPrimaryTaxonomicSource(sourceReference);
+                                               description.addElement(distribution);
+                                       }
+                               } else {
+                                       String message = "Empty distribution locality";
+                                       fireWarningEvent(message, next, 4);
+                               }
+                               return text;
+                       } else if (isStartingElement(next, COORDINATES)) {
+                               //TODO
+                               handleNotYetImplementedElement(next);
+                       } else if (isEndingElement(next, COORDINATES)) {
+                               //TODO
+                               popUnimplemented(next.asEndElement());
+                       } else if (next.isCharacters()) {
+                               text += next.asCharacters().getData();
+                       } else {
+                               handleUnexpectedElement(next);
+                       }
+               }
+               throw new IllegalStateException("<DistributionLocality> has no closing tag");
+       }
+
+          /**
+     * @param state
+     * @param taxon
+     * @param ref
+     * @return
+     */
+    protected TaxonDescription getExtractedMarkupMarkedDescription(MarkupImportState state, Taxon taxon, Reference sourceReference) {
+        MarkerType markerType = getMarkerType(
+                state,
+                MarkupTransformer.uuidMarkerExtractedMarkupData,
+                "Extracted factual data", "Marker type for factual data imported from markup where the markup for this data was included in parent markup that was also imported including the text from this markup.",
+                "Extr. data",
+                null);
+        String title = "Extracted markup data for " + taxon.getName().getTitleCache();
+        TaxonDescription description = getMarkedTaxonDescription(taxon, markerType, false, true, sourceReference, title);
+        return description;
+    }
+
+}