Project

General

Profile

Download (74.6 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2007 EDIT
3
* European Distributed Institute of Taxonomy 
4
* http://www.e-taxonomy.eu
5
* 
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9

    
10
package eu.etaxonomy.cdm.io.markup;
11

    
12
import java.net.MalformedURLException;
13
import java.util.ArrayList;
14
import java.util.Arrays;
15
import java.util.Collection;
16
import java.util.HashMap;
17
import java.util.HashSet;
18
import java.util.Iterator;
19
import java.util.List;
20
import java.util.Map;
21
import java.util.Set;
22
import java.util.Stack;
23
import java.util.UUID;
24
import java.util.regex.Matcher;
25
import java.util.regex.Pattern;
26

    
27
import javax.xml.namespace.QName;
28
import javax.xml.stream.Location;
29
import javax.xml.stream.XMLEventReader;
30
import javax.xml.stream.XMLStreamConstants;
31
import javax.xml.stream.XMLStreamException;
32
import javax.xml.stream.events.Attribute;
33
import javax.xml.stream.events.Characters;
34
import javax.xml.stream.events.EndElement;
35
import javax.xml.stream.events.StartElement;
36
import javax.xml.stream.events.XMLEvent;
37

    
38
import org.apache.commons.lang.StringUtils;
39
import org.apache.commons.lang.WordUtils;
40
import org.apache.log4j.Logger;
41

    
42
import eu.etaxonomy.cdm.api.service.IClassificationService;
43
import eu.etaxonomy.cdm.api.service.ITermService;
44
import eu.etaxonomy.cdm.common.CdmUtils;
45
import eu.etaxonomy.cdm.ext.geo.GeoServiceArea;
46
import eu.etaxonomy.cdm.ext.geo.IEditGeoService;
47
import eu.etaxonomy.cdm.io.common.CdmImportBase;
48
import eu.etaxonomy.cdm.io.common.CdmImportBase.TermMatchMode;
49
import eu.etaxonomy.cdm.io.common.events.IIoEvent;
50
import eu.etaxonomy.cdm.io.common.events.IoProblemEvent;
51
import eu.etaxonomy.cdm.io.common.mapping.UndefinedTransformerMethodException;
52
import eu.etaxonomy.cdm.model.agent.Team;
53
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
54
import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
55
import eu.etaxonomy.cdm.model.common.Annotation;
56
import eu.etaxonomy.cdm.model.common.AnnotationType;
57
import eu.etaxonomy.cdm.model.common.CdmBase;
58
import eu.etaxonomy.cdm.model.common.DefinedTermBase;
59
import eu.etaxonomy.cdm.model.common.Extension;
60
import eu.etaxonomy.cdm.model.common.ExtensionType;
61
import eu.etaxonomy.cdm.model.common.Language;
62
import eu.etaxonomy.cdm.model.common.OriginalSourceType;
63
import eu.etaxonomy.cdm.model.common.TermVocabulary;
64
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
65
import eu.etaxonomy.cdm.model.description.Distribution;
66
import eu.etaxonomy.cdm.model.description.Feature;
67
import eu.etaxonomy.cdm.model.description.PolytomousKey;
68
import eu.etaxonomy.cdm.model.description.PresenceAbsenceTermBase;
69
import eu.etaxonomy.cdm.model.description.PresenceTerm;
70
import eu.etaxonomy.cdm.model.description.TaxonDescription;
71
import eu.etaxonomy.cdm.model.description.TextData;
72
import eu.etaxonomy.cdm.model.location.NamedArea;
73
import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
74
import eu.etaxonomy.cdm.model.location.NamedAreaType;
75
import eu.etaxonomy.cdm.model.media.IdentifiableMediaEntity;
76
import eu.etaxonomy.cdm.model.media.Media;
77
import eu.etaxonomy.cdm.model.name.NomenclaturalCode;
78
import eu.etaxonomy.cdm.model.name.NonViralName;
79
import eu.etaxonomy.cdm.model.name.Rank;
80
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
81
import eu.etaxonomy.cdm.model.reference.Reference;
82
import eu.etaxonomy.cdm.model.taxon.Classification;
83
import eu.etaxonomy.cdm.model.taxon.Taxon;
84
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
85
import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
86

    
87
/**
88
 * @author a.mueller
89
 * @created 04.08.2008
90
 */
91
public abstract class MarkupImportBase  {
92
	@SuppressWarnings("unused")
93
	private static final Logger logger = Logger.getLogger(MarkupImportBase.class);
94

    
95
	//Base
96
	protected static final String ALTITUDE = "altitude";
97
	protected static final String ANNOTATION = "annotation";
98
	protected static final String BOLD = "bold";
99
	protected static final String BR = "br";
100
	protected static final String CITATION = "citation";
101
	protected static final String CLASS = "class";
102
	protected static final String COORDINATES = "coordinates";
103
	protected static final String DATES = "dates";
104
	protected static final String GATHERING = "gathering";
105
	protected static final String GENUS_ABBREVIATION = "genus abbreviation";
106
	protected static final String FOOTNOTE = "footnote";
107
	protected static final String FOOTNOTE_REF = "footnoteRef";
108
	protected static final String FULL_NAME = "fullName";
109
	protected static final String ITALICS = "italics";
110
	protected static final String NUM = "num";
111
	protected static final String NOTES = "notes";
112
	protected static final String PUBLICATION = "publication";
113
	protected static final String SPECIMEN_TYPE = "specimenType";
114
	protected static final String STATUS = "status";
115
	protected static final String SUB_HEADING = "subHeading";
116
	protected static final String TYPE = "type";
117
	protected static final String TYPE_STATUS = "typeStatus";
118

    
119
	protected static final boolean CREATE_NEW = true;
120
	protected static final boolean NO_IMAGE_GALLERY = false;
121
	protected static final boolean IMAGE_GALLERY = true;
122

    
123
	protected static final String ADDENDA = "addenda";
124
	protected static final String BIBLIOGRAPHY = "bibliography";
125
	protected static final String BIOGRAPHIES = "biographies";
126
	protected static final String CHAR = "char";
127
	protected static final String DEDICATION = "dedication";
128
	protected static final String DEFAULT_MEDIA_URL = "defaultMediaUrl";
129
	protected static final String DISTRIBUTION_LIST = "distributionList";
130
	protected static final String DISTRIBUTION_LOCALITY = "distributionLocality";
131
	protected static final String FEATURE = "feature";
132
	protected static final String FIGURE = "figure";
133
	protected static final String FIGURE_LEGEND = "figureLegend";
134
	protected static final String FIGURE_PART = "figurePart";
135
	protected static final String FIGURE_REF = "figureRef";
136
	protected static final String FIGURE_TITLE = "figureTitle";
137
	protected static final String FOOTNOTE_STRING = "footnoteString";
138
	protected static final String FREQUENCY = "frequency";
139
	protected static final String HEADING = "heading";
140
	protected static final String HABITAT = "habitat";
141
	protected static final String HABITAT_LIST = "habitatList";
142
	protected static final String IS_FREETEXT = "isFreetext";
143
	protected static final String ID = "id";
144
	protected static final String KEY = "key";
145
	protected static final String LIFE_CYCLE_PERIODS = "lifeCyclePeriods";
146
	protected static final String META_DATA = "metaData";
147
	protected static final String MODS = "mods";
148

    
149
	protected static final String NOMENCLATURE = "nomenclature";
150
	protected static final String QUOTE = "quote";
151
	protected static final String RANK = "rank";
152
	protected static final String REF = "ref";
153
	protected static final String REF_NUM = "refNum";
154
	protected static final String REFERENCE = "reference";
155
	protected static final String REFERENCES = "references";
156
	protected static final String SUB_CHAR = "subChar";
157
	protected static final String TAXON = "taxon";
158
	protected static final String TAXONTITLE = "taxontitle";
159
	protected static final String TAXONTYPE = "taxontype";
160
	protected static final String TEXT_SECTION = "textSection";
161
	protected static final String TREATMENT = "treatment";
162
	protected static final String SERIALS_ABBREVIATIONS = "serialsAbbreviations";
163
	protected static final String STRING = "string";
164
	protected static final String URL = "url";
165
	protected static final String VERNACULAR_NAMES = "vernacularNames";
166
	protected static final String WRITER = "writer";
167
	
168
	
169
	//Nomenclature
170
	protected static final String ACCEPTED = "accepted";
171
	protected static final String ACCEPTED_NAME = "acceptedName";
172
	protected static final String ALTERNATEPUBTITLE = "alternatepubtitle";
173
	protected static final String AUTHOR = "author";
174
	protected static final String DETAILS = "details";
175
	protected static final String EDITION = "edition";
176
	protected static final String EDITORS = "editors";
177
	protected static final String HOMONYM = "homonym";
178
	protected static final String HOMOTYPES = "homotypes";
179
	protected static final String INFRANK = "infrank";
180
	protected static final String INFRAUT = "infraut";
181
	protected static final String INFRPARAUT = "infrparaut";
182
	protected static final String ISSUE = "issue";
183
	protected static final String NAME = "name";
184
	protected static final String NAME_TYPE = "nameType";
185
	protected static final String NOM = "nom";
186
	protected static final String PAGES = "pages";
187
	protected static final String PARAUT = "paraut";
188
	protected static final String PUBFULLNAME = "pubfullname";
189
	protected static final String PUBNAME = "pubname";
190
	protected static final String PUBTITLE = "pubtitle";
191
	protected static final String PUBTYPE = "pubtype";
192
	protected static final String REF_PART = "refPart";
193
	protected static final String SYNONYM = "synonym";
194
	protected static final String USAGE = "usage";
195
	protected static final String VOLUME = "volume";
196
	protected static final String YEAR = "year";
197

    
198
	
199
	//keys
200
	protected static final String COUPLET = "couplet";
201
	protected static final String IS_SPOTCHARACTERS = "isSpotcharacters";
202
	protected static final String ONLY_NUMBERED_TAXA_EXIST = "onlyNumberedTaxaExist";
203
	protected static final String EXISTS = "exists";
204
	protected static final String KEYNOTES = "keynotes";
205
	protected static final String KEY_TITLE = "keyTitle";
206
	protected static final String QUESTION = "question";
207
	protected static final String TEXT = "text";
208
	protected static final String TO_COUPLET = "toCouplet";
209
	protected static final String TO_KEY = "toKey";
210
	protected static final String TO_TAXON = "toTaxon";
211

    
212

    
213
	protected MarkupDocumentImport docImport;
214

    
215
	private IEditGeoService editGeoService;
216
	
217
	public MarkupImportBase(MarkupDocumentImport docImport) {
218
		super();
219
		this.docImport = docImport;
220
		this.editGeoService = docImport.getEditGeoService();
221
	}
222

    
223
	private Stack<QName> unhandledElements = new Stack<QName>();
224
	private Stack<QName> handledElements = new Stack<QName>();
225

    
226

    
227
	protected <T extends CdmBase> void  save(Collection<T> collection, MarkupImportState state) {
228
		if (state.isCheck() || collection.isEmpty()){
229
			return;
230
		}
231
		T example = collection.iterator().next();
232
		if (example.isInstanceOf(TaxonBase.class)){
233
			Collection<TaxonBase> typedCollection = (Collection<TaxonBase>)collection;
234
			docImport.getTaxonService().saveOrUpdate(typedCollection);
235
		}else if (example.isInstanceOf(Classification.class)){
236
			Collection<Classification> typedCollection = (Collection<Classification>)collection;
237
			docImport.getClassificationService().saveOrUpdate(typedCollection);
238
		}else if (example.isInstanceOf(PolytomousKey.class)){
239
			Collection<PolytomousKey> typedCollection = (Collection<PolytomousKey>)collection;
240
			docImport.getPolytomousKeyService().saveOrUpdate(typedCollection);
241
		}else if (example.isInstanceOf(DefinedTermBase.class)){
242
			Collection<DefinedTermBase> typedCollection = (Collection<DefinedTermBase>)collection;
243
			getTermService().saveOrUpdate(typedCollection);
244
		}
245
		
246
	}
247
	
248

    
249
	//TODO move to service layer for all IdentifiableEntities	
250
	protected void save(CdmBase cdmBase, MarkupImportState state) {
251
		if (state.isCheck()){
252
			return;
253
		}
254
		cdmBase = CdmBase.deproxy(cdmBase, CdmBase.class);
255
		if (cdmBase == null){
256
			String message = "Tried to save a null object.";
257
			fireWarningEvent(message, "--location ?? --", 6,1);
258
		} else if (cdmBase.isInstanceOf(TaxonBase.class)){
259
			docImport.getTaxonService().saveOrUpdate((TaxonBase<?>)cdmBase);
260
		}else if (cdmBase.isInstanceOf(Classification.class)){
261
			docImport.getClassificationService().saveOrUpdate((Classification)cdmBase);
262
		}else if (cdmBase.isInstanceOf(PolytomousKey.class)){
263
			docImport.getPolytomousKeyService().saveOrUpdate((PolytomousKey)cdmBase);
264
		}else if (cdmBase.isInstanceOf(DefinedTermBase.class)){
265
			docImport.getTermService().saveOrUpdate((DefinedTermBase<?>)cdmBase);
266
		}else if (cdmBase.isInstanceOf(Media.class)){
267
			docImport.getMediaService().saveOrUpdate((Media)cdmBase);
268
		}else if (cdmBase.isInstanceOf(SpecimenOrObservationBase.class)){
269
			docImport.getOccurrenceService().saveOrUpdate((SpecimenOrObservationBase<?>)cdmBase);
270
		}else if (cdmBase.isInstanceOf(DescriptionElementBase.class)){
271
			docImport.getDescriptionService().saveDescriptionElement((DescriptionElementBase)cdmBase);
272
		}else if (cdmBase.isInstanceOf(Reference.class)){
273
			docImport.getReferenceService().saveOrUpdate((Reference<?>)cdmBase);
274
		}else{
275
			String message = "Unknown cdmBase type to save: " + cdmBase.getClass();
276
			fireWarningEvent(message, "Unknown location", 8);
277
		}
278
		//logger.warn("Saved " +  cdmBase);
279
	}
280
	
281
	
282
	protected ITermService getTermService() {
283
		return docImport.getTermService();
284
	}
285
	
286
	protected IClassificationService getClassificationService() {
287
		return docImport.getClassificationService();
288
	}
289

    
290
//*********************** Attribute methods *************************************/
291

    
292
	/**
293
	 * Returns a map for all attributes of an start element
294
	 * @param event
295
	 * @return
296
	 */
297
	protected Map<String, Attribute> getAttributes(XMLEvent event) {
298
		Map<String, Attribute> result = new HashMap<String, Attribute>();
299
		if (!event.isStartElement()){
300
			fireWarningEvent("Event is not an startElement. Can't check attributes", makeLocationStr(event.getLocation()), 1, 1);
301
			return result;
302
		}
303
		StartElement element = event.asStartElement(); 
304
		Iterator<Attribute> attributes = element.getAttributes();
305
		while (attributes.hasNext()){
306
			Attribute attribute = attributes.next();
307
			//TODO namespaces
308
			result.put(attribute.getName().getLocalPart(), attribute);
309
		}
310
		return result;
311
	}
312

    
313
	/**
314
	 * Throws an unexpected attributes event if the event has any attributes.
315
	 * @param event
316
	 */
317
	protected void checkNoAttributes(Map<String, Attribute> attributes, XMLEvent event) {
318
		String[] exceptions = new String[]{};
319
		handleUnexpectedAttributes(event.getLocation(), attributes, 1, exceptions);
320
	}
321
	
322
	
323
	
324
	/**
325
	 * Throws an unexpected attributes event if the event has any attributes.
326
	 * @param event
327
	 */
328
	protected void checkNoAttributes(XMLEvent event) {
329
		String[] exceptions = new String[]{};
330
		checkNoAttributes(event, 1, exceptions); 
331
	}
332

    
333
	/**
334
	 * Throws an unexpected attributes event if the event has any attributes except those mentioned in "exceptions".
335
	 * @param event
336
	 * @param exceptions
337
	 */
338
	protected void checkNoAttributes(XMLEvent event, int stackDepth, String... exceptions) {
339
		if (! event.isStartElement()){
340
			fireWarningEvent("Event is not an startElement. Can't check attributes", makeLocationStr(event.getLocation()), 1, 1);
341
			return;
342
		}
343
		StartElement startElement = event.asStartElement();
344
		Map<String, Attribute> attributes = getAttributes(startElement);
345
		handleUnexpectedAttributes(startElement.getLocation(), attributes, stackDepth+1, exceptions);
346
	}
347
	
348

    
349
	/**
350
	 * Checks if the given attribute exists and has the given value.
351
	 * If yes, true is returned and the attribute is removed from the attributes map.
352
	 * Otherwise false is returned.
353
	 * @param attributes
354
	 * @param attrName
355
	 * @param value
356
	 * @return <code>true</code> if attribute has given value, <code>false</code> otherwise
357
	 */
358
	protected boolean checkAndRemoveAttributeValue( Map<String, Attribute> attributes, String attrName, String value) {
359
		Attribute attr = attributes.get(attrName);
360
		if (attr == null ||value == null ){
361
			return false;
362
		}else{
363
			if (value.equals(attr.getValue())){
364
				attributes.remove(attrName);
365
				return true;
366
			}else{
367
				return false;
368
			}
369
		}
370
	}
371

    
372

    
373
	/**
374
	 * Returns the value of a given attribute name and removes the attribute from the attributes map. 
375
	 * @param attributes
376
	 * @param attrName
377
	 * @return
378
	 */
379
	protected String getAndRemoveAttributeValue(Map<String, Attribute> attributes, String attrName) {
380
		return getAndRemoveAttributeValue(null, attributes, attrName, false, 1);
381
	}
382
	
383
	/**
384
	 * Returns the value of a boolean attribute with the given name and removes the attribute from the attributes map. 
385
	 * Returns <code>defaultValue</code> if the attribute does not exist. ALso returns <code>defaultValue</code> and throws a warning if the
386
	 * attribute has no boolean value (true, false).
387
	 * @param 
388
	 * @param attributes the 
389
	 * @param attrName the name of the attribute
390
	 * @param defaultValue the default value to return if attribute does not exist or can not be defined
391
	 * @return
392
	 */
393
	protected Boolean getAndRemoveBooleanAttributeValue(XMLEvent event, Map<String, Attribute> attributes, String attrName, Boolean defaultValue) {
394
		String value = getAndRemoveAttributeValue(null, attributes, attrName, false, 1);
395
		Boolean result = defaultValue;
396
		if (value != null){
397
			if (value.equalsIgnoreCase("true")){
398
				result = true;
399
			}else if (value.equalsIgnoreCase("false")){
400
				result = false;
401
			}else{
402
				String message = "Boolean attribute has no boolean value ('true', 'false') but '%s'";
403
				fireWarningEvent(String.format(message, value), makeLocationStr(event.getLocation()), 6, 1);
404
			}
405
		}
406
		return result;
407
	}
408

    
409
	
410
	/**
411
	 * Returns the value of a given attribute name and returns the attribute from the attributes map.
412
	 * Fires a mandatory field is missing event if the attribute does not exist.
413
	 * @param xmlEvent
414
	 * @param attributes
415
	 * @param attrName
416
	 * @return
417
	 */
418
	protected String getAndRemoveRequiredAttributeValue(XMLEvent xmlEvent, Map<String, Attribute> attributes, String attrName) {
419
		return getAndRemoveAttributeValue(xmlEvent, attributes, attrName, true, 1);
420
	}
421
	
422
	/**
423
	 * Returns the value of a given attribute name and returns the attribute from the attributes map.
424
	 * If required is <code>true</code> and the attribute does not exist a mandatory field is missing event is fired.
425
	 * @param xmlEvent
426
	 * @param attributes
427
	 * @param attrName
428
	 * @param isRequired
429
	 * @return
430
	 */
431
	private String getAndRemoveAttributeValue(XMLEvent xmlEvent, Map<String, Attribute> attributes, String attrName, boolean isRequired, int stackDepth) {
432
		Attribute attr = attributes.get(attrName);
433
		if (attr == null ){
434
			if (isRequired){
435
				fireMandatoryElementIsMissing(xmlEvent, attrName, 8, stackDepth+1);
436
			}
437
			return null;
438
		}else{
439
			attributes.remove(attrName);
440
			return attr.getValue();
441
		}
442
	}	
443

    
444
	/**
445
	 * Fires an not yet implemented event if the given attribute exists in attributes.
446
	 * @param attributes
447
	 * @param attrName
448
	 */
449
	protected void handleNotYetImplementedAttribute(Map<String, Attribute>  attributes, String attrName) {
450
		Attribute attr = attributes.get(attrName);
451
		if (attr != null){
452
			attributes.remove(attrName);
453
			QName qName = attr.getName();
454
			fireNotYetImplementedAttribute(attr.getLocation(), qName, 1);
455
		}
456
	}
457

    
458
	/**
459
	 * Fires an unhandled attributes event, if attributes exist in attributes map not covered by the exceptions.
460
	 * No event is fired if the unhandled elements stack is not empty.
461
	 * @param location
462
	 * @param attributes
463
	 * @param exceptions
464
	 */
465
	protected void handleUnexpectedAttributes(Location location,Map<String, Attribute> attributes, String... exceptions) {
466
		handleUnexpectedAttributes(location, attributes, 1, exceptions);
467
	}
468
		
469
	/**
470
	 * see {@link #handleUnexpectedAttributes(Location, Map, String...)}
471
     *
472
	 * @param location
473
	 * @param attributes
474
	 * @param stackDepth the stack trace depth
475
	 * @param exceptions
476
	 */
477
	private void handleUnexpectedAttributes(Location location,Map<String, Attribute> attributes, int stackDepth, String... exceptions) {
478
		if (attributes.size() > 0){
479
			if (this.unhandledElements.size() == 0 ){
480
				boolean hasUnhandledAttributes = false;
481
				for (String key : attributes.keySet()){
482
					boolean isException = false;
483
					for (String exception : exceptions){
484
						if(key.equals(exception)){
485
							isException = true;
486
						}
487
					}
488
					if (!isException){
489
						hasUnhandledAttributes = true;
490
					}
491
				}
492
				if (hasUnhandledAttributes){
493
					fireUnexpectedAttributes(location, attributes, stackDepth+1);
494
				}
495
			}
496
		}
497
	}
498

    
499
	
500
	private void fireUnexpectedAttributes(Location location, Map<String, Attribute> attributes, int stackDepth) {
501
		String attributesString = "";
502
		for (String key : attributes.keySet()){
503
			Attribute attribute = attributes.get(key);
504
			attributesString = CdmUtils.concat(",", attributesString, attribute.getName().getLocalPart() + ":" + attribute.getValue());
505
		}
506
		String message = "Unexpected attributes: %s";
507
		IoProblemEvent event = makeProblemEvent(location, String.format(message, attributesString), 1 , stackDepth +1 );
508
		fire(event);	
509
	}
510
	
511

    
512
	protected void fireUnexpectedAttributeValue(XMLEvent parentEvent, String attrName, String attrValue) {
513
		String message = "Unexpected attribute value %s='%s'";
514
		message = String.format(message, attrName, attrValue);
515
		IoProblemEvent event = makeProblemEvent(parentEvent.getLocation(), message, 1 , 1 );
516
		fire(event);
517
	}
518

    
519
	protected void handleNotYetImplementedAttributeValue(XMLEvent xmlEvent, String attrName, String attrValue) {
520
		String message = "Attribute %s not yet implemented for value '%s'";
521
		message = String.format(message, attrName, attrValue);
522
		IIoEvent event = makeProblemEvent(xmlEvent.getLocation(), message, 1, 1 );
523
		fire(event);		
524
	}
525
	
526
	protected void fireNotYetImplementedAttribute(Location location, QName qName, int stackDepth) {
527
		String message = "Attribute not yet implemented: %s";
528
		IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart()), 1, stackDepth+1 );
529
		fire(event);		
530
	}
531
	
532

    
533
	protected void fireUnexpectedEvent(XMLEvent xmlEvent, int stackDepth) {
534
		Location location = xmlEvent.getLocation();
535
		String message = "Unexpected event: %s";
536
		IIoEvent event = makeProblemEvent(location, String.format(message, xmlEvent.toString()), 2, stackDepth +1);
537
		fire(event);		
538
	}
539

    
540
	protected void fireUnexpectedStartElement(Location location, StartElement startElement, int stackDepth) {
541
		QName qName = startElement.getName();
542
		String message = "Unexpected start element: %s";
543
		IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart()), 2, stackDepth +1);
544
		fire(event);		
545
	}
546
	
547

    
548
	protected void fireUnexpectedEndElement(Location location, EndElement endElement, int stackDepth) {
549
		QName qName = endElement.getName();
550
		String message = "Unexpected end element: %s";
551
		IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart()), 16, stackDepth+1);
552
		fire(event);		
553
	}
554
	
555
	protected void fireNotYetImplementedElement(Location location, QName qName, int stackDepth) {
556
		String message = "Element not yet implemented: %s";
557
		IIoEvent event = makeProblemEvent(location, String.format(message, qName.getLocalPart()), 1, stackDepth+1 );
558
		fire(event);		
559
	}
560

    
561
	protected void fireNotYetImplementedCharacters(Location location, Characters chars, int stackDepth) {
562
		String message = "Characters not yet handled: %s";
563
		IIoEvent event = makeProblemEvent(location, String.format(message, chars.getData()), 1, stackDepth+1 );
564
		fire(event);		
565
	}
566

    
567
	/**
568
	 * Creates a problem event.
569
	 * Be aware of the right depths of the stack trace !
570
	 * @param location 
571
	 * @param message
572
	 * @param severity
573
	 * @return
574
	 */
575
	private IoProblemEvent makeProblemEvent(Location location, String message, int severity, int stackDepth) {
576
		stackDepth++;
577
		StackTraceElement[] stackTrace = new Exception().getStackTrace();
578
		int lineNumber = stackTrace[stackDepth].getLineNumber();
579
		String methodName = stackTrace[stackDepth].getMethodName();
580
		String locationStr = makeLocationStr(location);
581
		String className = stackTrace[stackDepth].getClassName();
582
		Class<?> declaringClass;
583
		try {
584
			declaringClass = Class.forName(className);
585
		} catch (ClassNotFoundException e) {
586
			declaringClass = this.getClass();
587
		}
588
		IoProblemEvent event = IoProblemEvent.NewInstance(declaringClass, message, 
589
				locationStr, lineNumber, severity, methodName);
590
		return event;
591
	}
592

    
593
	/**
594
	 * Creates a string from a location
595
	 * @param location
596
	 * @return
597
	 */
598
	protected String makeLocationStr(Location location) {
599
		String locationStr = location == null ? " - no location - " : "l." + location.getLineNumber() + "/c."+ location.getColumnNumber();
600
		return locationStr;
601
	}
602
	
603

    
604
	/**
605
	 * Fires an unexpected element event if the unhandled elements stack is empty.
606
	 * Otherwise adds the element to the stack.
607
	 * @param event
608
	 */
609
	protected void handleUnexpectedStartElement(XMLEvent event) {
610
		handleUnexpectedStartElement(event, 1);
611
	}
612
	
613
	/**
614
	 * Fires an unexpected element event if the unhandled elements stack is empty.
615
	 * Otherwise adds the element to the stack.
616
	 * @param event
617
	 */
618
	protected void handleUnexpectedStartElement(XMLEvent event, int stackDepth) {
619
		QName qName = event.asStartElement().getName();
620
		if (! unhandledElements.empty()){
621
			unhandledElements.push(qName);
622
		}else{
623
			fireUnexpectedStartElement(event.getLocation(), event.asStartElement(), stackDepth + 1);
624
		}	
625
	}
626

    
627
	
628
	protected void handleUnexpectedEndElement(EndElement event) {
629
		handleUnexpectedEndElement(event, 1);
630
	}
631
	
632
	/**
633
	 * Fires an unexpected element event if the event is not the last on the stack.
634
	 * Otherwise removes last stack element.
635
	 * @param event
636
	 */
637
	protected void handleUnexpectedEndElement(EndElement event, int stackDepth) {
638
		QName qName = event.asEndElement().getName();
639
		if (!unhandledElements.isEmpty() && unhandledElements.peek().equals(qName)){
640
			unhandledElements.pop();
641
		}else{
642
			fireUnexpectedEndElement(event.getLocation(), event.asEndElement(), stackDepth + 1);
643
		}
644
	}
645
	
646
	/**
647
	 * 
648
	 * @param endElement
649
	 */
650
	protected void popUnimplemented(EndElement endElement) {
651
		QName qName = endElement.asEndElement().getName();
652
		if (unhandledElements.peek().equals(qName)){
653
			unhandledElements.pop();
654
		}else{
655
			String message = "End element is not last on stack: %s";
656
			message = String.format(message, qName.getLocalPart());
657
			IIoEvent event = makeProblemEvent(endElement.getLocation(), message, 16, 1);
658
			fire(event);
659
		}
660
		
661
	}
662
	
663
	
664
	/**
665
	 * Fires an unexpected element event if the unhandled element stack is empty.
666
	 * @param event
667
	 */
668
	protected void handleUnexpectedElement(XMLEvent event) {
669
		if (event.isStartElement()){
670
			handleUnexpectedStartElement(event);
671
		}else if (event.isEndElement()){
672
			handleUnexpectedEndElement(event.asEndElement());
673
		}else if (event.getEventType() == XMLStreamConstants.COMMENT){
674
			//do nothing
675
		}else if (! unhandledElements.empty()){
676
			//do nothing
677
		}else{
678
			fireUnexpectedEvent(event, 1);
679
		}	
680
	}
681
	
682
	/**
683
	 * Fires an not yet implemented event and adds the element name to the unhandled elements stack.
684
	 * @param event
685
	 */
686
	protected void handleNotYetImplementedCharacters(XMLEvent event) {
687
		Characters chars = event.asCharacters();
688
		fireNotYetImplementedCharacters(event.getLocation(), chars, 1);
689
	}
690

    
691
	/**
692
	 * Fires an not yet implemented event and adds the element name to the unhandled elements stack.
693
	 * @param event
694
	 */
695
	protected void handleNotYetImplementedElement(XMLEvent event) {
696
		QName qName = event.asStartElement().getName();
697
		boolean isTopLevel = unhandledElements.isEmpty();
698
		unhandledElements.push(qName);
699
		if (isTopLevel){
700
			fireNotYetImplementedElement(event.getLocation(), qName, 1);
701
		}
702
	}
703

    
704
	/**
705
	 * Checks if a mandatory text is not empty or null.
706
	 * Returns true if text is given.
707
	 * Fires an mandatory element is missing event otherwise and returns <code>null</code>.
708
	 * @param text
709
	 * @param parentEvent
710
	 * @return
711
	 */
712
	protected boolean checkMandatoryText(String text, XMLEvent parentEvent) {
713
		if (! StringUtils.isNotBlank(text)){
714
			fireMandatoryElementIsMissing(parentEvent, "CData", 4, 1);
715
			return false;
716
		}
717
		return true;
718
	}
719
	
720
	/**
721
	 * Fires an mandatory element is missing event if exists is <code>false</code>.
722
	 * @param hasMandatory
723
	 * @param parentEvent
724
	 * @param string
725
	 */
726
	protected void checkMandatoryElement(boolean exists, StartElement parentEvent, String attrName) {
727
		if (! exists){
728
			fireMandatoryElementIsMissing(parentEvent, attrName, 5, 1);
729
		}
730
	}
731

    
732
	
733
	/**
734
	 * Fires an element is missing event.
735
	 * @param xmlEvent
736
	 * @param string
737
	 * @param severity
738
	 * @param stackDepth
739
	 * @throws IllegalStateException if xmlEvent is not a StartElement and not an Attribute
740
	 */
741
	private void fireMandatoryElementIsMissing(XMLEvent xmlEvent, String missingEventName, int severity, int stackDepth) throws IllegalStateException{
742
		Location location = xmlEvent.getLocation();
743
		String typeName;
744
		QName qName;
745
		if (xmlEvent.isAttribute()){
746
			Attribute attribute = ((Attribute)xmlEvent);
747
			typeName = "attribute";
748
			qName = attribute.getName();
749
		}else if (xmlEvent.isStartElement()){
750
			typeName = "element";
751
			qName = xmlEvent.asStartElement().getName();
752
		}else{
753
			throw new IllegalStateException("mandatory element only allowed for attributes and start tags in " + makeLocationStr(location));
754
		}
755
		String message = "Mandatory %s '%s' is missing in %s";
756
		message = String.format(message, typeName , missingEventName, qName.getLocalPart());
757
		IIoEvent event = makeProblemEvent(location, message, severity, stackDepth +1);
758
		fire(event);		
759
	}
760
	
761

    
762

    
763

    
764
	/**
765
	 * Returns true if the "next" event is the ending tag for the "parent" event.
766
	 * @param next end element to test, must not be null
767
	 * @param parentEvent start element to test
768
	 * @return true if the "next" event is the ending tag for the "parent" event.
769
	 * @throws XMLStreamException
770
	 */
771
	protected boolean isMyEndingElement(XMLEvent next, XMLEvent parentEvent) throws XMLStreamException {
772
		if (! parentEvent.isStartElement()){
773
			String message = "Parent event should be start tag";
774
			fireWarningEvent(message, makeLocationStr(next.getLocation()), 6);
775
			return false;
776
		}
777
		return isEndingElement(next, parentEvent.asStartElement().getName().getLocalPart());
778
	}
779
	
780
	/**
781
	 * Trims the text and removes turns all whitespaces into single empty space.
782
	 * @param text
783
	 * @return
784
	 */
785
	protected String normalize(String text) {
786
		text = StringUtils.trimToEmpty(text);
787
		text = text.replaceAll("\\s+", " ");
788
		return text;
789
	}
790
	
791

    
792

    
793
	/**
794
	 * Removes whitespaces at beginning and end and makes the first letter
795
	 * a capital letter and all other letters small letters.
796
	 * @param value
797
	 * @return
798
	 */
799
	protected String toFirstCapital(String value) {
800
		if (StringUtils.isBlank(value)){
801
			return value;
802
		}else{
803
			String result = "";
804
			value = value.trim();
805
			result += value.trim().substring(0,1).toUpperCase();
806
			if (value.length()>1){
807
				result += value.substring(1).toLowerCase();
808
			}
809
			return result;
810
		}
811
	}
812
	
813
	/**
814
	 * Currently not used.
815
	 * @param str
816
	 * @param allowedNumberOfCharacters
817
	 * @param onlyFirstCapital
818
	 * @return
819
	 */
820
	protected boolean isAbbreviation(String str, int allowedNumberOfCharacters, boolean onlyFirstCapital){
821
		if (isBlank(str)){
822
			return false;
823
		}
824
		str = str.trim();
825
		if (! str.endsWith(".")){
826
			return false;
827
		}
828
		str = str.substring(0, str.length() -1);
829
		if (str.length() > allowedNumberOfCharacters){
830
			return false;
831
		}
832
		final String re = "^\\p{javaUpperCase}\\p{javaLowerCase}*$";
833
		if (str.matches(re)){
834
			return true;
835
		}else{
836
			return false;
837
		}
838
	}
839
	
840
	/**
841
	 * Checks if <code>abbrev</code> is the short form for the genus name (strGenusName).
842
	 * Usually this is the case if <code>abbrev</code> is the first letter (optional with ".") 
843
	 * of strGenusName. But in older floras it may also be the first 2 or 3 letters (optional with dot).
844
	 * However, we allow only a maximum of 2 letters to be anambigous. In cases with 3 letters better 
845
	 * change the original markup data.
846
	 * @param single
847
	 * @param strGenusName
848
	 * @return
849
	 */
850
	protected boolean isGenusAbbrev(String abbrev, String strGenusName) {
851
		if (! abbrev.matches("[A-Z][a-z]?\\.?")) {
852
			return false;
853
		}else if (abbrev.length() == 0 || strGenusName == null || strGenusName.length() == 0){
854
			return false; 
855
		}else{
856
			abbrev = abbrev.replace(".", "");
857
			return strGenusName.startsWith(abbrev);
858
//			boolean result = true;
859
//			for (int i = 0 ; i < abbrev.length(); i++){
860
//				result &= ( abbrev.charAt(i) == strGenusName.charAt(i));
861
//			}
862
//			return result;
863
		}
864
	}
865

    
866
	
867
	/**
868
	 * Checks if all words in the given string start with a capital letter but do not have any further capital letter.
869
	 * @param word the string to be checekd. Usually should be a single word.
870
	 * @return true if the above is the case, false otherwise
871
	 */
872
	protected boolean isFirstCapitalWord(String word) {
873
		if (WordUtils.capitalizeFully(word).equals(word)){
874
			return true;
875
		}else if (WordUtils.capitalizeFully(word,new char[]{'-'}).equals(word)){
876
			//for words like Le-Testui (which is a species epithet)
877
			return true;
878
		}else{
879
			return false;
880
		}
881
	}
882
	
883

    
884
	/**
885
	 * Read next event. Ignore whitespace events.
886
	 * @param reader
887
	 * @return
888
	 * @throws XMLStreamException
889
	 */
890
	protected XMLEvent readNoWhitespace(XMLEventReader reader) throws XMLStreamException {
891
		XMLEvent event = reader.nextEvent();
892
		while (event.isCharacters() && event.asCharacters().isWhiteSpace()){
893
			event = reader.nextEvent();
894
		}
895
		return event;
896
	}
897
	
898
	/**
899
	 * Returns the REQUIRED "class" attribute for a given event and checks that it is the only attribute.
900
	 * @param parentEvent
901
	 * @return
902
	 */
903
	protected String getClassOnlyAttribute(XMLEvent parentEvent) {
904
		return getClassOnlyAttribute(parentEvent, true);
905
	}
906

    
907

    
908
	/**
909
	 * Returns the "class" attribute for a given event and checks that it is the only attribute.
910
	 * @param parentEvent
911
	 * @return
912
	 */
913
	protected String getClassOnlyAttribute(XMLEvent parentEvent, boolean required) {
914
		return getOnlyAttribute(parentEvent, CLASS, required);
915
	}
916
	
917
	/**
918
	 * Returns the value for the only attribute for a given event and checks that it is the only attribute.
919
	 * @param parentEvent
920
	 * @return
921
	 */
922
	protected String getOnlyAttribute(XMLEvent parentEvent, String attrName, boolean required) {
923
		Map<String, Attribute> attributes = getAttributes(parentEvent);
924
		String classValue =getAndRemoveAttributeValue(parentEvent, attributes, attrName, required, 1);
925
		checkNoAttributes(attributes, parentEvent);
926
		return classValue;
927
	}
928
	
929
	
930
	protected void fireWarningEvent(String message, String locationStr, Integer severity, Integer depth) {
931
		docImport.fireWarningEvent(message, locationStr, severity, depth);
932
	}
933
	
934
	protected void fireWarningEvent(String message, XMLEvent event, Integer severity) {
935
		docImport.fireWarningEvent(message, makeLocationStr(event.getLocation()), severity, 1);
936
	}
937
	
938
	protected void fireSchemaConflictEventExpectedStartTag(String elName, XMLEventReader reader) throws XMLStreamException {
939
		docImport.fireSchemaConflictEventExpectedStartTag(elName, reader);
940
	}
941

    
942
	
943
	protected void fireWarningEvent(String message, String locationStr, int severity) {
944
		docImport.fireWarningEvent(message, locationStr, severity, 1);	
945
	}
946
	
947
	protected void fire(IIoEvent event) {
948
		docImport.fire(event);
949
	}
950
	
951
	protected boolean isNotBlank(String str){
952
		return StringUtils.isNotBlank(str);
953
	}
954
	
955
	protected boolean isBlank(String str){
956
		return StringUtils.isBlank(str);
957
	}
958

    
959
	protected TaxonDescription getTaxonDescription(Taxon taxon, Reference<?> ref, boolean isImageGallery, boolean createNewIfNotExists) {
960
		return docImport.getTaxonDescription(taxon, isImageGallery, createNewIfNotExists);	
961
	}	
962
	
963

    
964
	/**
965
	 * Returns the default language defined in the state. If no default language is defined in the state,
966
	 * the CDM default language is returned.
967
	 * @param state
968
	 * @return
969
	 */
970
	protected Language getDefaultLanguage(MarkupImportState state) {
971
		Language result = state.getDefaultLanguage();
972
		if (result == null){
973
			result = Language.DEFAULT();
974
		}
975
		return result;
976
	}
977

    
978

    
979
//*********************** FROM XML IMPORT BASE ****************************************
980
	protected boolean isEndingElement(XMLEvent event, String elName) throws XMLStreamException {
981
		return docImport.isEndingElement(event, elName);
982
	}
983
	
984
	protected boolean isStartingElement(XMLEvent event, String elName) throws XMLStreamException {
985
		return docImport.isStartingElement(event, elName);
986
	}
987
	
988

    
989
	protected void fillMissingEpithetsForTaxa(Taxon parentTaxon, Taxon childTaxon) {
990
		docImport.fillMissingEpithetsForTaxa(parentTaxon, childTaxon);	
991
	}
992
	
993
	protected Feature getFeature(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<Feature> voc){
994
		return docImport.getFeature(state, uuid, label, text, labelAbbrev, voc);
995
	}
996
	
997
	protected ExtensionType getExtensionType(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev){
998
		return docImport.getExtensionType(state, uuid, label, text, labelAbbrev);
999
	}
1000
	
1001
	protected AnnotationType getAnnotationType(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<AnnotationType> voc){
1002
		return docImport.getAnnotationType(state, uuid, label, text, labelAbbrev, voc);
1003
	}
1004
	
1005
	protected NamedAreaLevel getNamedAreaLevel(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<NamedAreaLevel> voc){
1006
		return docImport.getNamedAreaLevel(state, uuid, label, text, labelAbbrev, voc);
1007
	}
1008
	
1009
	protected NamedArea getNamedArea(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, NamedAreaType areaType, NamedAreaLevel level, TermVocabulary voc, TermMatchMode matchMode){
1010
		return docImport.getNamedArea(state, uuid, label, text, labelAbbrev, areaType, level, voc, matchMode);
1011
	}
1012
	
1013
	protected Language getLanguage(MarkupImportState state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<?> voc){
1014
		return docImport.getLanguage(state, uuid, label, text, labelAbbrev, voc);
1015
	}
1016
	
1017
// *************************************** Concrete methods **********************************************/
1018

    
1019

    
1020
	/**
1021
	 * @param state
1022
	 * @param classValue
1023
	 * @param byAbbrev
1024
	 * @return
1025
	 */
1026
	protected Rank makeRank(MarkupImportState state, String value, boolean byAbbrev) {
1027
		Rank rank = null;
1028
		if (StringUtils.isBlank(value)) {
1029
			return null;
1030
		}
1031
		try {
1032
			boolean useUnknown = true;
1033
			NomenclaturalCode nc = makeNomenclaturalCode(state);
1034
			if (value.equals(GENUS_ABBREVIATION)){
1035
				rank = Rank.GENUS();
1036
			}else if (byAbbrev) {
1037
				rank = Rank.getRankByAbbreviation(value, nc, useUnknown);
1038
			} else {
1039
				rank = Rank.getRankByEnglishName(value, nc, useUnknown);
1040
			}
1041
			if (rank.equals(Rank.UNKNOWN_RANK())) {
1042
				rank = null;
1043
			}
1044
		} catch (UnknownCdmTypeException e) {
1045
			// doNothing
1046
		}
1047
		return rank;
1048
	}
1049

    
1050

    
1051

    
1052
	protected TeamOrPersonBase<?> createAuthor(String authorTitle) {
1053
		// TODO atomize and also use by name creation
1054
		TeamOrPersonBase<?> result = Team.NewTitledInstance(authorTitle, authorTitle);
1055
		return result;
1056
	}
1057
	
1058
	protected String getAndRemoveMapKey(Map<String, String> map, String key) {
1059
		String result = map.get(key);
1060
		map.remove(key);
1061
		if (result != null) {
1062
			result = normalize(result);
1063
		}
1064
		return StringUtils.stripToNull(result);
1065
	}
1066

    
1067

    
1068
	/**
1069
	 * Creates a {@link NonViralName} object depending on the defined {@link NomenclaturalCode}
1070
	 * and the given parameters.
1071
	 * @param state
1072
	 * @param rank
1073
	 * @return
1074
	 */
1075
	protected NonViralName<?> createNameByCode(MarkupImportState state, Rank rank) {
1076
		NonViralName<?> name;
1077
		NomenclaturalCode nc = makeNomenclaturalCode(state);
1078
		name = (NonViralName<?>) nc.getNewTaxonNameInstance(rank);
1079
		return name;
1080
	}
1081
	
1082

    
1083
	/**
1084
	 * Returns the {@link NomenclaturalCode} for this import. Default is {@link NomenclaturalCode#ICBN} if
1085
	 * no code is defined.
1086
	 * @param state
1087
	 * @return
1088
	 */
1089
	protected NomenclaturalCode makeNomenclaturalCode(MarkupImportState state) {
1090
		NomenclaturalCode nc = state.getConfig().getNomenclaturalCode();
1091
		if (nc == null) {
1092
			nc = NomenclaturalCode.ICNAFP; // default;
1093
		}
1094
		return nc;
1095
	}
1096

    
1097

    
1098
	/**
1099
	 * @param state
1100
	 * @param levelString
1101
	 * @param next
1102
	 * @return
1103
	 */
1104
	protected NamedAreaLevel makeNamedAreaLevel(MarkupImportState state, String levelString, XMLEvent next) {
1105
		NamedAreaLevel level;
1106
		try {
1107
			level = state.getTransformer().getNamedAreaLevelByKey(levelString);
1108
			if (level == null) {
1109
				UUID levelUuid = state.getTransformer().getNamedAreaLevelUuid(levelString);
1110
				if (levelUuid == null) {
1111
					String message = "Unknown distribution locality class (named area level): %s. Create new level instead.";
1112
					message = String.format(message, levelString);
1113
					fireWarningEvent(message, next, 6);
1114
				}
1115
				level = getNamedAreaLevel(state, levelUuid, levelString, levelString, levelString, null);
1116
			}
1117
		} catch (UndefinedTransformerMethodException e) {
1118
			throw new RuntimeException(e);
1119
		}
1120
		return level;
1121
	}
1122
	
1123

    
1124
	/**
1125
	 * @param state
1126
	 * @param areaName
1127
	 * @param level
1128
	 * @return 
1129
	 */
1130
	protected NamedArea makeArea(MarkupImportState state, String areaName, NamedAreaLevel level) {
1131
		
1132
		//TODO FM vocabulary
1133
		TermVocabulary<NamedArea> voc = null; 
1134
		NamedAreaType areaType = null;
1135
		
1136
		NamedArea area = null;
1137
		try {
1138
			area = state.getTransformer().getNamedAreaByKey(areaName);
1139
		} catch (UndefinedTransformerMethodException e) {
1140
			throw new RuntimeException(e);
1141
		}
1142
		if (area == null){
1143
			boolean isNewInState = false;
1144
			UUID uuid = state.getAreaUuid(areaName);
1145
			if (uuid == null){
1146
				isNewInState = true;
1147
				
1148
				
1149
				try {
1150
					uuid = state.getTransformer().getNamedAreaUuid(areaName);
1151
				} catch (UndefinedTransformerMethodException e) {
1152
					throw new RuntimeException(e);
1153
				}
1154
			}
1155
			
1156
			CdmImportBase.TermMatchMode matchMode = CdmImportBase.TermMatchMode.UUID_LABEL;
1157
			area = getNamedArea(state, uuid, areaName, areaName, areaName, areaType, level, voc, matchMode);
1158
			if (isNewInState){
1159
				state.putAreaUuid(areaName, area.getUuid());
1160
				
1161
				//TODO just for testing -> make generic and move to better place
1162
				String geoServiceLayer="vmap0_as_bnd_political_boundary_a";
1163
				String layerFieldName ="nam";
1164
				
1165
				if ("Bangka".equals(areaName)){
1166
					String areaValue = "PULAU BANGKA#SUMATERA SELATAN";
1167
					GeoServiceArea geoServiceArea = new GeoServiceArea();
1168
					geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);
1169
					this.editGeoService.setMapping(area, geoServiceArea);
1170
//					save(area, state);
1171
				}
1172
				if ("Luzon".equals(areaName)){
1173
					GeoServiceArea geoServiceArea = new GeoServiceArea();
1174
					
1175
					List<String> list = Arrays.asList("HERMANA MAYOR ISLAND#CENTRAL LUZON",
1176
							"HERMANA MENOR ISLAND#CENTRAL LUZON",
1177
							"CENTRAL LUZON");
1178
					for (String areaValue : list){
1179
						geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);
1180
					}
1181
					
1182
					this.editGeoService.setMapping(area, geoServiceArea);
1183
//					save(area, state);
1184
				}
1185
				if ("Mindanao".equals(areaName)){
1186
					GeoServiceArea geoServiceArea = new GeoServiceArea();
1187
					
1188
					List<String> list = Arrays.asList("NORTHERN MINDANAO",
1189
							"SOUTHERN MINDANAO",
1190
							"WESTERN MINDANAO");
1191
					//TODO to be continued
1192
					for (String areaValue : list){
1193
						geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);
1194
					}
1195
					
1196
					this.editGeoService.setMapping(area, geoServiceArea);
1197
//					save(area, state);
1198
				}
1199
				if ("Palawan".equals(areaName)){
1200
					GeoServiceArea geoServiceArea = new GeoServiceArea();
1201
					
1202
					List<String> list = Arrays.asList("PALAWAN#SOUTHERN TAGALOG");
1203
					for (String areaValue : list){
1204
						geoServiceArea.add(geoServiceLayer, layerFieldName, areaValue);
1205
					}
1206
					
1207
					this.editGeoService.setMapping(area, geoServiceArea);
1208
//					save(area, state);
1209
				}
1210

    
1211
			}
1212
		}
1213
		return area;
1214
	}
1215

    
1216
	
1217
	
1218
	/**
1219
	 * Reads character data. Any element other than character data or the ending
1220
	 * tag will fire an unexpected element event.
1221
     *
1222
	 * @see #getCData(MarkupImportState, XMLEventReader, XMLEvent, boolean)
1223
	 * @param state
1224
	 * @param reader
1225
	 * @param next
1226
	 * @return
1227
	 * @throws XMLStreamException
1228
	 */
1229
	protected String getCData(MarkupImportState state, XMLEventReader reader, XMLEvent next) throws XMLStreamException {
1230
		return getCData(state, reader, next, true);
1231
	}
1232
		
1233
	/**
1234
	 * Reads character data. Any element other than character data or the ending
1235
	 * tag will fire an unexpected element event.
1236
	 * 
1237
	 * @param state
1238
	 * @param reader
1239
	 * @param next
1240
	 * @param inlineMarkup map for inline markup, this is used for e.g. the locality markup within a subheading
1241
	 * The map will be filled by the markup element name as key. The value may be a String, a CdmBase or any other object.
1242
	 * If null any markup text will be neglected but a warning will be fired if they exist.
1243
	 * @param removeInlineMarkupText if true the markedup text will be removed from the returned String 
1244
	 * @param checkAttributes
1245
	 * @return
1246
	 * @throws XMLStreamException
1247
	 */
1248
	protected String getCData(MarkupImportState state, XMLEventReader reader, XMLEvent parent, /*Map<String, Object> inlineMarkup, *boolean removeInlineMarkupText,*/ boolean checkAttributes) throws XMLStreamException {
1249
		if (checkAttributes){
1250
			checkNoAttributes(parent);
1251
		}
1252

    
1253
		String text = "";
1254
		while (reader.hasNext()) {
1255
			XMLEvent next = readNoWhitespace(reader);
1256
			if (isMyEndingElement(next, parent)) {
1257
				return text;
1258
			} else if (next.isCharacters()) {
1259
				text += next.asCharacters().getData();
1260
			} else if (isStartingElement(next, FOOTNOTE_REF)){
1261
				handleNotYetImplementedElement(next);
1262
//			} else if (isStartingElement(next, LOCALITY)){
1263
//				handleCDataLocality(state, reader, parent);
1264
			} else {
1265
				handleUnexpectedElement(next);
1266
			}
1267
		}
1268
		throw new IllegalStateException("Event has no closing tag");
1269

    
1270
	}
1271
	
1272
//	private void handleCDataLocality(MarkupImportState state, XMLEventReader reader, XMLEvent parent) {
1273
//		checkAndRemoveAttributeValue(attributes, attrName, value)
1274
//		
1275
//	}
1276

    
1277

    
1278

    
1279
	/**
1280
	 * For it returns a pure CData annotation string. This behaviour may change in future. More complex annotations
1281
	 * should be handled differently.
1282
	 * @param state
1283
	 * @param reader
1284
	 * @param parentEvent
1285
	 * @return
1286
	 * @throws XMLStreamException
1287
	 */
1288
	protected String handleSimpleAnnotation(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
1289
		String annotation = getCData(state, reader, parentEvent);
1290
		return annotation;
1291
	}
1292
	
1293
	/**
1294
	 * True if text is single "." oder "," or ";" or ":"
1295
	 * @param text
1296
	 * @return
1297
	 */
1298
	protected boolean isPunctuation(String text) {
1299
		return text == null ? false : text.trim().matches("^[\\.,;:]$");
1300
	}
1301

    
1302
	
1303
	protected String getXmlTag(XMLEvent event) {
1304
		String result;
1305
		if (event.isStartElement()) {
1306
			result = "<" + event.asStartElement().getName().getLocalPart()
1307
					+ ">";
1308
		} else if (event.isEndElement()) {
1309
			result = "</" + event.asEndElement().getName().getLocalPart() + ">";
1310
		} else {
1311
			String message = "Only start or end elements are allowed as Html tags";
1312
			throw new IllegalStateException(message);
1313
		}
1314
		return result;
1315
	}
1316

    
1317
	protected WriterDataHolder handleWriter(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
1318
		String text = "";
1319
		checkNoAttributes(parentEvent);
1320
		WriterDataHolder dataHolder = new WriterDataHolder();
1321
		List<FootnoteDataHolder> footnotes = new ArrayList<FootnoteDataHolder>();
1322

    
1323
		// TODO handle attributes
1324
		while (reader.hasNext()) {
1325
			XMLEvent next = readNoWhitespace(reader);
1326
			if (isMyEndingElement(next, parentEvent)) {
1327
				text = CdmUtils.removeBrackets(text);
1328
				if (checkMandatoryText(text, parentEvent)) {
1329
					text = normalize(text);
1330
					dataHolder.writer = text;
1331
					dataHolder.footnotes = footnotes;
1332

    
1333
					// Extension
1334
					UUID uuidWriterExtension = MarkupTransformer.uuidWriterExtension;
1335
					ExtensionType writerExtensionType = this
1336
							.getExtensionType(state, uuidWriterExtension,
1337
									"Writer", "writer", "writer");
1338
					Extension extension = Extension.NewInstance();
1339
					extension.setType(writerExtensionType);
1340
					extension.setValue(text);
1341
					dataHolder.extension = extension;
1342

    
1343
					// Annotation
1344
					UUID uuidWriterAnnotation = MarkupTransformer.uuidWriterAnnotation;
1345
					AnnotationType writerAnnotationType = this.getAnnotationType(state, uuidWriterAnnotation, "Writer", "writer", "writer", null);
1346
					Annotation annotation = Annotation.NewInstance(text, writerAnnotationType, getDefaultLanguage(state));
1347
					dataHolder.annotation = annotation;
1348

    
1349
					return dataHolder;
1350
				} else {
1351
					return null;
1352
				}
1353
			} else if (isStartingElement(next, FOOTNOTE_REF)) {
1354
				FootnoteDataHolder footNote = handleFootnoteRef(state, reader, next);
1355
				if (footNote.isRef()) {
1356
					footnotes.add(footNote);
1357
				} else {
1358
					logger.warn("Non ref footnotes not yet impelemnted");
1359
				}
1360
			} else if (next.isCharacters()) {
1361
				text += next.asCharacters().getData();
1362

    
1363
			} else {
1364
				handleUnexpectedElement(next);
1365
				state.setUnsuccessfull();
1366
			}
1367
		}
1368
		throw new IllegalStateException("<writer> has no end tag");
1369
	}
1370
	
1371

    
1372
	protected void registerFootnotes(MarkupImportState state, AnnotatableEntity entity, List<FootnoteDataHolder> footnotes) {
1373
		for (FootnoteDataHolder footNote : footnotes) {
1374
			registerFootnoteDemand(state, entity, footNote);
1375
		}
1376
	}
1377
	
1378

    
1379
	private void registerFootnoteDemand(MarkupImportState state, AnnotatableEntity entity, FootnoteDataHolder footnote) {
1380
		FootnoteDataHolder existingFootnote = state.getFootnote(footnote.ref);
1381
		if (existingFootnote != null) {
1382
			attachFootnote(state, entity, existingFootnote);
1383
		} else {
1384
			Set<AnnotatableEntity> demands = state.getFootnoteDemands(footnote.ref);
1385
			if (demands == null) {
1386
				demands = new HashSet<AnnotatableEntity>();
1387
				state.putFootnoteDemands(footnote.ref, demands);
1388
			}
1389
			demands.add(entity);
1390
		}
1391
	}
1392
	
1393

    
1394
	protected void attachFootnote(MarkupImportState state, AnnotatableEntity entity, FootnoteDataHolder footnote) {
1395
		AnnotationType annotationType = this.getAnnotationType(state, MarkupTransformer.uuidFootnote, "Footnote", "An e-flora footnote", "fn", null);
1396
		Annotation annotation = Annotation.NewInstance(footnote.string, annotationType, getDefaultLanguage(state));
1397
		// TODO transient objects
1398
		entity.addAnnotation(annotation);
1399
		save(entity, state);
1400
	}
1401
	
1402

    
1403
	protected void attachFigure(MarkupImportState state, XMLEvent next, AnnotatableEntity entity, Media figure) {
1404
		// IdentifiableEntity<?> toSave;
1405
		if (entity.isInstanceOf(TextData.class)) {
1406
			TextData deb = CdmBase.deproxy(entity, TextData.class);
1407
			deb.addMedia(figure);
1408
			// toSave = ((TaxonDescription)deb.getInDescription()).getTaxon();
1409
		} else if (entity.isInstanceOf(SpecimenOrObservationBase.class)) {
1410
			String message = "figures for specimen should be handled as Textdata";
1411
			fireWarningEvent(message, next, 4);
1412
			// toSave = ime;
1413
		} else if (entity.isInstanceOf(IdentifiableMediaEntity.class)) {
1414
			IdentifiableMediaEntity<?> ime = CdmBase.deproxy(entity, IdentifiableMediaEntity.class);
1415
			ime.addMedia(figure);
1416
			// toSave = ime;
1417
		} else {
1418
			String message = "Unsupported entity to attach media: %s";
1419
			message = String.format(message, entity.getClass().getName());
1420
			// toSave = null;
1421
		}
1422
		save(entity, state);
1423
	}
1424
	
1425

    
1426
	protected void registerGivenFootnote(MarkupImportState state, FootnoteDataHolder footnote) {
1427
		state.registerFootnote(footnote);
1428
		Set<AnnotatableEntity> demands = state.getFootnoteDemands(footnote.id);
1429
		if (demands != null) {
1430
			for (AnnotatableEntity entity : demands) {
1431
				attachFootnote(state, entity, footnote);
1432
			}
1433
		}
1434
	}
1435
	
1436

    
1437
	protected FootnoteDataHolder handleFootnote(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, 
1438
			MarkupSpecimenImport specimenImport, MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {
1439
		FootnoteDataHolder result = new FootnoteDataHolder();
1440
		Map<String, Attribute> attributes = getAttributes(parentEvent);
1441
		result.id = getAndRemoveAttributeValue(attributes, ID);
1442
		// result.ref = getAndRemoveAttributeValue(attributes, REF);
1443
		checkNoAttributes(attributes, parentEvent);
1444

    
1445
		while (reader.hasNext()) {
1446
			XMLEvent next = readNoWhitespace(reader);
1447
			if (isStartingElement(next, FOOTNOTE_STRING)) {
1448
				String string = handleFootnoteString(state, reader, next, specimenImport, nomenclatureImport);
1449
				result.string = string;
1450
			} else if (isMyEndingElement(next, parentEvent)) {
1451
				return result;
1452
			} else {
1453
				fireUnexpectedEvent(next, 0);
1454
			}
1455
		}
1456
		return result;
1457
	}
1458
	
1459

    
1460
	protected Media handleFigure(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, 
1461
			MarkupSpecimenImport specimenImport, MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {
1462
		// FigureDataHolder result = new FigureDataHolder();
1463

    
1464
		Map<String, Attribute> attributes = getAttributes(parentEvent);
1465
		String id = getAndRemoveAttributeValue(attributes, ID);
1466
		String type = getAndRemoveAttributeValue(attributes, TYPE);
1467
		String urlAttr = getAndRemoveAttributeValue(attributes, URL);
1468
		checkNoAttributes(attributes, parentEvent);
1469

    
1470
		String urlString = null;
1471
		String legendString = null;
1472
		String titleString = null;
1473
		String numString = null;
1474
		String text = null;
1475
		if (isNotBlank(urlAttr)){
1476
			urlString = CdmUtils.Nz(state.getBaseMediaUrl()) + urlAttr;
1477
		}
1478
		while (reader.hasNext()) {
1479
			XMLEvent next = readNoWhitespace(reader);
1480
			if (isMyEndingElement(next, parentEvent)) {
1481
				if (isNotBlank(text)){
1482
					fireWarningEvent("Text not yet handled for figures: " + text, next, 4);
1483
				}
1484
				Media media = makeFigure(state, id, type, urlString, legendString, titleString, numString, next);
1485
				return media;
1486
			} else if (isStartingElement(next, FIGURE_LEGEND)) {
1487
				// TODO same as figure string ?
1488
				legendString = handleFootnoteString(state, reader, next, specimenImport, nomenclatureImport);
1489
			} else if (isStartingElement(next, FIGURE_TITLE)) {
1490
				titleString = getCData(state, reader, next);
1491
			} else if (isStartingElement(next, URL)) {
1492
				String localUrl = getCData(state, reader, next);
1493
				String url = CdmUtils.Nz(state.getBaseMediaUrl()) + localUrl;
1494
				if (isBlank(urlString)){
1495
					urlString = url;
1496
				}
1497
				if (! url.equals(urlString)){
1498
					String message = "URL attribute and URL element differ. Attribute: %s, Element: %s";
1499
					fireWarningEvent(String.format(message, urlString, url), next, 2);
1500
				}
1501
			} else if (isStartingElement(next, NUM)) {
1502
				numString = getCData(state, reader, next);
1503
			} else if (next.isCharacters()) {
1504
				text += CdmUtils.concat("", text, next.asCharacters().getData());
1505
			} else {
1506
				fireUnexpectedEvent(next, 0);
1507
			}
1508
		}
1509
		throw new IllegalStateException("<figure> has no end tag");
1510
	}
1511

    
1512

    
1513
	/**
1514
	 * @param state
1515
	 * @param id
1516
	 * @param type
1517
	 * @param urlString
1518
	 * @param legendString
1519
	 * @param titleString
1520
	 * @param numString
1521
	 * @param next
1522
	 */
1523
	private Media makeFigure(MarkupImportState state, String id, String type, String urlString, 
1524
			String legendString, String titleString, String numString, XMLEvent next) {
1525
		Media media = null;
1526
		boolean isFigure = false;
1527
		try {
1528
			//TODO maybe everything is a figure as it is all taken from a book
1529
			if ("lineart".equals(type)) {
1530
				isFigure = true;
1531
//				media = Figure.NewInstance(url.toURI(), null, null,	null);
1532
			} else if (type == null || "photo".equals(type)
1533
					|| "signature".equals(type)
1534
					|| "others".equals(type)) {
1535
				//TODO
1536
			} else {
1537
				String message = "Unknown figure type '%s'";
1538
				message = String.format(message, type);
1539
				fireWarningEvent(message, next, 2);
1540
			}
1541
			media = docImport.getImageMedia(urlString, docImport.getReadMediaData(), isFigure);
1542
			
1543
			if (media != null){
1544
				// title
1545
				if (StringUtils.isNotBlank(titleString)) {
1546
					media.putTitle(getDefaultLanguage(state), titleString);
1547
				}
1548
				// legend
1549
				if (StringUtils.isNotBlank(legendString)) {
1550
					media.putDescription(getDefaultLanguage(state), legendString);
1551
				}
1552
				if (StringUtils.isNotBlank(numString)) {
1553
					// TODO use concrete source (e.g. DAPHNIPHYLLACEAE in FM
1554
					// vol.13)
1555
					Reference<?> citation = state.getConfig().getSourceReference();
1556
					media.addSource(OriginalSourceType.Import, numString, "num", citation, null);
1557
					// TODO name used in source if available
1558
				}
1559
				// TODO which citation
1560
				if (StringUtils.isNotBlank(id)) {
1561
					media.addSource(OriginalSourceType.Import, id, null, state.getConfig().getSourceReference(), null);
1562
				} else {
1563
					String message = "Figure id should never be empty or null";
1564
					fireWarningEvent(message, next, 6);
1565
				}
1566

    
1567
				// text
1568
				// do nothing
1569
				registerGivenFigure(state, next, id, media);
1570
				
1571
			}else{
1572
				String message = "No media found: ";
1573
				fireWarningEvent(message, next, 4);
1574
			}
1575
		} catch (MalformedURLException e) {
1576
			String message = "Media uri has incorrect syntax: %s";
1577
			message = String.format(message, urlString);
1578
			fireWarningEvent(message, next, 4);
1579
//		} catch (URISyntaxException e) {
1580
//			String message = "Media uri has incorrect syntax: %s";
1581
//			message = String.format(message, urlString);
1582
//			fireWarningEvent(message, next, 4);
1583
		}
1584

    
1585
		return media;
1586
	}
1587
	
1588

    
1589
	private void registerGivenFigure(MarkupImportState state, XMLEvent next, String id, Media figure) {
1590
		state.registerFigure(id, figure);
1591
		Set<AnnotatableEntity> demands = state.getFigureDemands(id);
1592
		if (demands != null) {
1593
			for (AnnotatableEntity entity : demands) {
1594
				attachFigure(state, next, entity, figure);
1595
			}
1596
		}
1597
		save(figure, state);
1598
	}
1599
	
1600

    
1601
	private FootnoteDataHolder handleFootnoteRef(MarkupImportState state,
1602
			XMLEventReader reader, XMLEvent parentEvent)
1603
			throws XMLStreamException {
1604
		FootnoteDataHolder result = new FootnoteDataHolder();
1605
		Map<String, Attribute> attributes = getAttributes(parentEvent);
1606
		result.ref = getAndRemoveAttributeValue(attributes, REF);
1607
		checkNoAttributes(attributes, parentEvent);
1608

    
1609
		// text is not handled, needed only for debugging purposes
1610
		String text = "";
1611
		while (reader.hasNext()) {
1612
			XMLEvent next = readNoWhitespace(reader);
1613
			// if (isStartingElement(next, FOOTNOTE_STRING)){
1614
			// String string = handleFootnoteString(state, reader, next);
1615
			// result.string = string;
1616
			// }else
1617
			if (isMyEndingElement(next, parentEvent)) {
1618
				return result;
1619
			} else if (next.isCharacters()) {
1620
				text += next.asCharacters().getData();
1621

    
1622
			} else {
1623
				fireUnexpectedEvent(next, 0);
1624
			}
1625
		}
1626
		return result;
1627
	}
1628

    
1629

    
1630

    
1631
	private String handleFootnoteString(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, MarkupSpecimenImport specimenImport, MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {
1632
		boolean isTextMode = true;
1633
		String text = "";
1634
		while (reader.hasNext()) {
1635
			XMLEvent next = readNoWhitespace(reader);
1636
			if (isMyEndingElement(next, parentEvent)) {
1637
				return text;
1638
			} else if (next.isEndElement()) {
1639
				if (isEndingElement(next, FULL_NAME)) {
1640
					popUnimplemented(next.asEndElement());
1641
				} else if (isEndingElement(next, BR)) {
1642
					isTextMode = true;
1643
				} else if (isHtml(next)) {
1644
					text += getXmlTag(next);
1645
				} else {
1646
					handleUnexpectedEndElement(next.asEndElement());
1647
				}
1648
			} else if (next.isStartElement()) {
1649
				if (isStartingElement(next, FULL_NAME)) {
1650
					handleNotYetImplementedElement(next);
1651
				} else if (isStartingElement(next, GATHERING)) {
1652
					text += specimenImport.handleInLineGathering(state, reader, next);
1653
				} else if (isStartingElement(next, REFERENCES)) {
1654
					text += " " + handleInLineReferences(state, reader, next, nomenclatureImport)+ " ";
1655
				} else if (isStartingElement(next, BR)) {
1656
					text += "<br/>";
1657
					isTextMode = false;
1658
				} else if (isStartingElement(next, NOMENCLATURE)) {
1659
					handleNotYetImplementedElement(next);
1660
				} else if (isHtml(next)) {
1661
					text += getXmlTag(next);
1662
				} else {
1663
					handleUnexpectedStartElement(next.asStartElement());
1664
				}
1665
			} else if (next.isCharacters()) {
1666
				if (!isTextMode) {
1667
					String message = "footnoteString is not in text mode";
1668
					fireWarningEvent(message, next, 6);
1669
				} else {
1670
					text += next.asCharacters().getData().trim(); 
1671
					// getCData(state, reader, next); does not work as we have inner tags like <references>
1672
				}
1673
			} else {
1674
				handleUnexpectedEndElement(next.asEndElement());
1675
			}
1676
		}
1677
		throw new IllegalStateException("<footnoteString> has no closing tag");
1678

    
1679
	}
1680

    
1681
	private static final List<String> htmlList = Arrays.asList("sub", "sup",
1682
			"ol", "ul", "li", "i", "b", "table", "br","tr","td");
1683

    
1684
	protected boolean isHtml(XMLEvent event) {
1685
		if (event.isStartElement()) {
1686
			String tag = event.asStartElement().getName().getLocalPart();
1687
			return htmlList.contains(tag);
1688
		} else if (event.isEndElement()) {
1689
			String tag = event.asEndElement().getName().getLocalPart();
1690
			return htmlList.contains(tag);
1691
		} else {
1692
			return false;
1693
		}
1694

    
1695
	}
1696
	
1697

    
1698
	private String handleInLineReferences(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent, MarkupNomenclatureImport nomenclatureImport) throws XMLStreamException {
1699
		checkNoAttributes(parentEvent);
1700

    
1701
		boolean hasReference = false;
1702
		String text = "";
1703
		while (reader.hasNext()) {
1704
			XMLEvent next = readNoWhitespace(reader);
1705
			if (isMyEndingElement(next, parentEvent)) {
1706
				checkMandatoryElement(hasReference, parentEvent.asStartElement(), REFERENCE);
1707
				return text;
1708
			} else if (isStartingElement(next, REFERENCE)) {
1709
				text += handleInLineReference(state, reader, next, nomenclatureImport);
1710
				hasReference = true;
1711
			} else {
1712
				handleUnexpectedElement(next);
1713
			}
1714
		}
1715
		throw new IllegalStateException("<References> has no closing tag");
1716
	}
1717

    
1718
	private String handleInLineReference(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent, MarkupNomenclatureImport nomenclatureImport)throws XMLStreamException {
1719
		Reference<?> reference = nomenclatureImport.handleReference(state, reader, parentEvent);
1720
		String result = "<cdm:ref uuid='%s'>%s</ref>";
1721
		result = String.format(result, reference.getUuid(), reference.getTitleCache());
1722
		save(reference, state);
1723
		return result;
1724
	}
1725
	
1726

    
1727
	/**
1728
	 * Handle string
1729
	 * @param state
1730
	 * @param reader
1731
	 * @param parentEvent
1732
	 * @param feature only needed for distributionLocalities
1733
	 * @return
1734
	 * @throws XMLStreamException
1735
	 */
1736
	protected Map<String, String> handleString(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, Feature feature)throws XMLStreamException {
1737
		// attributes
1738
		String classValue = getClassOnlyAttribute(parentEvent, false);
1739
		if (StringUtils.isNotBlank(classValue)) {
1740
			String message = "class attribute for <string> not yet implemented";
1741
			fireWarningEvent(message, parentEvent, 2);
1742
		}
1743

    
1744
		// subheadings
1745
		Map<String, String> subHeadingMap = new HashMap<String, String>();
1746
		String currentSubheading = null;
1747

    
1748
		boolean isTextMode = true;
1749
		String text = "";
1750
		while (reader.hasNext()) {
1751
			XMLEvent next = readNoWhitespace(reader);
1752
			if (isMyEndingElement(next, parentEvent)) {
1753
				putCurrentSubheading(subHeadingMap, currentSubheading, text);
1754
				return subHeadingMap;
1755
			} else if (isStartingElement(next, BR)) {
1756
				text += "<br/>";
1757
				isTextMode = false;
1758
			} else if (isEndingElement(next, BR)) {
1759
				isTextMode = true;
1760
			} else if (isHtml(next)) {
1761
				text += getXmlTag(next);
1762
			} else if (isStartingElement(next, SUB_HEADING)) {
1763
				text = putCurrentSubheading(subHeadingMap,currentSubheading, text);
1764
				// TODO footnotes
1765
				currentSubheading = getCData(state, reader, next).trim();
1766
			} else if (isStartingElement(next, DISTRIBUTION_LOCALITY)) {
1767
				if (feature != null && !feature.equals(Feature.DISTRIBUTION())) {
1768
					String message = "Distribution locality only allowed for feature of type 'distribution'";
1769
					fireWarningEvent(message, next, 4);
1770
				}
1771
				text += handleDistributionLocality(state, reader, next);
1772
			} else if (next.isCharacters()) {
1773
				if (! isTextMode) {
1774
					String message = "String is not in text mode";
1775
					fireWarningEvent(message, next, 6);
1776
				} else {
1777
					text += next.asCharacters().getData();
1778
				}
1779
			} else if (isStartingElement(next, HEADING)) {
1780
				//TODO
1781
				handleNotYetImplementedElement(next);
1782
			} else if (isStartingElement(next, VERNACULAR_NAMES)) {
1783
				//TODO
1784
				handleNotYetImplementedElement(next);
1785
			} else if (isStartingElement(next, QUOTE)) {
1786
				//TODO
1787
				handleNotYetImplementedElement(next);
1788
			} else if (isStartingElement(next, DEDICATION)) {
1789
				//TODO
1790
				handleNotYetImplementedElement(next);
1791
			} else if (isStartingElement(next, TAXONTYPE)) {
1792
				//TODO
1793
				handleNotYetImplementedElement(next);
1794
			} else if (isStartingElement(next, FULL_NAME)) {
1795
				//TODO
1796
				handleNotYetImplementedElement(next);
1797
			}else if (isStartingElement(next, REFERENCES)) {
1798
				//TODO
1799
				handleNotYetImplementedElement(next);
1800
			} else if (isStartingElement(next, GATHERING)) {
1801
				//TODO
1802
				handleNotYetImplementedElement(next);
1803
			} else if (isStartingElement(next, ANNOTATION)) {
1804
				//TODO  //TODO test handleSimpleAnnotation
1805
				handleNotYetImplementedElement(next);
1806
			} else if (isStartingElement(next, HABITAT)) {
1807
				//TODO
1808
				handleNotYetImplementedElement(next);
1809
			} else if (isStartingElement(next, FIGURE_REF)) {
1810
				//TODO
1811
				handleNotYetImplementedElement(next);
1812
			} else if (isStartingElement(next, FIGURE)) {
1813
				//TODO
1814
				handleNotYetImplementedElement(next);
1815
			} else if (isStartingElement(next, FOOTNOTE_REF)) {
1816
				//TODO
1817
				handleNotYetImplementedElement(next);
1818
			} else if (isStartingElement(next, FOOTNOTE)) {
1819
				//TODO
1820
				handleNotYetImplementedElement(next);
1821
			} else if (isStartingElement(next, WRITER)) {
1822
				//TODO
1823
				handleNotYetImplementedElement(next);
1824
			} else if (isStartingElement(next, DATES)) {
1825
				//TODO
1826
				handleNotYetImplementedElement(next);
1827
			} else {
1828
				handleUnexpectedElement(next);
1829
			}
1830
		}
1831
		throw new IllegalStateException("<String> has no closing tag");
1832
	}
1833
	
1834

    
1835
	/**
1836
	 * @param subHeadingMap
1837
	 * @param currentSubheading
1838
	 * @param text
1839
	 * @return
1840
	 */
1841
	private String putCurrentSubheading(Map<String, String> subHeadingMap, String currentSubheading, String text) {
1842
		if (StringUtils.isNotBlank(text)) {
1843
			text = removeStartingMinus(text);
1844
			subHeadingMap.put(currentSubheading, text.trim());
1845
		}
1846
		return "";
1847
	}
1848

    
1849
	private String removeStartingMinus(String string) {
1850
		string = replaceStart(string, "-");
1851
		string = replaceStart(string, "\u002d");
1852
		string = replaceStart(string, "\u2013");
1853
		string = replaceStart(string, "\u2014");
1854
		string = replaceStart(string, "--");
1855
		return string;
1856
	}
1857
	
1858
	
1859
	/**
1860
	 * @param value
1861
	 * @param replacementString
1862
	 */
1863
	private String replaceStart(String value, String replacementString) {
1864
		if (value.startsWith(replacementString) ){
1865
			value = value.substring(replacementString.length()).trim();
1866
		}
1867
		while (value.startsWith("-") || value.startsWith("\u2014") ){
1868
			value = value.substring("-".length()).trim();
1869
		}
1870
		return value;
1871
	}
1872
	
1873

    
1874
	private String handleDistributionLocality(MarkupImportState state,XMLEventReader reader, XMLEvent parentEvent)throws XMLStreamException {
1875
		Map<String, Attribute> attributes = getAttributes(parentEvent);
1876
		String classValue = getAndRemoveRequiredAttributeValue(parentEvent, attributes, CLASS);
1877
		String statusValue =getAndRemoveAttributeValue(attributes, STATUS);
1878
		String frequencyValue =getAndRemoveAttributeValue(attributes, FREQUENCY);
1879
		
1880

    
1881
		Taxon taxon = state.getCurrentTaxon();
1882
		// TODO which ref to take?
1883
		Reference<?> ref = state.getConfig().getSourceReference();
1884

    
1885
		String text = "";
1886
		while (reader.hasNext()) {
1887
			XMLEvent next = readNoWhitespace(reader);
1888
			if (isMyEndingElement(next, parentEvent)) {
1889
				if (StringUtils.isNotBlank(text)) {
1890
					String label = CdmUtils.removeTrailingDot(normalize(text));
1891
					TaxonDescription description = getTaxonDescription(taxon, ref, false, true);
1892
					NamedAreaLevel level = makeNamedAreaLevel(state,classValue, next);
1893
					
1894
					//status
1895
					PresenceAbsenceTermBase<?> status = null;
1896
					if (isNotBlank(statusValue)){
1897
						try {
1898
							status = state.getTransformer().getPresenceTermByKey(statusValue);
1899
							if (status == null){
1900
								//TODO
1901
								String message = "The presence/absence status '%s' could not be transformed to an CDM status";								
1902
								fireWarningEvent(String.format(message, statusValue), next, 4);
1903
							}
1904
						} catch (UndefinedTransformerMethodException e) {
1905
							throw new RuntimeException(e);
1906
						}
1907
					}else{
1908
						status = PresenceTerm.PRESENT();
1909
					}
1910
					//frequency
1911
					if (isNotBlank(frequencyValue)){
1912
						String message = "The frequency attribute is currently not yet available in CDM";
1913
						fireWarningEvent(message, parentEvent, 6);
1914
					}
1915
					
1916
					NamedArea higherArea = null;
1917
					List<NamedArea> areas = new ArrayList<NamedArea>(); 
1918
					
1919
					String patSingleArea = "([^,\\(]{3,})";
1920
					String patSeparator = "(,|\\sand\\s)";
1921
					String hierarchiePattern = String.format("%s\\((%s(%s%s)*)\\)",patSingleArea, patSingleArea, patSeparator, patSingleArea);
1922
					Pattern patHierarchie = Pattern.compile(hierarchiePattern, Pattern.CASE_INSENSITIVE);
1923
					Matcher matcher = patHierarchie.matcher(label); 
1924
					if (matcher.matches()){
1925
						String higherAreaStr = matcher.group(1).trim();
1926
						higherArea =  makeArea(state, higherAreaStr, level);
1927
						String[] innerAreas = matcher.group(2).split(patSeparator);
1928
						for (String innerArea : innerAreas){
1929
							if (isNotBlank(innerArea)){
1930
								NamedArea singleArea = makeArea(state, innerArea.trim(), level);
1931
								areas.add(singleArea);
1932
								NamedArea partOf = singleArea.getPartOf();
1933
//								if (partOf == null){
1934
//									singleArea.setPartOf(higherArea);
1935
//								}
1936
							}
1937
						}
1938
					}else{
1939
						NamedArea singleArea = makeArea(state, label, level);
1940
						areas.add(singleArea);
1941
					}
1942
					
1943
					for (NamedArea area : areas){
1944
						//create distribution
1945
						Distribution distribution = Distribution.NewInstance(area,status);
1946
						description.addElement(distribution);
1947
					}
1948
				} else {
1949
					String message = "Empty distribution locality";
1950
					fireWarningEvent(message, next, 4);
1951
				}
1952
				return text;
1953
			} else if (isStartingElement(next, COORDINATES)) {
1954
				//TODO
1955
				handleNotYetImplementedElement(next);
1956
			} else if (isEndingElement(next, COORDINATES)) {
1957
				//TODO
1958
				popUnimplemented(next.asEndElement());
1959
			} else if (next.isCharacters()) {
1960
				text += next.asCharacters().getData();
1961
			} else {
1962
				handleUnexpectedElement(next);
1963
			}
1964
		}
1965
		throw new IllegalStateException("<DistributionLocality> has no closing tag");
1966
	}	
1967

    
1968
	
1969
//********************************************** OLD *************************************	
1970

    
1971
//	protected boolean testAdditionalElements(Element parentElement, List<String> excludeList){
1972
//		boolean result = true;
1973
//		List<Element> list = parentElement.getChildren();
1974
//		for (Element element : list){
1975
//			if (! excludeList.contains(element.getName())){
1976
//				logger.warn("Unknown element (" + element.getName() + ") in parent element (" + parentElement.getName() + ")");
1977
//				result = false;
1978
//			}
1979
//		}
1980
//		return result;
1981
//	}
1982
//	
1983
//	
1984
//	protected <T extends IdentifiableEntity> T makeReferenceType(Element element, Class<? extends T> clazz, MapWrapper<? extends T> objectMap, ResultWrapper<Boolean> success){
1985
//		T result = null;
1986
//		String linkType = element.getAttributeValue("linkType");
1987
//		String ref = element.getAttributeValue("ref");
1988
//		if(ref == null && linkType == null){
1989
//			result = getInstance(clazz);
1990
//			if (result != null){
1991
//				String title = element.getTextNormalize();
1992
//				result.setTitleCache(title, true);
1993
//			}
1994
//		}else if (linkType == null || linkType.equals("local")){
1995
//			//TODO
1996
//			result = objectMap.get(ref);
1997
//			if (result == null){
1998
//				logger.warn("Object (ref = " + ref + ")could not be found in WrapperMap");
1999
//			}
2000
//		}else if(linkType.equals("external")){
2001
//			logger.warn("External link types not yet implemented");
2002
//		}else if(linkType.equals("other")){
2003
//			logger.warn("Other link types not yet implemented");
2004
//		}else{
2005
//			logger.warn("Unknown link type or missing ref");
2006
//		}
2007
//		if (result == null){
2008
//			success.setValue(false);
2009
//		}
2010
//		return result;
2011
//	}
2012
//	
2013
//	
2014
//	protected Reference makeAccordingTo(Element elAccordingTo, MapWrapper<Reference> referenceMap, ResultWrapper<Boolean> success){
2015
//		Reference result = null;
2016
//		if (elAccordingTo != null){
2017
//			String childName = "AccordingToDetailed";
2018
//			boolean obligatory = false;
2019
//			Element elAccordingToDetailed = XmlHelp.getSingleChildElement(success, elAccordingTo, childName, elAccordingTo.getNamespace(), obligatory);
2020
//
2021
//			childName = "Simple";
2022
//			obligatory = true;
2023
//			Element elSimple = XmlHelp.getSingleChildElement(success, elAccordingTo, childName, elAccordingTo.getNamespace(), obligatory);
2024
//			
2025
//			if (elAccordingToDetailed != null){
2026
//				result = makeAccordingToDetailed(elAccordingToDetailed, referenceMap, success);
2027
//			}else{
2028
//				result = ReferenceFactory.newGeneric();
2029
//				String title = elSimple.getTextNormalize();
2030
//				result.setTitleCache(title, true);
2031
//			}
2032
//		}
2033
//		return result;
2034
//	}
2035
//	
2036
//	
2037
//	private Reference makeAccordingToDetailed(Element elAccordingToDetailed, MapWrapper<Reference> referenceMap, ResultWrapper<Boolean> success){
2038
//		Reference result = null;
2039
//		Namespace tcsNamespace = elAccordingToDetailed.getNamespace();
2040
//		if (elAccordingToDetailed != null){
2041
//			//AuthorTeam
2042
//			String childName = "AuthorTeam";
2043
//			boolean obligatory = false;
2044
//			Element elAuthorTeam = XmlHelp.getSingleChildElement(success, elAccordingToDetailed, childName, tcsNamespace, obligatory);
2045
//			makeAccordingToAuthorTeam(elAuthorTeam, success);
2046
//			
2047
//			//PublishedIn
2048
//			childName = "PublishedIn";
2049
//			obligatory = false;
2050
//			Element elPublishedIn = XmlHelp.getSingleChildElement(success, elAccordingToDetailed, childName, tcsNamespace, obligatory);
2051
//			result = makeReferenceType(elPublishedIn, Reference.class, referenceMap, success);
2052
//			
2053
//			//MicroReference
2054
//			childName = "MicroReference";
2055
//			obligatory = false;
2056
//			Element elMicroReference = XmlHelp.getSingleChildElement(success, elAccordingToDetailed, childName, tcsNamespace, obligatory);
2057
//			String microReference = elMicroReference.getTextNormalize();
2058
//			if (CdmUtils.Nz(microReference).equals("")){
2059
//				//TODO
2060
//				logger.warn("MicroReference not yet implemented for AccordingToDetailed");	
2061
//			}
2062
//		}
2063
//		return result;
2064
//	}
2065
//
2066
//	private Team makeAccordingToAuthorTeam(Element elAuthorTeam, ResultWrapper<Boolean> succes){
2067
//		Team result = null;
2068
//		if (elAuthorTeam != null){
2069
//			//TODO
2070
//			logger.warn("AuthorTeam not yet implemented for AccordingToDetailed");
2071
//		}
2072
//		return result;
2073
//	}
2074

    
2075

    
2076

    
2077
}
(9-9/19)