ref #6369 adapt existing occurrences of interface to removed generics
[cdmlib.git] / cdmlib-io / src / main / java / eu / etaxonomy / cdm / io / markup / MarkupImportBase.java
index 5c9c6ad1076d113569d9b02b144f7758f81955ed..8e325481a183c9cc0b29d0aa2398ecb1c8cfa20b 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.PresenceAbsenceTerm;\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
-       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 DOUBTFUL = "doubtful";\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
-       protected static final String UNKNOWN = "unknown";\r
-       \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 WRITER = "writer";\r
-       \r
-       protected static final String LOCALITY = "locality";\r
-       \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
-       //Feature\r
-       protected static final String VERNACULAR_NAMES = "vernacularNames";\r
-       protected static final String VERNACULAR_NAME = "vernacularName";\r
-       protected static final String TRANSLATION = "translation";\r
-       protected static final String LOCAL_LANGUAGE = "localLanguage";\r
-       \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
-                       if (rank == null && "sous-genre".equalsIgnoreCase(value)){\r
-                               rank = Rank.SUBGENUS();\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;  //no difference between figure and media since v3.3\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());\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
-                                       PresenceAbsenceTerm 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 = PresenceAbsenceTerm.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.Team;
+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.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.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.Reference;
+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;
+
+/**
+ * @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 = "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<String, Attribute>();
+               if (!event.isStartElement()){
+                       fireWarningEvent("Event is not an startElement. Can't check attributes", makeLocationStr(event.getLocation()), 1, 1);
+                       return result;
+               }
+               StartElement element = event.asStartElement();
+               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.
+        * @param attributes
+        * @param attrName
+        * @return
+        */
+       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
+        */
+       protected void handleNotYetImplementedAttribute(Map<String, Attribute>  attributes, String attrName) {
+               Attribute attr = attributes.get(attrName);
+               if (attr != null){
+                       attributes.remove(attrName);
+                       QName qName = attr.getName();
+                       fireNotYetImplementedAttribute(attr.getLocation(), qName, 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, int stackDepth) {
+               String message = "Attribute not yet implemented: %s";
+               IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart()), 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 true 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, isImageGallery, createNewIfNotExists);
+       }
+
+
+       /**
+        * 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;
+       }
+
+
+
+       protected TeamOrPersonBase<?> createAuthor(String authorTitle) {
+               // TODO atomize and also use by name creation
+               TeamOrPersonBase<?> result = Team.NewTitledInstance(authorTitle, authorTitle);
+               return 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 NonViralName<?> createNameByCode(MarkupImportState state, Rank rank) {
+               NonViralName<?> name;
+               NomenclaturalCode nc = makeNomenclaturalCode(state);
+               name = (NonViralName<?>) nc.getNewTaxonNameInstance(rank);
+               return name;
+       }
+
+       protected void handleFullName(MarkupImportState state, XMLEventReader reader,
+                       INonViralName name, XMLEvent next) throws XMLStreamException {
+               String fullNameStr;
+               Map<String, Attribute> attrs = getAttributes(next);
+               String rankStr = getAndRemoveRequiredAttributeValue(next,
+                               attrs, "rank");
+               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, next, 6);
+                       name.setRank(Rank.UNKNOWN_RANK());
+               }
+               if (!attrs.isEmpty()) {
+                       handleUnexpectedAttributes(next.getLocation(), attrs);
+               }
+//             next = readNoWhitespace(reader);
+               fullNameStr = getCData(state, reader, next, false);
+               NonViralNameParserImpl.NewInstance().parseFullName(name, fullNameStr, rank, false);
+//             name.setTitleCache(fullNameStr, true);
+       }
+
+
+       /**
+        * 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);
+                               } 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<FootnoteDataHolder>();
+
+               // 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<AnnotatableEntity>();
+                               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);
+               String result = "<cdm:ref uuid='%s'>%s</ref>";
+               result = String.format(result, reference.getUuid(), reference.getTitleCache());
+               save(reference, state);
+               return result;
+       }
+
+
+       /**
+        * Handle string
+        * @param state
+        * @param reader
+        * @param parentEvent
+        * @param feature only needed for distributionLocalities
+        * @return
+        * @throws XMLStreamException
+        */
+       protected Map<String, String> 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, String> subHeadingMap = new HashMap<String, String>();
+               String currentSubheading = null;
+
+               boolean isTextMode = true;
+               String text = "";
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       if (isMyEndingElement(next, parentEvent)) {
+                               putCurrentSubheading(subHeadingMap, currentSubheading, text);
+                               if (isHabitat ){
+                                   if (currentSubheading != null && ! isHabitatHeading(currentSubheading) || ! isBlankOrPunctuation(text)){
+                                       String message = "String is habitat but currentSubHeading or text is not blank: " + CdmUtils.concat(": ", currentSubheading, text);
+                                       fireWarningEvent(message, next, 4);
+                                   }
+                               }
+                               return subHeadingMap;
+                       } else 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);
+                               // 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)) {
+                               //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)) {
+                           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 {
+                               handleUnexpectedElement(next);
+                       }
+               }
+               throw new IllegalStateException("<String> has no closing tag");
+       }
+
+
+    /**
+     * @param text2
+     * @return
+     */
+    private boolean isBlankOrPunctuation(String text) {
+        if (text == null){
+            return true;
+        } else {
+            return text.matches("^[\\s\\.,;:]*$");
+        }
+    }
+
+
+    /**
+     * @param currentSubheading
+     * @return
+     */
+    private boolean isHabitatHeading(String heading) {
+        return heading.trim().matches("(Ecol(ogy)?|Habitat|Habitat\\s&\\sEcology)\\.?");
+    }
+
+
+    /**
+        * @param subHeadingMap
+        * @param currentSubheading
+        * @param text
+        * @return
+        */
+       private String putCurrentSubheading(Map<String, String> subHeadingMap, String currentSubheading, String text) {
+               if (StringUtils.isNotBlank(text)) {
+                       text = removeStartingMinus(text);
+                       subHeadingMap.put(currentSubheading, text.trim());
+               }
+               return "";
+       }
+
+       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 ref = 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 = getTaxonDescription(taxon, ref, false, true);
+                                       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)){
+                                               String message = "The frequency attribute is currently not yet available in CDM";
+                                               fireWarningEvent(message, parentEvent, 6);
+                                       }
+
+                                       NamedArea higherArea = null;
+                                       List<NamedArea> areas = new ArrayList<NamedArea>();
+
+                                       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);
+                                               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");
+       }
+
+
+//********************************************** OLD *************************************
+
+//     protected boolean testAdditionalElements(Element parentElement, List<String> excludeList){
+//             boolean result = true;
+//             List<Element> list = parentElement.getChildren();
+//             for (Element element : list){
+//                     if (! excludeList.contains(element.getName())){
+//                             logger.warn("Unknown element (" + element.getName() + ") in parent element (" + parentElement.getName() + ")");
+//                             result = false;
+//                     }
+//             }
+//             return result;
+//     }
+//
+//
+//     protected <T extends IdentifiableEntity> T makeReferenceType(Element element, Class<? extends T> clazz, MapWrapper<? extends T> objectMap, ResultWrapper<Boolean> success){
+//             T result = null;
+//             String linkType = element.getAttributeValue("linkType");
+//             String ref = element.getAttributeValue("ref");
+//             if(ref == null && linkType == null){
+//                     result = getInstance(clazz);
+//                     if (result != null){
+//                             String title = element.getTextNormalize();
+//                             result.setTitleCache(title, true);
+//                     }
+//             }else if (linkType == null || linkType.equals("local")){
+//                     //TODO
+//                     result = objectMap.get(ref);
+//                     if (result == null){
+//                             logger.warn("Object (ref = " + ref + ")could not be found in WrapperMap");
+//                     }
+//             }else if(linkType.equals("external")){
+//                     logger.warn("External link types not yet implemented");
+//             }else if(linkType.equals("other")){
+//                     logger.warn("Other link types not yet implemented");
+//             }else{
+//                     logger.warn("Unknown link type or missing ref");
+//             }
+//             if (result == null){
+//                     success.setValue(false);
+//             }
+//             return result;
+//     }
+//
+//
+//     protected Reference makeAccordingTo(Element elAccordingTo, MapWrapper<Reference> referenceMap, ResultWrapper<Boolean> success){
+//             Reference result = null;
+//             if (elAccordingTo != null){
+//                     String childName = "AccordingToDetailed";
+//                     boolean obligatory = false;
+//                     Element elAccordingToDetailed = XmlHelp.getSingleChildElement(success, elAccordingTo, childName, elAccordingTo.getNamespace(), obligatory);
+//
+//                     childName = "Simple";
+//                     obligatory = true;
+//                     Element elSimple = XmlHelp.getSingleChildElement(success, elAccordingTo, childName, elAccordingTo.getNamespace(), obligatory);
+//
+//                     if (elAccordingToDetailed != null){
+//                             result = makeAccordingToDetailed(elAccordingToDetailed, referenceMap, success);
+//                     }else{
+//                             result = ReferenceFactory.newGeneric();
+//                             String title = elSimple.getTextNormalize();
+//                             result.setTitleCache(title, true);
+//                     }
+//             }
+//             return result;
+//     }
+//
+//
+//     private Reference makeAccordingToDetailed(Element elAccordingToDetailed, MapWrapper<Reference> referenceMap, ResultWrapper<Boolean> success){
+//             Reference result = null;
+//             Namespace tcsNamespace = elAccordingToDetailed.getNamespace();
+//             if (elAccordingToDetailed != null){
+//                     //AuthorTeam
+//                     String childName = "AuthorTeam";
+//                     boolean obligatory = false;
+//                     Element elAuthorTeam = XmlHelp.getSingleChildElement(success, elAccordingToDetailed, childName, tcsNamespace, obligatory);
+//                     makeAccordingToAuthorTeam(elAuthorTeam, success);
+//
+//                     //PublishedIn
+//                     childName = "PublishedIn";
+//                     obligatory = false;
+//                     Element elPublishedIn = XmlHelp.getSingleChildElement(success, elAccordingToDetailed, childName, tcsNamespace, obligatory);
+//                     result = makeReferenceType(elPublishedIn, Reference.class, referenceMap, success);
+//
+//                     //MicroReference
+//                     childName = "MicroReference";
+//                     obligatory = false;
+//                     Element elMicroReference = XmlHelp.getSingleChildElement(success, elAccordingToDetailed, childName, tcsNamespace, obligatory);
+//                     String microReference = elMicroReference.getTextNormalize();
+//                     if (CdmUtils.Nz(microReference).equals("")){
+//                             //TODO
+//                             logger.warn("MicroReference not yet implemented for AccordingToDetailed");
+//                     }
+//             }
+//             return result;
+//     }
+//
+//     private Team makeAccordingToAuthorTeam(Element elAuthorTeam, ResultWrapper<Boolean> succes){
+//             Team result = null;
+//             if (elAuthorTeam != null){
+//                     //TODO
+//                     logger.warn("AuthorTeam not yet implemented for AccordingToDetailed");
+//             }
+//             return result;
+//     }
+
+
+
+}