commit 7ad9934d4818aea51d41a8453420e785232f3053
Author: Andreas Müller <a.mueller@bgbm.org>
Date:   Thu Aug 4 08:51:30 2011 +0000

    update to markup import

diff --git a/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/MarkupDocumentImport.java b/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/MarkupDocumentImport.java
index 8712793a8f..2dd335ee89 100644
--- a/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/MarkupDocumentImport.java
+++ b/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/MarkupDocumentImport.java
@@ -45,6 +45,7 @@ import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeCacheStrategy;
 import eu.etaxonomy.cdm.common.CdmUtils;
 import eu.etaxonomy.cdm.io.common.ICdmIO;
 import eu.etaxonomy.cdm.io.common.mapping.UndefinedTransformerMethodException;
+import eu.etaxonomy.cdm.io.markup.UnmatchedLeads.UnmatchedLeadsKey;
 import eu.etaxonomy.cdm.model.agent.AgentBase;
 import eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor;
 import eu.etaxonomy.cdm.model.agent.Team;
@@ -62,6 +63,9 @@ import eu.etaxonomy.cdm.model.common.TimePeriod;
 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
 import eu.etaxonomy.cdm.model.description.Distribution;
 import eu.etaxonomy.cdm.model.description.Feature;
+import eu.etaxonomy.cdm.model.description.KeyStatement;
+import eu.etaxonomy.cdm.model.description.PolytomousKey;
+import eu.etaxonomy.cdm.model.description.PolytomousKeyNode;
 import eu.etaxonomy.cdm.model.description.PresenceAbsenceTermBase;
 import eu.etaxonomy.cdm.model.description.TaxonDescription;
 import eu.etaxonomy.cdm.model.description.TextData;
@@ -104,6 +108,24 @@ import eu.etaxonomy.cdm.strategy.parser.SpecimenTypeParser.TypeInfo;
 @Component
 public class MarkupDocumentImport extends MarkupImportBase implements
 		ICdmIO<MarkupImportState> {
+	private static final String TO_COUPLET = "toCouplet";
+
+	private static final String TO_TAXON = "toTaxon";
+
+	private static final String TO_KEY = "toKey";
+
+	private static final String TEXT = "text";
+
+	private static final String QUESTION = "question";
+
+	private static final String COUPLET = "couplet";
+
+	private static final String KEYNOTES = "keynotes";
+
+	private static final String KEY_TITLE = "keyTitle";
+
+	private static final String IS_SPOTCHARACTERS = "isSpotcharacters";
+
 	private static final String REF_NUM = "refNum";
 
 	private static final String SERIALS_ABBREVIATIONS = "serialsAbbreviations";
@@ -529,9 +551,6 @@ public class MarkupDocumentImport extends MarkupImportBase implements
 					} else if (isEndingElement(next, TEXT_SECTION)) {
 						// NOT YET IMPLEMENTED
 						popUnimplemented(next.asEndElement());
-					} else if (isEndingElement(next, KEY)) {
-						// NOT YET IMPLEMENTED
-						popUnimplemented(next.asEndElement());
 					} else if (isEndingElement(next, REFERENCES)) {
 						// NOT YET IMPLEMENTED
 						popUnimplemented(next.asEndElement());
@@ -546,29 +565,11 @@ public class MarkupDocumentImport extends MarkupImportBase implements
 					taxonTitle = handleTaxonTitle(state, reader, next);
 					hasTitle = true;
 				} else if (isStartingElement(next, WRITER)) {
-
-					WriterDataHolder writer = handleWriter(state, reader, next);
-					taxon.addExtension(writer.extension);
-					// TODO what if taxonTitle comes later
-					if (StringUtils.isNotBlank(taxonTitle)
-							&& writer.extension != null) {
-						Reference<?> sec = ReferenceFactory.newBookSection();
-						sec.setTitle(taxonTitle);
-						TeamOrPersonBase<?> author = createAuthor(writer.writer);
-						sec.setAuthorTeam(author);
-						sec.setInReference(state.getConfig()
-								.getSourceReference());
-						taxon.setSec(sec);
-						registerFootnotes(state, sec, writer.footnotes);
-					} else {
-						String message = "No taxontitle exists for writer";
-						fireWarningEvent(message, next, 6);
-					}
-
+					makeKeyWriter(state, reader, taxon, taxonTitle, next);
 				} else if (isStartingElement(next, TEXT_SECTION)) {
 					handleNotYetImplementedElement(next);
 				} else if (isStartingElement(next, KEY)) {
-					handleNotYetImplementedElement(next);
+					handleKey(state, reader, next);
 				} else if (isStartingElement(next, NOMENCLATURE)) {
 					handleNomenclature(state, reader, next);
 					hasNomenclature = true;
@@ -617,6 +618,219 @@ public class MarkupDocumentImport extends MarkupImportBase implements
 		throw new IllegalStateException("Taxon has no closing tag");
 	}
 
+	private void handleKey(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
+		// attributes
+		Map<String, Attribute> attributes = getAttributes(parentEvent);
+		String isSpotcharacters = getAndRemoveAttributeValue(attributes, IS_SPOTCHARACTERS);
+		if (isNotBlank(isSpotcharacters) ) {
+			//TODO isSpotcharacters
+			String message = "isSpotcharacters not yet implemented for <key>";
+			fireWarningEvent(message, parentEvent, 4);
+		}
+		
+		PolytomousKey key = PolytomousKey.NewInstance();
+		key.addTaxonomicScope(state.getCurrentTaxon());
+		state.setCurrentKey(key);
+		
+		boolean isFirstCouplet = true;
+		while (reader.hasNext()) {
+			XMLEvent next = readNoWhitespace(reader);
+			if (isMyEndingElement(next, parentEvent)) {
+				save(key, state);
+				state.setCurrentKey(null);
+				return;
+			} else if (isEndingElement(next, KEYNOTES)){
+				popUnimplemented(next.asEndElement());
+			} else if (next.isStartElement()) {
+				if (isStartingElement(next, KEY_TITLE)) {
+					String keyTitle = getCData(state, reader, next);
+					if (isNotBlank(keyTitle)){
+						key.setTitleCache(keyTitle, true);
+					}
+				} else if (isStartingElement(next, KEYNOTES)) {
+					//TODO
+					handleNotYetImplementedElement(next);
+				} else if (isStartingElement(next, COUPLET)) {
+					PolytomousKeyNode node = null;
+					if (isFirstCouplet){
+						node = key.getRoot();
+						isFirstCouplet = false;
+					}
+					handleCouplet(state, reader, next, node);
+				} else {
+					handleUnexpectedStartElement(next.asStartElement());
+				}
+			} else {
+				handleUnexpectedElement(next);
+			}
+		}
+		throw new IllegalStateException("<key> has no closing tag");
+		
+
+	}
+
+	private void handleCouplet(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, PolytomousKeyNode parentNode) throws XMLStreamException {
+		// attributes
+		Map<String, Attribute> attributes = getAttributes(parentEvent);
+		//TODO
+		String num = getAndRemoveRequiredAttributeValue(parentEvent, attributes, NUM);
+		
+		
+		while (reader.hasNext()) {
+			XMLEvent next = readNoWhitespace(reader);
+			if (isMyEndingElement(next, parentEvent)) {
+				return;
+			} else if (isEndingElement(next, KEYNOTES)){
+				popUnimplemented(next.asEndElement());
+			} else if (isStartingElement(next, QUESTION)) {
+				handleQuestion(state, reader, next, parentNode, num);
+			} else if (isStartingElement(next, KEYNOTES)) {
+				//TODO
+				handleNotYetImplementedElement(next);
+			} else {
+				handleUnexpectedElement(next);
+			}
+		}
+		throw new IllegalStateException("<couplet> has no closing tag");
+	}
+
+	private void handleQuestion(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, PolytomousKeyNode parentNode, String coupletNum) throws XMLStreamException {
+		// attributes
+		Map<String, Attribute> attributes = getAttributes(parentEvent);
+		//needed only for data lineage
+		String questionNum = getAndRemoveRequiredAttributeValue(parentEvent, attributes, NUM);
+		
+		PolytomousKeyNode node = PolytomousKeyNode.NewInstance();
+		if (parentNode != null){
+			parentNode.addChild(node);
+		}else if (isNotBlank(coupletNum)){
+			
+		}else{
+			String message = "Parent num could not be determined. Please check if num (%s) is correct";
+			message = String.format(message, coupletNum);
+			fireWarningEvent(message, parentEvent, 6);
+		}
+		
+		while (reader.hasNext()) {
+			XMLEvent next = readNoWhitespace(reader);
+			if (isMyEndingElement(next, parentEvent)) {
+				return;
+			} else if (isEndingElement(next, TO_KEY)){
+				popUnimplemented(next.asEndElement());
+			} else if (isEndingElement(next, KEYNOTES)){
+				popUnimplemented(next.asEndElement());
+			
+			} else if (isStartingElement(next, TEXT)) {
+				String text = getCData(state, reader, next);
+				KeyStatement statement = KeyStatement.NewInstance(text);
+				node.setStatement(statement);
+			} else if (isStartingElement(next, COUPLET)) {
+				//TODO test
+				handleCouplet(state, reader, next, node);
+			} else if (isStartingElement(next, TO_COUPLET)) {
+				handleToCouplet(state, reader, next, node);
+			} else if (isStartingElement(next, TO_TAXON)) {
+				handleToTaxon(state, reader, next, node);
+			} else if (isStartingElement(next, TO_KEY)) {
+				//TODO
+				handleNotYetImplementedElement(next);
+			} else if (isStartingElement(next, KEYNOTES)) {
+				//TODO
+				handleNotYetImplementedElement(next);
+			} else {
+				handleUnexpectedElement(next);
+			}
+		}
+		throw new IllegalStateException("<question> has no closing tag");
+		
+	}
+
+	private void handleToCouplet(MarkupImportState state, XMLEventReader reader, XMLEvent next, PolytomousKeyNode node) throws XMLStreamException {
+		String num = getOnlyAttribute(next, NUM, true);
+		String cData = getCData(state, reader, next, false);
+		if (isNotBlank(cData)){
+			String message = "CData ('%s') not handled";
+			message = String.format(message, cData);
+			fireWarningEvent(message, next, 4);
+		}
+		UnmatchedLeadsKey unmatched = UnmatchedLeadsKey.NewInstance(state.getCurrentKey(), num);
+		state.getUnmatchedLeads().addKey(unmatched, node);
+	}
+
+	private void handleToTaxon(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, PolytomousKeyNode node) throws XMLStreamException {
+		Map<String, Attribute> attributes = getAttributes(parentEvent);
+		String num = getAndRemoveAttributeValue(attributes, NUM);
+		String taxonStr = getCData(state, reader, parentEvent, false);
+		//TODO ?
+		taxonStr = makeTaxonKey(taxonStr, state.getCurrentTaxon());
+		UnmatchedLeadsKey unmatched = UnmatchedLeadsKey.NewInstance(num, taxonStr);
+		state.getUnmatchedLeads().addKey(unmatched, node);
+		return;
+	}
+	
+	private String makeTaxonKey(String strGoto, Taxon taxon) {
+		String result = "";
+		if (strGoto == null){
+			return "";
+		}
+		String strGenusName = CdmBase.deproxy(taxon.getName(), NonViralName.class).getGenusOrUninomial();
+		strGoto = strGoto.replaceAll("\\([^\\(\\)]*\\)", "");  //replace all brackets
+		strGoto = strGoto.replaceAll("\\s+", " "); //replace multiple whitespaces by exactly one whitespace
+		
+		strGoto = strGoto.trim();  
+		String[] split = strGoto.split("\\s");
+		for (int i = 0; i<split.length; i++){
+			String single = split[i];
+			if (isGenusAbbrev(single, strGenusName)){
+				split[i] = strGenusName;
+			}
+//			if (isInfraSpecificMarker(single)){
+//				String strSpeciesName = CdmBase.deproxy(taxon.getName(), NonViralName.class).getSpecificEpithet();
+//				split[i] = strGenusName + " " + strSpeciesName + " ";
+//			}
+			result = (result + " " + split[i]).trim();
+		}
+		return result;
+	}
+	
+	private boolean isGenusAbbrev(String single, String strGenusName) {
+		if (! single.matches("[A-Z]\\.?")) {
+			return false;
+		}else if (single.length() == 0 || strGenusName == null || strGenusName.length() == 0){
+			return false; 
+		}else{
+			return single.charAt(0) == strGenusName.charAt(0);
+		}
+	}
+
+	/**
+	 * @param state
+	 * @param reader
+	 * @param taxon
+	 * @param taxonTitle
+	 * @param next
+	 * @throws XMLStreamException
+	 */
+	private void makeKeyWriter(MarkupImportState state, XMLEventReader reader, Taxon taxon, String taxonTitle, XMLEvent next) throws XMLStreamException {
+		WriterDataHolder writer = handleWriter(state, reader, next);
+		taxon.addExtension(writer.extension);
+		// TODO what if taxonTitle comes later
+		if (StringUtils.isNotBlank(taxonTitle)
+				&& writer.extension != null) {
+			Reference<?> sec = ReferenceFactory.newBookSection();
+			sec.setTitle(taxonTitle);
+			TeamOrPersonBase<?> author = createAuthor(writer.writer);
+			sec.setAuthorTeam(author);
+			sec.setInReference(state.getConfig()
+					.getSourceReference());
+			taxon.setSec(sec);
+			registerFootnotes(state, sec, writer.footnotes);
+		} else {
+			String message = "No taxontitle exists for writer";
+			fireWarningEvent(message, next, 6);
+		}
+	}
+
 	private String handleNotes(MarkupImportState state, XMLEventReader reader,
 			XMLEvent parentEvent) throws XMLStreamException {
 		checkNoAttributes(parentEvent);
@@ -629,8 +843,6 @@ public class MarkupDocumentImport extends MarkupImportBase implements
 			} else if (next.isEndElement()) {
 				if (isEndingElement(next, HEADING)) {
 					popUnimplemented(next.asEndElement());
-				} else if (isEndingElement(next, SUB_HEADING)) {
-					popUnimplemented(next.asEndElement());
 				} else if (isEndingElement(next, WRITER)) {
 					popUnimplemented(next.asEndElement());
 				} else if (isEndingElement(next, NUM)) {
@@ -642,23 +854,17 @@ public class MarkupDocumentImport extends MarkupImportBase implements
 				if (isStartingElement(next, HEADING)) {
 					handleNotYetImplementedElement(next);
 				} else if (isStartingElement(next, SUB_HEADING)) {
-					handleNotYetImplementedElement(next);
+					String subheading = getCData(state, reader, next).trim();
+					if (! isNoteHeading(subheading)) {
+						fireNotYetImplementedElement(next.getLocation(), next.asStartElement().getName(), 0);
+					}
 				} else if (isStartingElement(next, WRITER)) {
 					handleNotYetImplementedElement(next);
 				} else if (isStartingElement(next, NUM)) {
 					handleNotYetImplementedElement(next);
 				} else if (isStartingElement(next, STRING)) {
 					// TODO why multiple strings in schema?
-
-					Map<String, String> stringMap = handleString(state, reader,
-							next);
-					if (stringMap.size() != 1
-							|| stringMap.keySet().iterator().next() != null) {
-						String message = "Subheadings not yet supported in <notes> or no text available";
-						fireWarningEvent(message, next, 4);
-					} else {
-						text += stringMap.values().iterator().next();
-					}
+					text = makeNotesString(state, reader, text, next);
 				} else {
 					handleUnexpectedStartElement(next.asStartElement());
 				}
@@ -669,6 +875,42 @@ public class MarkupDocumentImport extends MarkupImportBase implements
 		throw new IllegalStateException("<Notes> has no closing tag");
 	}
 
+	/**
+	 * @param state
+	 * @param reader
+	 * @param text
+	 * @param next
+	 * @return
+	 * @throws XMLStreamException
+	 */
+	private String makeNotesString(MarkupImportState state,	XMLEventReader reader, String text, XMLEvent next)
+			throws XMLStreamException {
+		Map<String, String> stringMap = handleString(state, reader,	next);
+		if (stringMap.size() == 0){
+			String message = "No text available in <notes>";
+			fireWarningEvent(message, next, 4);
+		}else if (stringMap.size() > 1){
+			String message = "Subheadings not yet supported in <notes>";
+			fireWarningEvent(message, next, 4);
+		}else{
+			String firstSubheading = stringMap.keySet().iterator().next();
+			if ( firstSubheading != null && ! isNoteHeading (firstSubheading) )  {
+				String message = "Subheadings not yet supported in <notes>";
+				fireWarningEvent(message, next, 4);
+			}
+		}
+		for (String subheading : stringMap.keySet()){
+			text += subheading;
+			text += stringMap.get(subheading);
+		}
+		return text;
+	}
+
+	private boolean isNoteHeading(String heading) {
+		String excludePattern = "(i?)(Notes?):?";
+		return heading.matches(excludePattern);
+	}
+
 	/**
 	 * @param state
 	 * @param attributes
@@ -712,10 +954,10 @@ public class MarkupDocumentImport extends MarkupImportBase implements
 	 * @param rank
 	 * @return
 	 */
-	private NonViralName createNameByCode(MarkupImportState state, Rank rank) {
-		NonViralName name;
+	private NonViralName<?> createNameByCode(MarkupImportState state, Rank rank) {
+		NonViralName<?> name;
 		NomenclaturalCode nc = makeNomenclaturalCode(state);
-		name = (NonViralName) nc.getNewTaxonNameInstance(rank);
+		name = (NonViralName<?>) nc.getNewTaxonNameInstance(rank);
 		return name;
 	}
 
@@ -973,63 +1215,8 @@ public class MarkupDocumentImport extends MarkupImportBase implements
 		while (reader.hasNext()) {
 			XMLEvent next = readNoWhitespace(reader);
 			if (isMyEndingElement(next, parentEvent)) {
-				Media media = null;
-				try {
-					URL url = new URL(urlString);
-					if ("lineart".equals(type)) {
-						media = Figure.NewInstance(url.toURI(), null, null,
-								null);
-					} else if (type == null || "photo".equals(type)
-							|| "signature".equals(type)
-							|| "others".equals(type)) {
-						media = Media
-								.NewInstance(url.toURI(), null, null, null);
-					} else {
-						String message = "Unknown figure type '%s'";
-						message = String.format(message, type);
-						// TODO location
-						fireWarningEvent(message, "-", 2);
-						media = Media
-								.NewInstance(url.toURI(), null, null, null);
-					}
-					// title
-					if (StringUtils.isNotBlank(titleString)) {
-						media.putTitle(Language.DEFAULT(), titleString);
-					}
-					// legend
-					if (StringUtils.isNotBlank(legendString)) {
-						media.addDescription(legendString, Language.DEFAULT());
-					}
-					if (StringUtils.isNotBlank(numString)) {
-						// TODO use concrete source (e.g. DAPHNIPHYLLACEAE in FM
-						// vol.13)
-						Reference citation = state.getConfig()
-								.getSourceReference();
-						media.addSource(numString, "num", citation, null);
-						// TODO name used in source if available
-					}
-					// TODO which citation
-					if (StringUtils.isNotBlank(id)) {
-						media.addSource(id, null, state.getConfig()
-								.getSourceReference(), null);
-					} else {
-						String message = "Figure id should never be empty or null";
-						fireWarningEvent(message, next, 6);
-					}
-					// text
-					// do nothing
-
-				} catch (MalformedURLException e) {
-					String message = "Media uri has incorrect syntax: %s";
-					message = String.format(message, urlString);
-					fireWarningEvent(message, next, 4);
-				} catch (URISyntaxException e) {
-					String message = "Media uri has incorrect syntax: %s";
-					message = String.format(message, urlString);
-					fireWarningEvent(message, next, 4);
-				}
-
-				registerGivenFigure(state, id, media);
+				makeFigure(state, id, type, urlString, legendString,
+						titleString, numString, next);
 				return;
 			} else if (isStartingElement(next, FIGURE_LEGEND)) {
 				// TODO same as figurestring ?
@@ -1050,6 +1237,75 @@ public class MarkupDocumentImport extends MarkupImportBase implements
 		throw new IllegalStateException("<figure> has no end tag");
 	}
 
+	/**
+	 * @param state
+	 * @param id
+	 * @param type
+	 * @param urlString
+	 * @param legendString
+	 * @param titleString
+	 * @param numString
+	 * @param next
+	 */
+	private void makeFigure(MarkupImportState state, String id, String type,
+			String urlString, String legendString, String titleString,
+			String numString, XMLEvent next) {
+		Media media = null;
+		try {
+			URL url = new URL(urlString);
+			if ("lineart".equals(type)) {
+				media = Figure.NewInstance(url.toURI(), null, null,
+						null);
+			} else if (type == null || "photo".equals(type)
+					|| "signature".equals(type)
+					|| "others".equals(type)) {
+				media = Media.NewInstance(url.toURI(), null, null, null);
+			} else {
+				String message = "Unknown figure type '%s'";
+				message = String.format(message, type);
+				// TODO location
+				fireWarningEvent(message, "-", 2);
+				media = Media.NewInstance(url.toURI(), null, null, null);
+			}
+			// title
+			if (StringUtils.isNotBlank(titleString)) {
+				media.putTitle(Language.DEFAULT(), titleString);
+			}
+			// legend
+			if (StringUtils.isNotBlank(legendString)) {
+				media.addDescription(legendString, Language.DEFAULT());
+			}
+			if (StringUtils.isNotBlank(numString)) {
+				// TODO use concrete source (e.g. DAPHNIPHYLLACEAE in FM
+				// vol.13)
+				Reference citation = state.getConfig().getSourceReference();
+				media.addSource(numString, "num", citation, null);
+				// TODO name used in source if available
+			}
+			// TODO which citation
+			if (StringUtils.isNotBlank(id)) {
+				media.addSource(id, null, state.getConfig()
+						.getSourceReference(), null);
+			} else {
+				String message = "Figure id should never be empty or null";
+				fireWarningEvent(message, next, 6);
+			}
+			// text
+			// do nothing
+
+		} catch (MalformedURLException e) {
+			String message = "Media uri has incorrect syntax: %s";
+			message = String.format(message, urlString);
+			fireWarningEvent(message, next, 4);
+		} catch (URISyntaxException e) {
+			String message = "Media uri has incorrect syntax: %s";
+			message = String.format(message, urlString);
+			fireWarningEvent(message, next, 4);
+		}
+
+		registerGivenFigure(state, id, media);
+	}
+
 	private FigureDataHolder handleFigureRef(MarkupImportState state,
 			XMLEventReader reader, XMLEvent parentEvent)
 			throws XMLStreamException {
@@ -1175,8 +1431,7 @@ public class MarkupDocumentImport extends MarkupImportBase implements
 				} else if (isStartingElement(next, COLLECTION)) {
 					handleNotYetImplementedElement(next);
 				} else if (isStartingElement(next, REFERENCES)) {
-					text += " " + handleInLineReferences(state, reader, next)
-							+ " ";
+					text += " " + handleInLineReferences(state, reader, next)+ " ";
 				} else if (isStartingElement(next, BR)) {
 					text += "<br/>";
 					isTextMode = false;
@@ -1712,6 +1967,10 @@ public class MarkupDocumentImport extends MarkupImportBase implements
 		return createAuthor(collectorStr);
 	}
 
+	private String getCData(MarkupImportState state, XMLEventReader reader, XMLEvent next) throws XMLStreamException {
+		return getCData(state, reader, next, true);
+	}
+		
 	/**
 	 * Reads character data. Any element other than character data or the ending
 	 * tag will fire an unexpected element event.
@@ -1722,9 +1981,10 @@ public class MarkupDocumentImport extends MarkupImportBase implements
 	 * @return
 	 * @throws XMLStreamException
 	 */
-	private String getCData(MarkupImportState state, XMLEventReader reader,
-			XMLEvent next) throws XMLStreamException {
-		checkNoAttributes(next);
+	private String getCData(MarkupImportState state, XMLEventReader reader, XMLEvent next,boolean checkAttributes) throws XMLStreamException {
+		if (checkAttributes){
+			checkNoAttributes(next);
+		}
 
 		String text = "";
 		while (reader.hasNext()) {
@@ -2370,28 +2630,21 @@ public class MarkupDocumentImport extends MarkupImportBase implements
 					taxonDescription.addElement(textData);
 				} else if (isStartingElement(next, STRING)) {
 					lastDescriptionElement = makeFeatureString(state, reader,
-							feature, taxonDescription, lastDescriptionElement,
-							next);
+							feature, taxonDescription, lastDescriptionElement,next);
 				} else if (isStartingElement(next, FIGURE_REF)) {
-					lastDescriptionElement = makeFeatureFigureRef(state,
-							reader, taxonDescription, isDescription,
+					lastDescriptionElement = makeFeatureFigureRef(state, reader, taxonDescription, isDescription,
 							lastDescriptionElement, next);
 				} else if (isStartingElement(next, REFERENCES)) {
 					// TODO details/microcitation ??
 
-					List<Reference<?>> refs = handleReferences(state, reader,
-							next);
+					List<Reference<?>> refs = handleReferences(state, reader, next);
 					if (!refs.isEmpty()) {
 						// TODO
-						Reference<?> descriptionRef = state.getConfig()
-								.getSourceReference();
-						TaxonDescription description = getTaxonDescription(
-								taxon, descriptionRef, false, true);
-						TextData featurePlaceholder = getFeaturePlaceholder(
-								state, description, feature, true);
+						Reference<?> descriptionRef = state.getConfig().getSourceReference();
+						TaxonDescription description = getTaxonDescription(taxon, descriptionRef, false, true);
+						TextData featurePlaceholder = getFeaturePlaceholder(state, description, feature, true);
 						for (Reference<?> citation : refs) {
-							featurePlaceholder.addSource(null, null, citation,
-									null);
+							featurePlaceholder.addSource(null, null, citation, null);
 						}
 					} else {
 						String message = "No reference found in references";
@@ -2557,8 +2810,7 @@ public class MarkupDocumentImport extends MarkupImportBase implements
 		}
 	}
 
-	private List<Reference<?>> handleReferences(MarkupImportState state,
-			XMLEventReader reader, XMLEvent parentEvent)
+	private List<Reference<?>> handleReferences(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent)
 			throws XMLStreamException {
 		// attributes
 		Map<String, Attribute> attributes = getAttributes(parentEvent);
@@ -2604,10 +2856,9 @@ public class MarkupDocumentImport extends MarkupImportBase implements
 					handleNotYetImplementedElement(next);
 				} else if (isStartingElement(next, SUB_HEADING)) {
 					String subheading = getCData(state, reader, next).trim();
-					String excludePattern = "(References?|Literature):?";
+					String excludePattern = "(i?)(References?|Literature):?";
 					if (!subheading.matches(excludePattern)) {
-						fireNotYetImplementedElement(next.getLocation(), next
-								.asStartElement().getName(), 0);
+						fireNotYetImplementedElement(next.getLocation(), next.asStartElement().getName(), 0);
 					}
 				} else if (isStartingElement(next, WRITER)) {
 					handleNotYetImplementedElement(next);
diff --git a/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/MarkupImportBase.java b/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/MarkupImportBase.java
index fdb47a9911..12b862262b 100644
--- a/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/MarkupImportBase.java
+++ b/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/MarkupImportBase.java
@@ -33,6 +33,7 @@ import eu.etaxonomy.cdm.io.common.events.IIoEvent;
 import eu.etaxonomy.cdm.io.common.events.IoProblemEvent;
 import eu.etaxonomy.cdm.model.common.CdmBase;
 import eu.etaxonomy.cdm.model.common.DefinedTermBase;
+import eu.etaxonomy.cdm.model.description.PolytomousKey;
 import eu.etaxonomy.cdm.model.taxon.Classification;
 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
 
@@ -64,6 +65,9 @@ public abstract class MarkupImportBase  extends XmlImportBase<MarkupImportConfig
 		}else if (cdmBase.isInstanceOf(Classification.class)){
 			Classification classification = CdmBase.deproxy(cdmBase, Classification.class);
 			getClassificationService().saveOrUpdate(classification);
+		}else if (cdmBase.isInstanceOf(PolytomousKey.class)){
+			PolytomousKey key = CdmBase.deproxy(cdmBase, PolytomousKey.class);
+			getPolytomousKeyService().saveOrUpdate(key);
 		}else if (cdmBase.isInstanceOf(DefinedTermBase.class)){
 			DefinedTermBase term = CdmBase.deproxy(cdmBase, DefinedTermBase.class);
 			getTermService().saveOrUpdate(term);
@@ -539,8 +543,17 @@ public abstract class MarkupImportBase  extends XmlImportBase<MarkupImportConfig
 	 * @return
 	 */
 	protected String getClassOnlyAttribute(XMLEvent parentEvent, boolean required) {
+		return getOnlyAttribute(parentEvent, CLASS, required);
+	}
+	
+	/**
+	 * Returns the value for the only attribute for a given event and checks that it is the only attribute.
+	 * @param parentEvent
+	 * @return
+	 */
+	protected String getOnlyAttribute(XMLEvent parentEvent, String attrName, boolean required) {
 		Map<String, Attribute> attributes = getAttributes(parentEvent);
-		String classValue =getAndRemoveAttributeValue(parentEvent, attributes, CLASS, required, 1);
+		String classValue =getAndRemoveAttributeValue(parentEvent, attributes, attrName, required, 1);
 		checkNoAttributes(attributes, parentEvent);
 		return classValue;
 	}
diff --git a/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/MarkupImportState.java b/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/MarkupImportState.java
index 8d4e4c7f8c..dc625c9f4b 100644
--- a/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/MarkupImportState.java
+++ b/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/MarkupImportState.java
@@ -22,6 +22,7 @@ import eu.etaxonomy.cdm.io.common.mapping.IInputTransformer;
 import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
 import eu.etaxonomy.cdm.model.common.Language;
 import eu.etaxonomy.cdm.model.description.FeatureNode;
+import eu.etaxonomy.cdm.model.description.PolytomousKey;
 import eu.etaxonomy.cdm.model.description.PolytomousKeyNode;
 import eu.etaxonomy.cdm.model.media.Media;
 import eu.etaxonomy.cdm.model.taxon.Taxon;
@@ -42,6 +43,9 @@ public class MarkupImportState extends XmlImportState<MarkupImportConfigurator,
 	
 	private Set<PolytomousKeyNode> polytomousKeyNodesToSave = new HashSet<PolytomousKeyNode>();
 	
+	private PolytomousKey currentKey;
+
+
 	private Language defaultLanguage;
 	
 	private Taxon currentTaxon;
@@ -191,4 +195,18 @@ public class MarkupImportState extends XmlImportState<MarkupImportConfigurator,
 		figureRefRegister.put(figureId, demands);
 	}
 
+	/**
+	 * @param key
+	 */
+	public void setCurrentKey(PolytomousKey key) {
+		this.currentKey = key;
+	}
+	
+	/**
+	 * @return the currentKey
+	 */
+	public PolytomousKey getCurrentKey() {
+		return currentKey;
+	}
+
 }
\ No newline at end of file
diff --git a/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/UnmatchedLeads.java b/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/UnmatchedLeads.java
index 19364f5444..e43452ab16 100644
--- a/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/UnmatchedLeads.java
+++ b/cdmlib-io/src/main/java/eu/etaxonomy/cdm/io/markup/UnmatchedLeads.java
@@ -16,11 +16,8 @@ import java.util.Set;
 import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
 
-import eu.etaxonomy.cdm.api.service.IFeatureTreeService;
 import eu.etaxonomy.cdm.api.service.IPolytomousKeyNodeService;
-import eu.etaxonomy.cdm.api.service.IPolytomousKeyService;
 import eu.etaxonomy.cdm.common.CdmUtils;
-import eu.etaxonomy.cdm.model.description.FeatureNode;
 import eu.etaxonomy.cdm.model.description.PolytomousKey;
 import eu.etaxonomy.cdm.model.description.PolytomousKeyNode;
 
@@ -124,13 +121,20 @@ public class UnmatchedLeads {
 //************************* METHODS ********************************/
 	
 	
+	/**
+	 * Adds a polytomous key node to the 
+	 * @param key
+	 * @param node
+	 */
 	public void addKey(UnmatchedLeadsKey key, PolytomousKeyNode node){
 		Set<PolytomousKeyNode> nodes = map.get(key);
 		if (nodes == null){
 			nodes = new HashSet<PolytomousKeyNode>();
 			map.put(key, nodes);
 		}else{
-			logger.info("A Feature node for this key does already exist: " + key.toString());
+			String message = "A feature node for this key does already exist: %s";
+			message = String.format(message, key.toString());
+			logger.info(message);
 		}
 		nodes.add(node);
 	}
@@ -157,7 +161,8 @@ public class UnmatchedLeads {
 	}
 	
 	/**
-	 * SaveOrUpdates all polytomousKeyNodes in the unmatchedLeadsKey map
+	 * SaveOrUpdates all polytomousKeyNodes in the unmatchedLeadsKey map.
+	 * Used to move nodes from one transaction to another.
 	 * @param service
 	 */
 	public void saveToSession(IPolytomousKeyNodeService service){
