fix #6799: add totalCount of ticks as totalwork and start the progress monitor when...
[cdmlib.git] / cdmlib-io / src / main / java / eu / etaxonomy / cdm / io / markup / MarkupSpecimenImport.java
index 5c11d49fc936d7b98f9e634371d43c6a940d88bc..ae5f47248ec9eeeda35c92c84909e85e3d51960e 100644 (file)
-/**\r
- * Copyright (C) 2009 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.util.ArrayList;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-\r
-import javax.xml.stream.XMLEventReader;\r
-import javax.xml.stream.XMLStreamException;\r
-import javax.xml.stream.events.Attribute;\r
-import javax.xml.stream.events.XMLEvent;\r
-\r
-import org.apache.commons.lang.StringUtils;\r
-import org.apache.log4j.Logger;\r
-\r
-import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade;\r
-import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade.DerivedUnitType;\r
-import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeCacheStrategy;\r
-import eu.etaxonomy.cdm.common.CdmUtils;\r
-import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;\r
-import eu.etaxonomy.cdm.model.common.CdmBase;\r
-import eu.etaxonomy.cdm.model.common.TimePeriod;\r
-import eu.etaxonomy.cdm.model.description.DescriptionElementBase;\r
-import eu.etaxonomy.cdm.model.description.Feature;\r
-import eu.etaxonomy.cdm.model.description.IndividualsAssociation;\r
-import eu.etaxonomy.cdm.model.location.NamedArea;\r
-import eu.etaxonomy.cdm.model.location.NamedAreaLevel;\r
-import eu.etaxonomy.cdm.model.location.WaterbodyOrCountry;\r
-import eu.etaxonomy.cdm.model.name.HomotypicalGroup;\r
-import eu.etaxonomy.cdm.model.name.NonViralName;\r
-import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignationStatus;\r
-import eu.etaxonomy.cdm.model.name.TaxonNameBase;\r
-import eu.etaxonomy.cdm.model.occurrence.Collection;\r
-import eu.etaxonomy.cdm.model.occurrence.DerivedUnitBase;\r
-import eu.etaxonomy.cdm.model.occurrence.Specimen;\r
-import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;\r
-import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;\r
-import eu.etaxonomy.cdm.strategy.parser.SpecimenTypeParser;\r
-import eu.etaxonomy.cdm.strategy.parser.SpecimenTypeParser.TypeInfo;\r
-\r
-/**\r
- * @author a.mueller\r
- * @created 30.05.2012\r
- * \r
- */\r
-public class MarkupSpecimenImport extends MarkupImportBase  {\r
-       @SuppressWarnings("unused")\r
-       private static final Logger logger = Logger.getLogger(MarkupSpecimenImport.class);\r
-       \r
-       private static final String ALTERNATIVE_COLLECTION_TYPE_STATUS = "alternativeCollectionTypeStatus";\r
-       private static final String ALTERNATIVE_COLLECTOR = "alternativeCollector";\r
-       private static final String ALTERNATIVE_FIELD_NUM = "alternativeFieldNum";\r
-       private static final String COLLECTOR = "collector";\r
-       private static final String COLLECTION = "collection";\r
-       private static final String COLLECTION_AND_TYPE = "collectionAndType";\r
-       private static final String COLLECTION_TYPE_STATUS = "collectionTypeStatus";\r
-       private static final String DAY = "day";\r
-       private static final String DESTROYED = "destroyed";\r
-       private static final String FIELD_NUM = "fieldNum";\r
-       private static final String FULL_TYPE = "fullType";\r
-       private static final String FULL_DATE = "fullDate";\r
-       private static final String LOCALITY = "locality";\r
-       private static final String LOST = "lost";\r
-       private static final String MONTH = "month";\r
-       private static final String SUB_GATHERING = "subGathering";\r
-       private static final String NOT_FOUND = "notFound";\r
-       private static final String NOT_SEEN = "notSeen";\r
-       private static final String ORIGINAL_DETERMINATION = "originalDetermination";\r
-\r
-       private static final String UNKNOWN = "unknown";\r
-       private static final String YEAR = "year";\r
-\r
-\r
-\r
-       public MarkupSpecimenImport(MarkupDocumentImport docImport) {\r
-               super(docImport);\r
-       }\r
-       \r
-\r
-       public void handleSpecimenType(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent,\r
-                               HomotypicalGroup homotypicalGroup) throws XMLStreamException {\r
-       \r
-               // attributes\r
-               Map<String, Attribute> attributes = getAttributes(parentEvent);\r
-               String typeStatus = getAndRemoveAttributeValue(attributes, TYPE_STATUS);\r
-               String notSeen = getAndRemoveAttributeValue(attributes, NOT_SEEN);\r
-               String unknown = getAndRemoveAttributeValue(attributes, UNKNOWN);\r
-               String notFound = getAndRemoveAttributeValue(attributes, NOT_FOUND);\r
-               String destroyed = getAndRemoveAttributeValue(attributes, DESTROYED);\r
-               String lost = getAndRemoveAttributeValue(attributes, LOST);\r
-               checkNoAttributes(attributes, parentEvent);\r
-               if (StringUtils.isNotEmpty(typeStatus)) {\r
-                       // TODO\r
-                       // currently not needed\r
-               } else if (StringUtils.isNotEmpty(notSeen)) {\r
-                       handleNotYetImplementedAttribute(attributes, NOT_SEEN);\r
-               } else if (StringUtils.isNotEmpty(unknown)) {\r
-                       handleNotYetImplementedAttribute(attributes, UNKNOWN);\r
-               } else if (StringUtils.isNotEmpty(notFound)) {\r
-                       handleNotYetImplementedAttribute(attributes, NOT_FOUND);\r
-               } else if (StringUtils.isNotEmpty(destroyed)) {\r
-                       handleNotYetImplementedAttribute(attributes, DESTROYED);\r
-               } else if (StringUtils.isNotEmpty(lost)) {\r
-                       handleNotYetImplementedAttribute(attributes, LOST);\r
-               }\r
-\r
-               NonViralName<?> firstName = null;\r
-               Set<TaxonNameBase> names = homotypicalGroup.getTypifiedNames();\r
-               if (names.isEmpty()) {\r
-                       String message = "There is no name in a homotypical group. Can't create the specimen type";\r
-                       fireWarningEvent(message, parentEvent, 8);\r
-               } else {\r
-                       firstName = CdmBase.deproxy(names.iterator().next(),NonViralName.class);\r
-               }\r
-\r
-               DerivedUnitFacade facade = DerivedUnitFacade.NewInstance(DerivedUnitType.Specimen);\r
-               String text = "";\r
-               String collectionAndType = "";\r
-               // elements\r
-               while (reader.hasNext()) {\r
-                       XMLEvent next = readNoWhitespace(reader);\r
-                       if (isMyEndingElement(next, parentEvent)) {\r
-                               makeSpecimenType(state, facade, text, collectionAndType, firstName, parentEvent);\r
-                               return;\r
-                       } else if (isStartingElement(next, FULL_TYPE)) {\r
-                               handleNotYetImplementedElement(next);\r
-                               // homotypicalGroup = handleNom(state, reader, next, taxon,\r
-                               // homotypicalGroup);\r
-                       } else if (isStartingElement(next, TYPE_STATUS)) {\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, GATHERING)) {\r
-                               handleGathering(state, reader, next, facade);\r
-                       } else if (isStartingElement(next, ORIGINAL_DETERMINATION)) {\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, SPECIMEN_TYPE)) {\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, COLLECTION_AND_TYPE)) {\r
-                               collectionAndType += getCData(state, reader, next, true);\r
-                       } else if (isStartingElement(next, CITATION)) {\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, NOTES)) {\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, ANNOTATION)) {\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (next.isCharacters()) {\r
-                               text += next.asCharacters().getData();\r
-                       } else {\r
-                               handleUnexpectedElement(next);\r
-                       }\r
-               }\r
-               // TODO handle missing end element\r
-               throw new IllegalStateException("Specimen type has no closing tag"); \r
-       }\r
-\r
-       \r
-\r
-       private void makeSpecimenType(MarkupImportState state, DerivedUnitFacade facade, String text, String collectionAndType, \r
-                       NonViralName<?> name, XMLEvent parentEvent) {\r
-               text = text.trim();\r
-               if (isPunctuation(text)){\r
-                       return;\r
-               }else{\r
-                       String message = "Text '%s' not handled for <SpecimenType>";\r
-                       this.fireWarningEvent(String.format(message, text), parentEvent, 4);\r
-               }\r
-               \r
-               // remove brackets\r
-               if (collectionAndType.matches("^\\(.*\\)\\.?$")) {\r
-                       collectionAndType = collectionAndType.replaceAll("\\.", "");\r
-                       collectionAndType = collectionAndType.substring(1, collectionAndType.length() - 1);\r
-               }\r
-               \r
-               String[] split = collectionAndType.split("[;,]");\r
-               for (String str : split) {\r
-                       str = str.trim();\r
-                       boolean addToAllNamesInGroup = true;\r
-                       TypeInfo typeInfo = makeSpecimenTypeTypeInfo(str, parentEvent);\r
-                       SpecimenTypeDesignationStatus typeStatus = typeInfo.status;\r
-                       Collection collection = createCollection(typeInfo.collectionString);\r
-\r
-                       // TODO improve cache strategy handling\r
-                       DerivedUnitBase typeSpecimen = facade.addDuplicate(collection, null, null, null, null);\r
-                       typeSpecimen.setCacheStrategy(new DerivedUnitFacadeCacheStrategy());\r
-                       name.addSpecimenTypeDesignation((Specimen) typeSpecimen, typeStatus, null, null, null, false, addToAllNamesInGroup);\r
-               }\r
-       }\r
-       \r
-\r
-       private Collection createCollection(String code) {\r
-               // TODO deduplicate\r
-               // TODO code <-> name\r
-               Collection result = Collection.NewInstance();\r
-               result.setCode(code);\r
-               return result;\r
-       }\r
-       \r
-\r
-       private TypeInfo makeSpecimenTypeTypeInfo(String originalString, XMLEvent event) {\r
-               TypeInfo result = new TypeInfo();\r
-               String[] split = originalString.split("\\s+");\r
-               for (String str : split) {\r
-                       if (str.matches(SpecimenTypeParser.typeTypePattern)) {\r
-                               SpecimenTypeDesignationStatus status;\r
-                               try {\r
-                                       status = SpecimenTypeParser.parseSpecimenTypeStatus(str);\r
-                               } catch (UnknownCdmTypeException e) {\r
-                                       String message = "Specimen type status '%s' not recognized by parser";\r
-                                       fireWarningEvent(String.format(message, str), event, 4);\r
-                                       status = null;\r
-                               }\r
-                               result.status = status;\r
-                       } else if (str.matches(SpecimenTypeParser.collectionPattern)) {\r
-                               result.collectionString = str;\r
-                       } else {\r
-                               String message = "Type part '%s' could not be recognized";\r
-                               fireWarningEvent(String.format(message, str), event, 2);\r
-                       }\r
-               }\r
-\r
-               return result;\r
-       }\r
-       \r
-       \r
-       private void handleGathering(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent , DerivedUnitFacade facade) throws XMLStreamException {\r
-               checkNoAttributes(parentEvent);\r
-               boolean hasCollector = false;\r
-               boolean hasFieldNum = false;\r
-\r
-               // elements\r
-               while (reader.hasNext()) {\r
-                       XMLEvent next = readNoWhitespace(reader);\r
-                       if (isMyEndingElement(next, parentEvent)) {\r
-                               if (! hasCollector){\r
-                                       if (state.getCurrentCollector() == null){\r
-                                               checkMandatoryElement(hasCollector,parentEvent.asStartElement(), COLLECTOR);\r
-                                       }else{\r
-                                               facade.setCollector(state.getCurrentCollector());\r
-                                       }\r
-                               }\r
-                               checkMandatoryElement(hasFieldNum,parentEvent.asStartElement(), FIELD_NUM);\r
-                               return;\r
-                       }else if (isStartingElement(next, COLLECTOR)) {\r
-                               hasCollector = true;\r
-                               String collectorStr = getCData(state, reader, next);\r
-                               TeamOrPersonBase<?> collector = createCollector(collectorStr);\r
-                               facade.setCollector(collector);\r
-                               state.setCurrentCollector(collector);\r
-                       } else if (isStartingElement(next, ALTERNATIVE_COLLECTOR)) {\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, FIELD_NUM)) {\r
-                               hasFieldNum = true;\r
-                               String fieldNumStr = getCData(state, reader, next);\r
-                               facade.setFieldNumber(fieldNumStr);\r
-                       } else if (isStartingElement(next, ALTERNATIVE_FIELD_NUM)) {\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, COLLECTION_TYPE_STATUS)) {\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, COLLECTION_AND_TYPE)) {  //does this make sense here?\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, ALTERNATIVE_COLLECTION_TYPE_STATUS)) {\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, SUB_GATHERING)) {\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, COLLECTION)) {\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, LOCALITY)) {\r
-                               handleLocality(state, reader, next, facade);\r
-                       } else if (isStartingElement(next, DATES)) {\r
-                               TimePeriod timePeriod = handleDates(state, reader, next);\r
-                               facade.setGatheringPeriod(timePeriod);\r
-                       } else if (isStartingElement(next, NOTES)) {\r
-                               handleNotYetImplementedElement(next);\r
-                       } else {\r
-                               handleUnexpectedElement(next);\r
-                       }\r
-               }\r
-               throw new IllegalStateException("Collection has no closing tag.");\r
-\r
-       }\r
-       \r
-\r
-       private TimePeriod handleDates(MarkupImportState state, XMLEventReader reader, XMLEvent parent) throws XMLStreamException {\r
-               checkNoAttributes(parent);\r
-               TimePeriod result = TimePeriod.NewInstance();\r
-               String parseMessage = "%s can not be parsed: %s";\r
-               boolean hasFullDate = false;\r
-               boolean hasAtomised = false;\r
-               boolean hasUnparsedAtomised = false;\r
-               while (reader.hasNext()) {\r
-                       XMLEvent next = readNoWhitespace(reader);\r
-                       if (isMyEndingElement(next, parent)) {\r
-                               if (! isAlternative(hasFullDate, hasAtomised, hasUnparsedAtomised)){\r
-                                       String message = "Some problems exist when defining the date";\r
-                                       fireWarningEvent(message, parent, 4);\r
-                               }\r
-                               return result;\r
-                       } else if (isStartingElement(next, FULL_DATE)) {\r
-                               String fullDate = getCData(state, reader, next, true);\r
-                               result = TimePeriod.parseString(fullDate);\r
-                               if (result.getFreeText() != null){\r
-                                       fireWarningEvent(String.format(parseMessage, FULL_DATE, fullDate), parent, 1);\r
-                               }\r
-                               hasFullDate = true;\r
-                       } else if (isStartingElement(next, DAY)) {\r
-                               String day = getCData(state, reader, next, true).trim();\r
-                               day = normalizeDate(day);\r
-                               if (CdmUtils.isNumeric(day)){\r
-                                       result.setStartDay(Integer.valueOf(day));\r
-                                       hasAtomised = true;\r
-                               }else{\r
-                                       fireWarningEvent(String.format(parseMessage,"Day", day), parent, 2);\r
-                                       hasUnparsedAtomised = true;\r
-                               }\r
-                       } else if (isStartingElement(next, MONTH)) {\r
-                               String month = getCData(state, reader, next, true).trim();\r
-                               month = normalizeDate(month);\r
-                               if (CdmUtils.isNumeric(month)){\r
-                                       result.setStartMonth(Integer.valueOf(month));\r
-                                       hasAtomised = true;\r
-                               }else{\r
-                                       fireWarningEvent(String.format(parseMessage,"Month", month), parent, 2);\r
-                                       hasUnparsedAtomised = true;\r
-                               }\r
-                       } else if (isStartingElement(next, YEAR)) {\r
-                               String year = getCData(state, reader, next, true).trim();\r
-                               year = normalizeDate(year);\r
-                               if (CdmUtils.isNumeric(year)){\r
-                                       result.setStartYear(Integer.valueOf(year));\r
-                                       hasAtomised = true;\r
-                               }else{\r
-                                       fireWarningEvent(String.format(parseMessage,"Year", year), parent, 2);\r
-                                       hasUnparsedAtomised = true;\r
-                               }\r
-                       } else {\r
-                               handleUnexpectedElement(next);\r
-                       }\r
-               }\r
-               throw new IllegalStateException("Dates has no closing tag.");\r
-       }\r
-\r
-\r
-       private String normalizeDate(String partOfDate) {\r
-               if (isBlank(partOfDate)){\r
-                       return null;\r
-               }\r
-               partOfDate = partOfDate.trim();\r
-               while (partOfDate.startsWith("-")){\r
-                       partOfDate = partOfDate.substring(1);\r
-               }\r
-               return partOfDate;\r
-       }\r
-\r
-\r
-       private boolean isAlternative(boolean first, boolean second, boolean third) {\r
-               return ( (first ^ second) && !third)  || \r
-                               (! first && ! second && third) ;\r
-       }\r
-\r
-\r
-       private void handleLocality(MarkupImportState state, XMLEventReader reader,XMLEvent parentEvent, DerivedUnitFacade facade)throws XMLStreamException {\r
-               String classValue = getClassOnlyAttribute(parentEvent);\r
-               boolean isLocality = false;\r
-               NamedAreaLevel areaLevel = null;\r
-               if ("locality".equalsIgnoreCase(classValue)) {\r
-                       isLocality = true;\r
-               } else {\r
-                       areaLevel = makeNamedAreaLevel(state, classValue, parentEvent);\r
-               }\r
-\r
-               String text = "";\r
-               // elements\r
-               while (reader.hasNext()) {\r
-                       XMLEvent next = readNoWhitespace(reader);\r
-                       if (isMyEndingElement(next, parentEvent)) {\r
-                               if (StringUtils.isNotBlank(text)) {\r
-                                       text = normalize(text);\r
-                                       if (isLocality) {\r
-                                               facade.setLocality(text, getDefaultLanguage(state));\r
-                                       } else {\r
-                                               text = CdmUtils.removeTrailingDot(text);\r
-                                               NamedArea area = makeArea(state, text, areaLevel);\r
-                                               facade.addCollectingArea(area);\r
-                                       }\r
-                               }\r
-                               // TODO\r
-                               return;\r
-                       }else if (isStartingElement(next, ALTITUDE)) {\r
-                               handleNotYetImplementedElement(next);\r
-                               // homotypicalGroup = handleNom(state, reader, next, taxon,\r
-                               // homotypicalGroup);\r
-                       } else if (isStartingElement(next, COORDINATES)) {\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (isStartingElement(next, ANNOTATION)) {\r
-                               handleNotYetImplementedElement(next);\r
-                       } else if (next.isCharacters()) {\r
-                               text += next.asCharacters().getData();\r
-                       } else {\r
-                               handleUnexpectedElement(next);\r
-                       }\r
-               }\r
-               throw new IllegalStateException("<SpecimenType> has no closing tag"); \r
-       }\r
-\r
-\r
-\r
-       private TeamOrPersonBase<?> createCollector(String collectorStr) {\r
-               return createAuthor(collectorStr);\r
-       }\r
-\r
-       \r
-       public List<DescriptionElementBase> handleMaterialsExamined(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, Feature feature) throws XMLStreamException {\r
-               List<DescriptionElementBase> result = new ArrayList<DescriptionElementBase>();\r
-               //reset current areas\r
-               state.removeCurrentAreas();\r
-               while (reader.hasNext()) {\r
-                       XMLEvent next = readNoWhitespace(reader);\r
-                       if (isMyEndingElement(next, parentEvent)) {\r
-                               if (result.isEmpty()){\r
-                                       fireWarningEvent("Materials examined created empty Individual Associations list", parentEvent, 4);\r
-                               }\r
-                               state.removeCurrentAreas();\r
-                               return result;\r
-                       } else if (isStartingElement(next, SUB_HEADING)) {\r
-//                             Map<String, Object> inlineMarkup = new HashMap<String, Object>();\r
-                               String text = getCData(state, reader, next, true);\r
-                               if (isFeatureHeading(state, next, text)){\r
-                                       feature = makeHeadingFeature(state, next, text, feature);\r
-                               }else{\r
-                                       String message = "Unhandled subheading: %s";\r
-                                       fireWarningEvent(String.format(message,  text), next, 4);\r
-                               }\r
-//                             for (String key : inlineMarkup.keySet()){\r
-//                                     handleInlineMarkup(state, key, inlineMarkup);\r
-//                             }\r
-                               \r
-                       } else if (isStartingElement(next, BR) || isEndingElement(next, BR)) {\r
-                               //do nothing\r
-                       } else if (isStartingElement(next, GATHERING)) {\r
-                               DerivedUnitFacade facade = DerivedUnitFacade.NewInstance(DerivedUnitType.DerivedUnit.DerivedUnit);\r
-                               addCurrentAreas(state, next, facade);\r
-                               handleGathering(state, reader, next, facade);\r
-                               SpecimenOrObservationBase<?> specimen;\r
-                               if (facade.innerDerivedUnit() != null){\r
-                                       specimen = facade.innerDerivedUnit();\r
-                               }else{\r
-                                       specimen = facade.innerFieldObservation();\r
-                               }\r
-                               IndividualsAssociation individualsAssociation = IndividualsAssociation.NewInstance();\r
-                               individualsAssociation.setAssociatedSpecimenOrObservation(specimen);\r
-                               result.add(individualsAssociation);\r
-                       }else if (next.isCharacters()) {\r
-                               String text = next.asCharacters().getData().trim();\r
-                               if (isPunctuation(text)){\r
-                                       //do nothing\r
-                               }else{\r
-                                       String message = "Unrecognized text: %s";\r
-                                       fireWarningEvent(String.format(message, text), next, 6);\r
-                               }\r
-                       } else {\r
-                               handleUnexpectedElement(next);\r
-                       }\r
-               }\r
-               throw new IllegalStateException("<String> has no closing tag");\r
-               \r
-       }\r
-\r
-       \r
-\r
-private void addCurrentAreas(MarkupImportState state, XMLEvent event, DerivedUnitFacade facade) {\r
-               for (NamedArea area : state.getCurrentAreas()){\r
-                       if (area == null){\r
-                               continue;\r
-                       }else if (area.isInstanceOf(WaterbodyOrCountry.class)){\r
-                               facade.setCountry(area);\r
-                       }else{\r
-                               String message = "Current area %s is not country. This is not expected for currently known data.";\r
-                               fireWarningEvent(String.format(message, area.getTitleCache()), event, 2);\r
-                               facade.addCollectingArea(area);\r
-                       }\r
-               }\r
-               \r
-       }\r
-\r
-\r
-//     private void handleInlineMarkup(MarkupImportState state, String key, Map<String, Object> inlineMarkup) {\r
-//             Object obj = inlineMarkup.get(key);\r
-//             if (key.equals(LOCALITY)){\r
-//                     if (obj instanceof NamedArea){\r
-//                             NamedArea area = (NamedArea)obj;\r
-//                             state.addCurrentArea(area);\r
-//                     }\r
-//             }\r
-//             \r
-//     }\r
-\r
-\r
-       /**\r
-        * Changes the feature if the (sub)-heading implies this. Also recognizes hidden country information\r
-        * @param state \r
-        * @param parent\r
-        * @param text\r
-        * @param feature\r
-        * @return\r
-        */\r
-       private Feature makeHeadingFeature(MarkupImportState state, XMLEvent parent, String originalText, Feature feature) {\r
-               //expand, provide by config or service\r
-               String materialRegEx = "Mat[\u00E9\u00C9]riel";\r
-               String examinedRegEx = "[\u00E9\u00C9]tudi[\u00E9\u00C9]";\r
-               String countryRegEx = "(gabonais)";\r
-               String postfixCountryRegEx = "\\s+(pour le Gabon)";\r
-               \r
-               String materialExaminedRegEx = "(?i)" + materialRegEx + "\\s+(" + countryRegEx +"\\s+)?" + examinedRegEx + "(" +postfixCountryRegEx + ")?:?";\r
-               \r
-               String text = originalText;\r
-               \r
-               if (isBlank(text)){\r
-                       return feature;\r
-               }else{\r
-                       if (text.matches(materialExaminedRegEx)){\r
-                               //gabon specific\r
-                               if (text.contains("gabonais ")){\r
-                                       text = text.replace("gabonais ", "");\r
-                                       state.addCurrentArea(WaterbodyOrCountry.GABONGABONESEREPUBLIC());\r
-                               }\r
-                               if (text.contains(" pour le Gabon")){\r
-                                       text = text.replace(" pour le Gabon", "");\r
-                                       state.addCurrentArea(WaterbodyOrCountry.GABONGABONESEREPUBLIC());\r
-                               }\r
-                               \r
-                               //update feature\r
-                               feature = Feature.MATERIALS_EXAMINED();\r
-                               state.putFeatureToGeneralSorterList(feature);\r
-                               return feature;\r
-                       }else{\r
-                               String message = "Heading/Subheading not recognized: %s";\r
-                               fireWarningEvent(String.format(message, originalText), parent, 4);\r
-                               return feature;\r
-                       }\r
-               }\r
-       }\r
-\r
-\r
-       /**\r
-        * True if heading or subheading represents feature information\r
-        * @param state\r
-        * @param parent\r
-        * @param text\r
-        * @return\r
-        */\r
-       private boolean isFeatureHeading(MarkupImportState state, XMLEvent parent, String text) {\r
-               return makeHeadingFeature(state, parent, text, null) != null;\r
-       }\r
-\r
-\r
-       public String handleInLineGathering(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {\r
-               DerivedUnitFacade facade = DerivedUnitFacade.NewInstance(DerivedUnitType.DerivedUnit.FieldObservation);\r
-               handleGathering(state, reader, parentEvent, facade);\r
-               SpecimenOrObservationBase<?> specimen  = facade.innerFieldObservation();\r
-               if (specimen == null){\r
-                       specimen = facade.innerDerivedUnit();\r
-                       String message = "Inline gaterhing has no field observation";\r
-                       fireWarningEvent(message, parentEvent, 2);\r
-               }\r
-               \r
-               String result = "<cdm:specimen uuid='%s'>%s</specimen>";\r
-               if (specimen != null){\r
-                       result = String.format(result, specimen.getUuid(), specimen.getTitleCache());\r
-               }else{\r
-                       String message = "Inline gathering has no specimen";\r
-                       fireWarningEvent(message, parentEvent, 4);\r
-               }\r
-               save(specimen, state);\r
-               return result;  \r
-       }\r
-\r
-\r
-\r
-\r
-\r
-}\r
+/**
+ * Copyright (C) 2009 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.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.XMLEvent;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+
+import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade;
+import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeCacheStrategy;
+import eu.etaxonomy.cdm.common.CdmUtils;
+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.Language;
+import eu.etaxonomy.cdm.model.common.Marker;
+import eu.etaxonomy.cdm.model.common.MarkerType;
+import eu.etaxonomy.cdm.model.common.TimePeriod;
+import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
+import eu.etaxonomy.cdm.model.description.Feature;
+import eu.etaxonomy.cdm.model.description.IndividualsAssociation;
+import eu.etaxonomy.cdm.model.description.TaxonDescription;
+import eu.etaxonomy.cdm.model.location.Country;
+import eu.etaxonomy.cdm.model.location.NamedArea;
+import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
+import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
+import eu.etaxonomy.cdm.model.name.INonViralName;
+import eu.etaxonomy.cdm.model.name.Rank;
+import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
+import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignationStatus;
+import eu.etaxonomy.cdm.model.name.TaxonName;
+import eu.etaxonomy.cdm.model.occurrence.Collection;
+import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
+import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
+import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
+import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
+import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
+import eu.etaxonomy.cdm.model.reference.Reference;
+import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
+import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
+import eu.etaxonomy.cdm.strategy.parser.SpecimenTypeParser;
+import eu.etaxonomy.cdm.strategy.parser.SpecimenTypeParser.TypeInfo;
+import eu.etaxonomy.cdm.strategy.parser.TimePeriodParser;
+
+/**
+ * @author a.mueller
+ * @created 30.05.2012
+ *
+ */
+public class MarkupSpecimenImport extends MarkupImportBase  {
+       @SuppressWarnings("unused")
+       private static final Logger logger = Logger.getLogger(MarkupSpecimenImport.class);
+
+       private static final String ALTERNATIVE_COLLECTION_TYPE_STATUS = "alternativeCollectionTypeStatus";
+       private static final String ALTERNATIVE_COLLECTOR = "alternativeCollector";
+       private static final String ALTERNATIVE_FIELD_NUM = "alternativeFieldNum";
+       private static final String COLLECTOR = "collector";
+       private static final String COLLECTION = "collection";
+       private static final String COLLECTION_AND_TYPE = "collectionAndType";
+       private static final String COLLECTION_TYPE_STATUS = "collectionTypeStatus";
+       private static final String DAY = "day";
+       private static final String DESTROYED = "destroyed";
+       private static final String FIELD_NUM = "fieldNum";
+       private static final String FULL_TYPE = "fullType";
+       private static final String FULL_DATE = "fullDate";
+       private static final String GATHERING_NOTES = "gatheringNotes";
+       private static final String LOST = "lost";
+       private static final String MONTH = "month";
+       private static final String SUB_GATHERING = "subGathering";
+       private static final String NOT_FOUND = "notFound";
+       private static final String NOT_SEEN = "notSeen";
+       private static final String ORIGINAL_DETERMINATION = "originalDetermination";
+
+       private static final String UNKNOWN = "unknown";
+       private static final String YEAR = "year";
+
+
+
+       public MarkupSpecimenImport(MarkupDocumentImport docImport) {
+               super(docImport);
+       }
+
+
+       public void handleSpecimenType(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent,
+                               HomotypicalGroup homotypicalGroup) throws XMLStreamException {
+
+               // attributes
+               Map<String, Attribute> attributes = getAttributes(parentEvent);
+               String typeStatus = getAndRemoveAttributeValue(attributes, TYPE_STATUS);
+               String notSeen = getAndRemoveAttributeValue(attributes, NOT_SEEN);
+               String unknown = getAndRemoveAttributeValue(attributes, UNKNOWN);
+               String notFound = getAndRemoveAttributeValue(attributes, NOT_FOUND);
+               String destroyed = getAndRemoveAttributeValue(attributes, DESTROYED);
+               String lost = getAndRemoveAttributeValue(attributes, LOST);
+               checkNoAttributes(attributes, parentEvent);
+               if (isNotBlank(typeStatus)) {
+                       // TODO
+                       // currently not needed
+                       fireWarningEvent("Type status not yet used", parentEvent, 4);
+               }
+               if (isNotBlank(notSeen)) {
+                       handleNotYetImplementedAttribute(attributes, NOT_SEEN, parentEvent);
+               }
+               if (isNotBlank(unknown)) {
+                       handleNotYetImplementedAttribute(attributes, UNKNOWN, parentEvent);
+               }
+               if (isNotBlank(notFound)) {
+                       handleNotYetImplementedAttribute(attributes, NOT_FOUND, parentEvent);
+               }
+               if (isNotBlank(destroyed)) {
+                       handleNotYetImplementedAttribute(attributes, DESTROYED, parentEvent);
+               }
+               if (isNotBlank(lost)) {
+                       handleNotYetImplementedAttribute(attributes, LOST, parentEvent);
+               }
+
+               INonViralName firstName = null;
+               Set<TaxonName> names = homotypicalGroup.getTypifiedNames();
+               if (names.isEmpty()) {
+                       String message = "There is no name in a homotypical group. Can't create the specimen type";
+                       fireWarningEvent(message, parentEvent, 8);
+               } else {
+                       firstName = CdmBase.deproxy(names.iterator().next());
+               }
+
+               DerivedUnitFacade facade = DerivedUnitFacade.NewInstance(SpecimenOrObservationType.PreservedSpecimen);
+               state.setFirstSpecimenInFacade(true);
+               String text = "";
+               state.resetCollectionAndType();
+               state.setSpecimenType(true);
+               boolean isFullType = false;
+               // elements
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       if (isMyEndingElement(next, parentEvent)) {
+                               if (! isFullType){
+                                       makeSpecimenType(state, facade, text, state.getCollectionAndType(), firstName, parentEvent);
+                               }
+                               state.setSpecimenType(false);
+                               state.resetCollectionAndType();
+                               state.setFirstSpecimenInFacade(false);
+                               return;
+                       } else if (isStartingElement(next, FULL_TYPE)) {
+                               handleAmbigousManually(state, reader, next.asStartElement());
+                               isFullType = true;
+                       } else if (isStartingElement(next, TYPE_STATUS)) {
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, GATHERING)) {
+                               handleGathering(state, reader, next, facade);
+                       } else if (isStartingElement(next, ORIGINAL_DETERMINATION)) {
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, SPECIMEN_TYPE)) {
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, COLLECTION_AND_TYPE)) {
+                               String colAndType = getCData(state, reader, next, true);
+                               state.addCollectionAndType(colAndType);
+                       } else if (isStartingElement(next, CITATION)) {
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, NOTES)) {
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, ANNOTATION)) {
+                               handleNotYetImplementedElement(next);
+                       } else if (next.isCharacters()) {
+                               text += next.asCharacters().getData();
+                       } else {
+                               handleUnexpectedElement(next);
+                       }
+               }
+               throw new IllegalStateException("Specimen type has no closing tag");
+       }
+
+
+
+       private void makeSpecimenType(MarkupImportState state, DerivedUnitFacade facade, String text, String collectionAndType,
+                       INonViralName name, XMLEvent parentEvent) {
+               text = text.trim();
+               if (isBlank(text) || isPunctuation(text)){
+                       //do nothing
+               }else{
+                       String message = "Text '%s' not handled for <SpecimenType>";
+                       this.fireWarningEvent(String.format(message, text), parentEvent, 4);
+               }
+
+               if (makeFotgSpecimenType(state, collectionAndType, facade, name, parentEvent) || state.getConfig().isUseFotGSpecimenTypeCollectionAndTypeOnly()){
+                       return;
+               }else{
+                       // remove brackets
+                       if (collectionAndType.matches("^\\(.*\\)\\.?$")) {
+                               collectionAndType = collectionAndType.replaceAll("\\.$", "");
+                               collectionAndType = collectionAndType.substring(1, collectionAndType.length() - 1);
+                       }
+
+                       String[] splitsSemi = collectionAndType.split("[;]");
+            for (String splitSemi : splitsSemi) {
+                String[] splitKomma = splitSemi.split("[,]");
+                TypeInfo lastTypeInfo = null;
+                for (String str : splitKomma) {
+                    str = str.trim();
+                               boolean addToAllNamesInGroup = true;
+                               TypeInfo typeInfo = makeSpecimenTypeTypeInfo(state, str, lastTypeInfo, parentEvent);
+                               SpecimenTypeDesignationStatus typeStatus = typeInfo.status;
+                               Collection collection = this.getCollection(state, typeInfo.collectionString);
+
+                               // TODO improve cache strategy handling
+                               DerivedUnit typeSpecimen;
+                               if (state.isFirstSpecimenInFacade()){
+                                   state.setFirstSpecimenInFacade(false);
+                                   typeSpecimen = facade.innerDerivedUnit();
+                                   typeSpecimen.setCollection(collection);
+                               }else{
+                                   typeSpecimen = facade.addDuplicate(collection, null, null, null, null);
+                               }
+                               typeSpecimen.setCacheStrategy(new DerivedUnitFacadeCacheStrategy());
+                               name.addSpecimenTypeDesignation(typeSpecimen, typeStatus,
+                                       null, null, null, typeInfo.notDesignated, addToAllNamesInGroup);
+                               handleNotSeen(state, typeSpecimen, typeInfo);
+                               lastTypeInfo = typeInfo;
+                }
+                       }
+               }
+       }
+
+
+       /**
+     * @param state
+     * @param typeSpecimen
+     * @param typeInfo
+     */
+    private void handleNotSeen(MarkupImportState state, DerivedUnit typeSpecimen, TypeInfo typeInfo) {
+        if (typeInfo.notSeen){
+            String text = "n.v. for " + state.getConfig().getSourceReference().getAbbrevTitleCache();
+            typeSpecimen.addAnnotation(Annotation.NewInstance(text, AnnotationType.EDITORIAL(), getDefaultLanguage(state)));
+            if(state.getConfig().getSpecimenNotSeenMarkerTypeUuid() != null){
+                UUID uuidNotSeenMarker = state.getConfig().getSpecimenNotSeenMarkerTypeUuid();
+                String markerTypeNotSeenLabel = state.getConfig().getSpecimenNotSeenMarkerTypeLabel();
+                markerTypeNotSeenLabel = markerTypeNotSeenLabel == null ? "Not seen" : markerTypeNotSeenLabel;
+                MarkerType notSeenMarkerType = getMarkerType(state, uuidNotSeenMarker, markerTypeNotSeenLabel, markerTypeNotSeenLabel, null, null);
+                Marker marker = Marker.NewInstance(notSeenMarkerType, true);
+                typeSpecimen.addMarker(marker);
+            }
+        }
+    }
+
+
+    private Pattern fotgTypePattern = null;
+       /**
+        * Implemented for Flora of the Guyanas this may include duplicated code from similar places
+        * @param state
+        * @param collectionAndTypeOrig
+        * @param facade
+        * @param name
+        * @param parentEvent
+        * @return
+        */
+       private boolean makeFotgSpecimenType(MarkupImportState state, final String collectionAndTypeOrig, DerivedUnitFacade facade, INonViralName name, XMLEvent parentEvent) {
+               String collectionAndType = collectionAndTypeOrig;
+
+               String notDesignatedRE = "not\\s+designated";
+               String designatedByRE = "\\s*\\(((designated\\s+by\\s+|according\\s+to\\s+)[^\\)]+|here\\s+designated)\\)";
+               String typesRE = "(holotype|isotypes?|neotype|isoneotype|syntype|lectotype|isolectotypes?|typ\\.\\scons\\.,?)";
+               String collectionRE = "[A-Z\\-]{1,5}!?";
+               String collectionsRE = String.format("%s(,\\s+%s)*",collectionRE, collectionRE);
+               String addInfoRE = "(not\\s+seen|(presumed\\s+)?destroyed)";
+               String singleTypeTypeRE = String.format("(%s\\s)?%s(,\\s+%s)*", typesRE, collectionsRE, addInfoRE);
+               String allTypesRE = String.format("(\\(not\\s+seen\\)|\\(%s([,;]\\s%s)?\\))", singleTypeTypeRE, singleTypeTypeRE);
+               String designatedRE = String.format("%s(%s)?", allTypesRE, designatedByRE);
+               if (fotgTypePattern == null){
+
+                       String pattern = String.format("(%s|%s)", notDesignatedRE, designatedRE );
+                       fotgTypePattern = Pattern.compile(pattern);
+               }
+               Matcher matcher = fotgTypePattern.matcher(collectionAndType);
+
+               if (matcher.matches()){
+                   fireWarningEvent("Try to synchronize type handling (at least creation) with standard type handling. E.g. use TypeInfo and according algorithms", parentEvent, 2);
+                       if (collectionAndType.matches(notDesignatedRE)){
+                               SpecimenTypeDesignation desig = SpecimenTypeDesignation.NewInstance();
+                               desig.setNotDesignated(true);
+//                             name.addSpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalNameString, isNotDesignated, addToAllHomotypicNames)
+                               name.addTypeDesignation(desig, true);
+                       }else if(collectionAndType.matches(designatedRE)){
+                               String designatedBy = null;
+                               Matcher desigMatcher = Pattern.compile(designatedByRE).matcher(collectionAndType);
+                               boolean hasDesignatedBy = desigMatcher.find();
+                               if (hasDesignatedBy){
+                                       designatedBy = desigMatcher.group(0);
+                                       collectionAndType = collectionAndType.replace(designatedBy, "");
+                               }
+
+                               //remove brackets
+                               collectionAndType = collectionAndType.substring(1, collectionAndType.length() -1);
+                               List<String> singleTypes = new ArrayList<String>();
+                               Pattern singleTypePattern = Pattern.compile("^" + singleTypeTypeRE);
+                               matcher = singleTypePattern.matcher(collectionAndType);
+                               while (matcher.find()){
+                                       String match = matcher.group(0);
+                                       singleTypes.add(match);
+                                       collectionAndType = collectionAndType.substring(match.length());
+                                       if (!collectionAndType.isEmpty()){
+                                               collectionAndType = collectionAndType.substring(1).trim();
+                                       }else{
+                                               break;
+                                       }
+                                       matcher = singleTypePattern.matcher(collectionAndType);
+                               }
+
+                               List<SpecimenTypeDesignation> designations = new ArrayList<SpecimenTypeDesignation>();
+
+                               //single types
+                               for (String singleTypeOrig : singleTypes){
+                                       String singleType = singleTypeOrig;
+                                       //type
+                                       Pattern typePattern = Pattern.compile("^" + typesRE);
+                                       matcher = typePattern.matcher(singleType);
+                                       SpecimenTypeDesignationStatus typeStatus = null;
+                                       if (matcher.find()){
+                                               String typeStr = matcher.group(0);
+                                               singleType = singleType.substring(typeStr.length()).trim();
+                                               try {
+                                                       typeStatus = SpecimenTypeParser.parseSpecimenTypeStatus(typeStr);
+                                               } catch (UnknownCdmTypeException e) {
+                                                       fireWarningEvent("specimen type not recognized. Use generic type instead", parentEvent, 4);
+                                                       typeStatus = SpecimenTypeDesignationStatus.TYPE();
+                                                       //TODO use also type info from state
+                                               }
+                                       }else{
+                                               typeStatus = SpecimenTypeDesignationStatus.TYPE();
+                                               //TODO use also type info from state
+                                       }
+
+
+                                       //collection
+                                       Pattern collectionPattern = Pattern.compile("^" + collectionsRE);
+                                       matcher = collectionPattern.matcher(singleType);
+                                       String[] collectionStrings = new String[0];
+                                       if (matcher.find()){
+                                               String collectionStr = matcher.group(0);
+                                               singleType = singleType.substring(collectionStr.length());
+                                               collectionStr = collectionStr.replace("(", "").replace(")", "").replaceAll("\\s", "");
+                                               collectionStrings = collectionStr.split(",");
+                                       }
+
+                                       //addInfo
+                                       if (!singleType.isEmpty() && singleType.startsWith(", ")){
+                                               singleType = singleType.substring(2);
+                                       }
+
+                                       boolean notSeen = false;
+                                       if (singleType.equals("not seen")){
+                                               singleType = singleType.replace("not seen", "");
+                                               notSeen = true;
+                                       }
+                                       if (singleType.startsWith("not seen, ")){
+                                               singleType = singleType.replace("not seen, ", "");
+                                               notSeen = true;
+                                       }
+                                       boolean destroyed = false;
+                                       if (singleType.equals("destroyed")){
+                                               destroyed = true;
+                                               singleType = singleType.replace("destroyed", "");
+                                       }
+                                       boolean presumedDestroyed = false;
+                                       if (singleType.equals("presumed destroyed")){
+                                               presumedDestroyed = true;
+                                               singleType = singleType.replace("presumed destroyed", "");
+                                       }
+                                       boolean hasAddInfo = notSeen || destroyed || presumedDestroyed;
+
+
+                                       if (!singleType.isEmpty()){
+                                               String message = "SingleType was not fully read. Remaining: " + singleType + ". Original singleType was: " + singleTypeOrig;
+                                               fireWarningEvent(message, parentEvent, 6);
+                                               System.out.println(message);
+                                       }
+
+                                       if (collectionStrings.length > 0){
+                                               boolean isFirst = true;
+                                               for (String collStr : collectionStrings){
+                                                       Collection collection = getCollection(state, collStr);
+                                                       DerivedUnit unit = isFirst ? facade.innerDerivedUnit()
+                                                                       : facade.addDuplicate(collection, null, null, null, null);
+                                                       SpecimenTypeDesignation desig = SpecimenTypeDesignation.NewInstance();
+                                                       designations.add(desig);
+                                                       desig.setTypeSpecimen(unit);
+                                                       desig.setTypeStatus(typeStatus);
+                                                       handleSpecimenTypeAddInfo(state, notSeen, destroyed,
+                                                                       presumedDestroyed, desig);
+                                                       name.addTypeDesignation(desig, true);
+                                                       isFirst = false;
+                                               }
+                                       }else if (hasAddInfo){  //handle addInfo if no collection data available
+                                               SpecimenTypeDesignation desig = SpecimenTypeDesignation.NewInstance();
+                                               designations.add(desig);
+                                               desig.setTypeStatus(typeStatus);
+                                               handleSpecimenTypeAddInfo(state, notSeen, destroyed,
+                                                               presumedDestroyed, desig);
+                                               name.addTypeDesignation(desig, true);
+                                       }else{
+                                               fireWarningEvent("No type designation could be created as collection info was not recognized", parentEvent, 4);
+                                       }
+                               }
+
+                               if (designatedBy != null){
+                                       if (designations.size() != 1){
+                                               fireWarningEvent("Size of type designations is not exactly 1, which is expected for 'designated by'", parentEvent, 2);
+                                       }
+                                       designatedBy = designatedBy.trim();
+                                       if (designatedBy.startsWith("(") && designatedBy.endsWith(")") ){
+                                               designatedBy = designatedBy.substring(1, designatedBy.length() - 1);
+                                       }
+
+                                       for (SpecimenTypeDesignation desig : designations){
+                                               if (designatedBy.startsWith("designated by")){
+                                                       String titleCache = designatedBy.replace("designated by", "").trim();
+                                                       Reference reference = ReferenceFactory.newGeneric();
+                                                       reference.setTitleCache(titleCache, true);
+                                                       desig.setCitation(reference);
+                                                       //in future we could also try to parse it automatically
+                                                       fireWarningEvent("MANUALLY: Designated by should be parsed manually: " + titleCache, parentEvent, 1);
+                                               }else if (designatedBy.equals("designated here")){
+                                                       Reference ref = state.getConfig().getSourceReference();
+                                                       desig.setCitation(ref);
+                                                       fireWarningEvent("MANUALLY: Microcitation should be added to 'designated here", parentEvent, 1);
+                                               }else if (designatedBy.startsWith("according to")){
+                                                       String annotationStr = designatedBy.replace("according to", "").trim();
+                                                       Annotation annotation = Annotation.NewInstance(annotationStr, AnnotationType.EDITORIAL(), Language.ENGLISH());
+                                                       desig.addAnnotation(annotation);
+                                               }else{
+                                                       fireWarningEvent("Designated by does not match known pattern: " + designatedBy, parentEvent, 6);
+                                               }
+                                       }
+                               }
+                       }else{
+                               fireWarningEvent("CollectionAndType unexpectedly not matching: " + collectionAndTypeOrig, parentEvent, 6);
+                       }
+                       return true;
+               }else{
+                       if (state.getConfig().isUseFotGSpecimenTypeCollectionAndTypeOnly()){
+                               fireWarningEvent("NO MATCH: " + collectionAndTypeOrig, parentEvent, 4);
+                       }
+                       return false;
+               }
+
+//             // remove brackets
+//             if (collectionAndType.matches("^\\(.*\\)\\.?$")) {
+//                     collectionAndType = collectionAndType.replaceAll("\\.$", "");
+//                     collectionAndType = collectionAndType.substring(1, collectionAndType.length() - 1);
+//             }
+//
+//             String[] split = collectionAndType.split("[;,]");
+//             for (String str : split) {
+//                     str = str.trim();
+//                     boolean addToAllNamesInGroup = true;
+//                     TypeInfo typeInfo = makeSpecimenTypeTypeInfo(str, parentEvent);
+//                     SpecimenTypeDesignationStatus typeStatus = typeInfo.status;
+//                     Collection collection = this.getCollection(state, typeInfo.collectionString);
+//
+//                     // TODO improve cache strategy handling
+//                     DerivedUnit typeSpecimen = facade.addDuplicate(collection, null, null, null, null);
+//                     typeSpecimen.setCacheStrategy(new DerivedUnitFacadeCacheStrategy());
+//                     name.addSpecimenTypeDesignation(typeSpecimen, typeStatus, null, null, null, false, addToAllNamesInGroup);
+//             }
+       }
+
+
+       /**
+        * @param notSeen
+        * @param destroyed
+        * @param presumedDestroyed
+        * @param desig
+        */
+       private void handleSpecimenTypeAddInfo(MarkupImportState state, boolean notSeen, boolean destroyed,
+                       boolean presumedDestroyed, SpecimenTypeDesignation desig) {
+               DerivedUnit specimen = desig.getTypeSpecimen();
+               AnnotatableEntity annotEntity = specimen != null ? specimen : desig;
+
+           if (notSeen){
+                       UUID uuidNotSeenMarker = MarkupTransformer.uuidMarkerNotSeen;
+                       MarkerType notSeenMarkerType = getMarkerType(state, uuidNotSeenMarker, "Not seen", "Not seen", null, null);
+                       Marker marker = Marker.NewInstance(notSeenMarkerType, true);
+                       annotEntity.addMarker(marker);
+                       fireWarningEvent("not seen not yet implemented", "handleSpecimenTypeAddInfo", 4);
+               }
+               if (destroyed){
+                       UUID uuidDestroyedMarker = MarkupTransformer.uuidMarkerDestroyed;
+                       MarkerType destroyedMarkerType = getMarkerType(state, uuidDestroyedMarker, "Destroyed", "Destroyed", null, null);
+                       Marker marker = Marker.NewInstance(destroyedMarkerType, true);
+                       annotEntity.addMarker(marker);
+                       fireWarningEvent("'destroyed' not yet fully implemented", "handleSpecimenTypeAddInfo", 4);
+               }
+               if (presumedDestroyed){
+                       Annotation annotation = Annotation.NewInstance("presumably destroyed", Language.ENGLISH());
+                       annotation.setAnnotationType(AnnotationType.EDITORIAL());
+                       annotEntity.addAnnotation(annotation);
+               }
+       }
+
+
+       private TypeInfo makeSpecimenTypeTypeInfo(MarkupImportState state, String originalString, TypeInfo lastTypeInfo, XMLEvent event) {
+               TypeInfo result = new TypeInfo();
+               if ("not designated".equals(originalString)){
+                       result.notDesignated = true;
+                       return result;
+               }
+               List<String> knownCollections = state.getConfig().getKnownCollections();
+               for (String knownCollection:knownCollections){
+                   if (originalString.contains(knownCollection)){
+                       result.collectionString = knownCollection;
+                       originalString = originalString.replace(knownCollection, "").trim();
+                       break;
+                   }
+               }
+
+               String[] split = originalString.split("(?<!not)\\s+");
+
+               String unrecognizedTypeParts = null;
+               for (String str : split) {
+                   //holo/lecto/iso ...
+                       if (str.matches(SpecimenTypeParser.typeTypePattern)) {
+                               SpecimenTypeDesignationStatus status;
+                               try {
+                                       status = SpecimenTypeParser.parseSpecimenTypeStatus(str);
+                               } catch (UnknownCdmTypeException e) {
+                                       String message = "Specimen type status '%s' not recognized by parser";
+                                       fireWarningEvent(String.format(message, str), event, 4);
+                                       status = null;
+                               }
+                if (result.status != null){
+                    String message = "More than 1 status string found: " + originalString;
+                    fireWarningEvent(message, event, 4);
+                }
+                               result.status = status;
+                       } else if (str.matches(SpecimenTypeParser.collectionPattern)) {
+                               if (result.collectionString != null){
+                                   String message = "More than 1 collection string found: " + originalString;
+                    fireWarningEvent(message, event, 4);
+                               }
+                           result.collectionString = str;
+                       } else if (str.matches(SpecimenTypeParser.notSeen)) {
+                if (result.notSeen){
+                    String message = "More than 1 'not seen' string found: " + originalString;
+                    fireWarningEvent(message, event, 4);
+                }
+                result.notSeen = true;
+            } else {
+                unrecognizedTypeParts = CdmUtils.concat(" ", unrecognizedTypeParts, str);
+                       }
+                       if (result.status == null && lastTypeInfo != null && lastTypeInfo.status != null){
+                           result.status = lastTypeInfo.status;
+                       }
+               }
+               if(isNotBlank(unrecognizedTypeParts)){
+                   String message = "Type parts '%s' could not be recognized";
+            fireWarningEvent(String.format(message, unrecognizedTypeParts), event, 2);
+               }
+               return result;
+       }
+
+
+       private void handleGathering(MarkupImportState state, XMLEventReader readerOrig, XMLEvent parentEvent , DerivedUnitFacade facade) throws XMLStreamException {
+               checkNoAttributes(parentEvent);
+               boolean hasCollector = false;
+               boolean hasFieldNum = false;
+
+               LookAheadEventReader reader = new LookAheadEventReader(parentEvent.asStartElement(), readerOrig);
+
+               // elements
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       if (isMyEndingElement(next, parentEvent)) {
+                               if (! hasCollector){
+                                       if (state.getCurrentCollector() == null){
+                                               checkMandatoryElement(hasCollector,parentEvent.asStartElement(), COLLECTOR);
+                                       }else{
+                                               facade.setCollector(state.getCurrentCollector());
+                                       }
+                               }
+                               checkMandatoryElement(hasFieldNum,parentEvent.asStartElement(), FIELD_NUM);
+                               return;
+                       }else if (isStartingElement(next, COLLECTOR)) {
+                               hasCollector = true;
+                               String collectorStr = getCData(state, reader, next);
+                               TeamOrPersonBase<?> collector = createCollector(state, collectorStr);
+                               facade.setCollector(collector);
+                               state.setCurrentCollector(collector);
+                       } else if (isStartingElement(next, ALTERNATIVE_COLLECTOR)) {
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, FIELD_NUM)) {
+                               hasFieldNum = true;
+                               String fieldNumStr = getCData(state, reader, next);
+                               facade.setFieldNumber(fieldNumStr);
+                       } else if (isStartingElement(next, ALTERNATIVE_FIELD_NUM)) {
+                               handleAlternativeFieldNumber(state, reader, next, facade.innerFieldUnit());
+                       } else if (isStartingElement(next, COLLECTION_TYPE_STATUS)) {
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, COLLECTION_AND_TYPE)) {
+                               handleGatheringCollectionAndType(state, reader, next, facade);
+                       } else if (isStartingElement(next, ALTERNATIVE_COLLECTION_TYPE_STATUS)) {
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, SUB_GATHERING)) {
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, COLLECTION)) {
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, LOCALITY)) {
+                               handleLocality(state, reader, next, facade);
+                       } else if (isStartingElement(next, FULL_NAME)) {
+                               Rank defaultRank = Rank.SPECIES(); // can be any
+                               INonViralName nvn = createNameByCode(state, defaultRank);
+                               handleFullName(state, reader, nvn, next);
+                               TaxonName name = TaxonName.castAndDeproxy(nvn);
+                               DeterminationEvent.NewInstance(name, facade.innerDerivedUnit() != null ? facade.innerDerivedUnit() : facade.innerFieldUnit());
+                       } else if (isStartingElement(next, DATES)) {
+                               TimePeriod timePeriod = handleDates(state, reader, next);
+                               facade.setGatheringPeriod(timePeriod);
+                       } else if (isStartingElement(next, GATHERING_NOTES)) {
+                               handleAmbigousManually(state, reader, next.asStartElement());
+                       } else if (isStartingElement(next, NOTES)) {
+                               handleNotYetImplementedElement(next);
+                       }else if (next.isCharacters()) {
+                               String text = next.asCharacters().getData().trim();
+                               if (isPunctuation(text)){
+                                       //do nothing
+                               }else if (state.isSpecimenType() && charIsSimpleType(text) ){
+                                               //do nothing
+                               }else if ( (text.equals("=") || text.equals("(") ) && reader.nextIsStart(ALTERNATIVE_FIELD_NUM)){
+                                       //do nothing
+                               }else if ( (text.equals(").") || text.equals(")")) && reader.previousWasEnd(ALTERNATIVE_FIELD_NUM)){
+                                       //do nothing
+                               }else if ( charIsOpeningOrClosingBracket(text) ){
+                                       //for now we don't do anything, however in future brackets may have semantics
+                               }else{
+                                       //TODO
+                                       String message = "Unrecognized text: %s";
+                                       fireWarningEvent(String.format(message, text), next, 6);
+                               }
+                       } else {
+                               handleUnexpectedElement(next);
+                       }
+               }
+               throw new IllegalStateException("Collection has no closing tag.");
+
+       }
+
+
+       private final String fotgPattern = "^\\(([A-Z]{1,3})(?:,\\s?([A-Z]{1,3}))*\\)"; // eg. (US, B, CAN)
+       private void handleGatheringCollectionAndType(MarkupImportState state, XMLEventReader reader, XMLEvent parent, DerivedUnitFacade facade) throws XMLStreamException {
+               checkNoAttributes(parent);
+
+               XMLEvent next = readNoWhitespace(reader);
+
+               if (next.isCharacters()){
+                       String txt = next.asCharacters().getData().trim();
+                       if (state.isSpecimenType()){
+                               state.addCollectionAndType(txt);
+                       }else{
+
+                               Matcher fotgMatcher = Pattern.compile(fotgPattern).matcher(txt);
+
+                               if (fotgMatcher.matches()){
+                                       txt = txt.substring(1, txt.length() - 1);  //remove bracket
+                                       String[] splits = txt.split(",");
+                                       for (String split : splits ){
+                                               Collection collection = getCollection(state, split.trim());
+                                               if (facade.innerDerivedUnit() == null){
+                                                   String message = "Adding a duplicate to a non derived unit based facade is not possible. Please check why no derived unit exists yet in facade!";
+                                                   this.fireWarningEvent(message, next, -6);
+                                               }else{
+                                                   facade.addDuplicate(collection, null, null, null, null);
+                                               }
+                                       }
+                                       //FIXME 9
+                                       //create derived units and and add collections
+
+                               }else{
+                                       fireWarningEvent("Collection and type pattern for gathering not recognized: " + txt, next, 4);
+                               }
+                       }
+
+               }else{
+                       fireUnexpectedEvent(next, 0);
+               }
+
+               if (isMyEndingElement(next, parent)){
+                       return;  //in case we have a completely empty element
+               }
+               next = readNoWhitespace(reader);
+               if (isMyEndingElement(next, parent)){
+                       return;
+               }else{
+                       fireUnexpectedEvent(next, 0);
+                       return;
+               }
+       }
+
+
+       private Collection getCollection(MarkupImportState state, String code) {
+               Collection collection = state.getCollectionByCode(code);
+               if (collection == null){
+                       List<Collection> list = this.docImport.getCollectionService().searchByCode(code);
+                       if (list.size() == 1){
+                               collection = list.get(0);
+                       }else if (list.size() > 1){
+                               fireWarningEvent("More then one occurrence for collection " + code +  " in database. Collection not reused" , "", 1);
+                       }
+
+                       if (collection == null){
+                               collection = Collection.NewInstance();
+                               collection.setCode(code);
+                               this.docImport.getCollectionService().saveOrUpdate(collection);
+                       }
+                       state.putCollectionByCode(code, collection);
+               }
+               return collection;
+       }
+
+
+       private void handleAlternativeFieldNumber(MarkupImportState state, XMLEventReader reader, XMLEvent parent, FieldUnit fieldUnit) throws XMLStreamException {
+               Map<String, Attribute> attrs = getAttributes(parent);
+               Boolean doubtful = this.getAndRemoveBooleanAttributeValue(parent, attrs, "doubful", false);
+
+               //for now we do not handle annotation and typeNotes
+               String altFieldNum = getCData(state, reader, parent, false).trim();
+               DefinedTerm type = this.getIdentifierType(state, MarkupTransformer.uuidIdentTypeAlternativeFieldNumber, "Alternative field number", "Alternative field number", "alt. field no.", null);
+               fieldUnit.addIdentifier(altFieldNum, type);
+               if (doubtful){
+                       fireWarningEvent("Marking alternative field numbers as doubtful not yet possible, see #4673", parent,4);
+//                     Marker.NewInstance(identifier, "true", MarkerType.IS_DOUBTFUL());
+               }
+
+       }
+
+
+       private boolean charIsOpeningOrClosingBracket(String text) {
+               return text.equals("(") || text.equals(")");
+       }
+
+
+       private TimePeriod handleDates(MarkupImportState state, XMLEventReader reader, XMLEvent parent) throws XMLStreamException {
+               checkNoAttributes(parent);
+               TimePeriod result = TimePeriod.NewInstance();
+               String parseMessage = "%s can not be parsed: %s";
+               boolean hasFullDate = false;
+               boolean hasAtomised = false;
+               boolean hasUnparsedAtomised = false;
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       if (isMyEndingElement(next, parent)) {
+                               if (! isAlternative(hasFullDate, hasAtomised, hasUnparsedAtomised)){
+                                       String message = "Some problems exist when defining the date";
+                                       fireWarningEvent(message, parent, 4);
+                               }
+                               return result;
+                       } else if (isStartingElement(next, FULL_DATE)) {
+                               String fullDate = getCData(state, reader, next, true);
+                               if (fullDate.endsWith(".")){
+                                   fullDate = fullDate.substring(0, fullDate.length()-1);
+                               }
+                               result = TimePeriodParser.parseString(fullDate);
+                               if (result.getFreeText() != null){
+                                       fireWarningEvent(String.format(parseMessage, FULL_DATE, fullDate), parent, 1);
+                               }
+                               hasFullDate = true;
+                       } else if (isStartingElement(next, DAY)) {
+                               String day = getCData(state, reader, next, true).trim();
+                               day = normalizeDate(day);
+                               if (CdmUtils.isNumeric(day)){
+                                       result.setStartDay(Integer.valueOf(day));
+                                       hasAtomised = true;
+                               }else{
+                                       fireWarningEvent(String.format(parseMessage,"Day", day), parent, 2);
+                                       hasUnparsedAtomised = true;
+                               }
+                       } else if (isStartingElement(next, MONTH)) {
+                               String month = getCData(state, reader, next, true).trim();
+                               month = normalizeDate(month);
+                               if (CdmUtils.isNumeric(month)){
+                                       result.setStartMonth(Integer.valueOf(month));
+                                       hasAtomised = true;
+                               }else{
+                                       fireWarningEvent(String.format(parseMessage,"Month", month), parent, 2);
+                                       hasUnparsedAtomised = true;
+                               }
+                       } else if (isStartingElement(next, YEAR)) {
+                               String year = getCData(state, reader, next, true).trim();
+                               year = normalizeDate(year);
+                               if (CdmUtils.isNumeric(year)){
+                                       result.setStartYear(Integer.valueOf(year));
+                                       hasAtomised = true;
+                               }else{
+                                       fireWarningEvent(String.format(parseMessage,"Year", year), parent, 2);
+                                       hasUnparsedAtomised = true;
+                               }
+                       } else {
+                               handleUnexpectedElement(next);
+                       }
+               }
+               throw new IllegalStateException("Dates has no closing tag.");
+       }
+
+
+       private String normalizeDate(String partOfDate) {
+               if (isBlank(partOfDate)){
+                       return null;
+               }
+               partOfDate = partOfDate.trim();
+               while (partOfDate.startsWith("-")){
+                       partOfDate = partOfDate.substring(1);
+               }
+               return partOfDate;
+       }
+
+
+       private boolean isAlternative(boolean first, boolean second, boolean third) {
+               return ( (first ^ second) && !third)  ||
+                               (! first && ! second && third) ;
+       }
+
+
+       private void handleLocality(MarkupImportState state, XMLEventReader reader,XMLEvent parentEvent, DerivedUnitFacade facade)throws XMLStreamException {
+               String classValue = getClassOnlyAttribute(parentEvent);
+               boolean isLocality = false;
+               NamedAreaLevel areaLevel = null;
+               if ("locality".equalsIgnoreCase(classValue)||state.getConfig().isIgnoreLocalityClass()) {
+                       isLocality = true;
+               } else {
+                       areaLevel = makeNamedAreaLevel(state, classValue, parentEvent);
+               }
+
+               String text = "";
+               // elements
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       if (isMyEndingElement(next, parentEvent)) {
+                               if (StringUtils.isNotBlank(text)) {
+                                       text = normalize(text);text = removeTrailingPunctuation(text);
+                                       if (isLocality) {
+                                               facade.setLocality(text, getDefaultLanguage(state));
+                                       } else {
+                                               text = CdmUtils.removeTrailingDot(text);
+                                               NamedArea area = makeArea(state, text, areaLevel);
+                                               facade.addCollectingArea(area);
+                                       }
+                               }
+                               // TODO
+                               return;
+                       }else if (isStartingElement(next, ALTITUDE)) {
+                               handleNotYetImplementedElement(next);
+                               // homotypicalGroup = handleNom(state, reader, next, taxon,
+                               // homotypicalGroup);
+                       } else if (isStartingElement(next, COORDINATES)) {
+                               handleNotYetImplementedElement(next);
+                       } else if (isStartingElement(next, ANNOTATION)) {
+                               handleNotYetImplementedElement(next);
+                       } else if (next.isCharacters()) {
+                               text += next.asCharacters().getData();
+                       } else {
+                               handleUnexpectedElement(next);
+                       }
+               }
+               throw new IllegalStateException("<SpecimenType> has no closing tag");
+       }
+
+
+
+       /**
+     * @param text
+     * @return
+     */
+    private String removeTrailingPunctuation(String text) {
+        while (isPunctuation(text.substring(text.length()-1))){
+            text = text.substring(0, text.length()-1).trim();
+        }
+        return text;
+    }
+
+
+    private TeamOrPersonBase<?> createCollector(MarkupImportState state, String collectorStr) {
+               return createAuthor(state, collectorStr);
+       }
+
+
+       public List<DescriptionElementBase> handleMaterialsExamined(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, Feature feature, TaxonDescription defaultDescription) throws XMLStreamException {
+               List<DescriptionElementBase> result = new ArrayList<DescriptionElementBase>();
+               //reset current areas
+               state.removeCurrentAreas();
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       if (isMyEndingElement(next, parentEvent)) {
+                               if (result.isEmpty()){
+                                       fireWarningEvent("Materials examined created empty Individual Associations list", parentEvent, 4);
+                               }
+                               state.removeCurrentAreas();
+                               return result;
+                       } else if (isStartingElement(next, SUB_HEADING)) {
+//                             Map<String, Object> inlineMarkup = new HashMap<String, Object>();
+                               String text = getCData(state, reader, next, true);
+                               if (isFeatureHeading(state, next, text)){
+                                       feature = makeHeadingFeature(state, next, text, feature);
+                               }else{
+                                       String message = "Unhandled subheading: %s";
+                                       fireWarningEvent(String.format(message,  text), next, 4);
+                               }
+//                             for (String key : inlineMarkup.keySet()){
+//                                     handleInlineMarkup(state, key, inlineMarkup);
+//                             }
+
+                       } else if (isStartingElement(next, BR) || isEndingElement(next, BR)) {
+                               //do nothing
+                       } else if (isStartingElement(next, GATHERING)) {
+                               DerivedUnitFacade facade = DerivedUnitFacade.NewInstance(SpecimenOrObservationType.DerivedUnit);
+                               addCurrentAreas(state, next, facade);
+                               handleGathering(state, reader, next, facade);
+                               SpecimenOrObservationBase<?> specimen;
+                               if (facade.innerDerivedUnit() != null){
+                                       specimen = facade.innerDerivedUnit();
+                               }else{
+                                       specimen = facade.innerFieldUnit();
+                               }
+                               IndividualsAssociation individualsAssociation = IndividualsAssociation.NewInstance();
+                               individualsAssociation.addPrimaryTaxonomicSource(state.getConfig().getSourceReference());
+                               individualsAssociation.setAssociatedSpecimenOrObservation(specimen);
+                               result.add(individualsAssociation);
+                       } else if (isStartingElement(next, GATHERING_GROUP)) {
+                               List<DescriptionElementBase> list = getGatheringGroupDescription(state, reader, next);
+                               result.addAll(list);
+                       }else if (next.isCharacters()) {
+                               String text = next.asCharacters().getData().trim();
+                               if (isPunctuation(text)){
+                                       //do nothing
+                               }else{
+                                       String message = "Unrecognized text: %s";
+                                       fireWarningEvent(String.format(message, text), next, 6);
+                               }
+                       } else {
+                               handleUnexpectedElement(next);
+                       }
+               }
+               throw new IllegalStateException("<String> has no closing tag");
+
+       }
+
+
+
+       private List<DescriptionElementBase> getGatheringGroupDescription(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
+               Map<String, Attribute> attributes = getAttributes(parentEvent);
+               String geoScope = getAndRemoveAttributeValue(attributes, "geoscope");
+               Boolean doubtful = getAndRemoveBooleanAttributeValue(parentEvent, attributes, DOUBTFUL, null);
+               checkNoAttributes(attributes, parentEvent);
+
+               List<DescriptionElementBase> result = new ArrayList<DescriptionElementBase>();
+
+
+               TaxonDescription td = null;
+
+               if (isNotBlank(geoScope)){
+                       NamedArea area = Country.getCountryByLabel(geoScope);
+                       if (area == null){
+                               try {
+                                       area = state.getTransformer().getNamedAreaByKey(geoScope);
+                               } catch (Exception e) {
+                                       fireWarningEvent("getNamedArea not supported", parentEvent, 16);
+                               }
+                       }
+                       if (area == null){
+                               fireWarningEvent("Area for geoscope not found: " +  geoScope +"; add specimen group to ordinary description", parentEvent, 4);
+                       }else{
+                               state.addCurrentArea(area);
+                               Set<TaxonDescription> descs = state.getCurrentTaxon().getDescriptions();
+                               for (TaxonDescription desc : descs){
+                                       Set<NamedArea> scopes = desc.getGeoScopes();
+                                       if (scopes.size() == 1 && scopes.iterator().next().equals(area)){
+                                               td = desc;
+                                               break;
+                                       }
+                               }
+                               if (td == null){
+                                       TaxonDescription desc = TaxonDescription.NewInstance(state.getCurrentTaxon());
+                                       desc.addPrimaryTaxonomicSource(state.getConfig().getSourceReference(), null);
+                                       desc.addGeoScope(area);
+                                       if (doubtful != null){
+                                               desc.addMarker(Marker.NewInstance(MarkerType.IS_DOUBTFUL(), doubtful));
+                                       }
+                                       td = desc;
+                               }
+                       }
+               }
+
+               while (reader.hasNext()) {
+                       XMLEvent next = readNoWhitespace(reader);
+                       if (isMyEndingElement(next, parentEvent)) {
+                               if (result.isEmpty()){
+                                       fireWarningEvent("Gathering group created empty Individual Associations list", parentEvent, 4);
+                               }
+                               state.removeCurrentAreas();
+                               return result;
+                       } else if (isStartingElement(next, GATHERING)) {
+                               DerivedUnitFacade facade = DerivedUnitFacade.NewInstance(SpecimenOrObservationType.DerivedUnit);
+                               addCurrentAreas(state, next, facade);
+                               handleGathering(state, reader, next, facade);
+                               SpecimenOrObservationBase<?> specimen;
+                               if (facade.innerDerivedUnit() != null){
+                                       specimen = facade.innerDerivedUnit();
+                               }else{
+                                       specimen = facade.innerFieldUnit();
+                               }
+                               IndividualsAssociation individualsAssociation = IndividualsAssociation.NewInstance();
+                               individualsAssociation.addPrimaryTaxonomicSource(state.getConfig().getSourceReference());
+                               individualsAssociation.setAssociatedSpecimenOrObservation(specimen);
+                               result.add(individualsAssociation);
+
+                       }else if (next.isCharacters()) {
+                               String text = next.asCharacters().getData().trim();
+                               if (isPunctuation(text)){
+                                       //do nothing
+                               }else{
+                                       //TODO
+                                       String message = "Unrecognized text: %s";
+                                       fireWarningEvent(String.format(message, text), next, 6);
+                               }
+                       } else {
+                               handleUnexpectedElement(next);
+                       }
+               }
+               throw new IllegalStateException("<Gathering group> has no closing tag");
+
+       }
+
+       private void addCurrentAreas(MarkupImportState state, XMLEvent event, DerivedUnitFacade facade) {
+               for (NamedArea area : state.getCurrentAreas()){
+                       if (area == null){
+                               continue;
+                       }else if (area.isInstanceOf(Country.class)){
+                               facade.setCountry(area);
+                       }else{
+                               String message = "Current area %s is not country. This is not expected for currently known data.";
+                               fireWarningEvent(String.format(message, area.getTitleCache()), event, 2);
+                               facade.addCollectingArea(area);
+                       }
+               }
+
+       }
+
+
+//     private void handleInlineMarkup(MarkupImportState state, String key, Map<String, Object> inlineMarkup) {
+//             Object obj = inlineMarkup.get(key);
+//             if (key.equals(LOCALITY)){
+//                     if (obj instanceof NamedArea){
+//                             NamedArea area = (NamedArea)obj;
+//                             state.addCurrentArea(area);
+//                     }
+//             }
+//
+//     }
+
+
+       /**
+        * Changes the feature if the (sub)-heading implies this. Also recognizes hidden country information
+        * @param state
+        * @param parent
+        * @param text
+        * @param feature
+        * @return
+        */
+       private Feature makeHeadingFeature(MarkupImportState state, XMLEvent parent, String originalText, Feature feature) {
+               //expand, provide by config or service
+               String materialRegEx = "Mat[\u00E9\u00C9]riel";
+               String examinedRegEx = "[\u00E9\u00C9]tudi[\u00E9\u00C9]";
+               String countryRegEx = "(gabonais)";
+               String postfixCountryRegEx = "\\s+(pour le Gabon)";
+
+               String materialExaminedRegEx = "(?i)" + materialRegEx + "\\s+(" + countryRegEx +"\\s+)?" + examinedRegEx + "(" +postfixCountryRegEx + ")?:?";
+
+               String text = originalText;
+
+               if (isBlank(text)){
+                       return feature;
+               }else{
+                       if (text.matches(materialExaminedRegEx)){
+                               //gabon specific
+                               if (text.contains("gabonais ")){
+                                       text = text.replace("gabonais ", "");
+                                       state.addCurrentArea(Country.GABONGABONESEREPUBLIC());
+                               }
+                               if (text.contains(" pour le Gabon")){
+                                       text = text.replace(" pour le Gabon", "");
+                                       state.addCurrentArea(Country.GABONGABONESEREPUBLIC());
+                               }
+
+                               //update feature
+                               feature = Feature.MATERIALS_EXAMINED();
+                               state.putFeatureToGeneralSorterList(feature);
+                               return feature;
+                       }else{
+                               String message = "Heading/Subheading not recognized: %s";
+                               fireWarningEvent(String.format(message, originalText), parent, 4);
+                               return feature;
+                       }
+               }
+       }
+
+
+       /**
+        * True if heading or subheading represents feature information
+        * @param state
+        * @param parent
+        * @param text
+        * @return
+        */
+       private boolean isFeatureHeading(MarkupImportState state, XMLEvent parent, String text) {
+               return makeHeadingFeature(state, parent, text, null) != null;
+       }
+
+
+       public String handleInLineGathering(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
+               DerivedUnitFacade facade = DerivedUnitFacade.NewInstance(SpecimenOrObservationType.FieldUnit);
+               handleGathering(state, reader, parentEvent, facade);
+               SpecimenOrObservationBase<?> specimen  = facade.innerFieldUnit();
+               if (specimen == null){
+                       specimen = facade.innerDerivedUnit();
+                       String message = "Inline gaterhing has no field unit";
+                       fireWarningEvent(message, parentEvent, 2);
+               }
+
+               String result = "<cdm:specimen uuid='%s'>%s</specimen>";
+               if (specimen != null){
+                       result = String.format(result, specimen.getUuid(), specimen.getTitleCache());
+               }else{
+                       String message = "Inline gathering has no specimen";
+                       fireWarningEvent(message, parentEvent, 4);
+               }
+               save(specimen, state);
+               return result;
+       }
+
+
+
+
+
+}