Project

General

Profile

Download (41.5 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
 * Copyright (C) 2009 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
package eu.etaxonomy.cdm.io.markup;
10

    
11
import java.util.ArrayList;
12
import java.util.HashSet;
13
import java.util.List;
14
import java.util.Map;
15
import java.util.Set;
16
import java.util.UUID;
17

    
18
import javax.xml.stream.XMLEventReader;
19
import javax.xml.stream.XMLStreamException;
20
import javax.xml.stream.events.Attribute;
21
import javax.xml.stream.events.XMLEvent;
22

    
23
import org.apache.commons.lang.StringUtils;
24
import org.apache.logging.log4j.LogManager;
25
import org.apache.logging.log4j.Logger;
26

    
27
import eu.etaxonomy.cdm.common.CdmUtils;
28
import eu.etaxonomy.cdm.io.common.mapping.UndefinedTransformerMethodException;
29
import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
30
import eu.etaxonomy.cdm.model.common.Annotation;
31
import eu.etaxonomy.cdm.model.common.AnnotationType;
32
import eu.etaxonomy.cdm.model.common.CdmBase;
33
import eu.etaxonomy.cdm.model.common.IntextReference;
34
import eu.etaxonomy.cdm.model.common.Language;
35
import eu.etaxonomy.cdm.model.common.LanguageString;
36
import eu.etaxonomy.cdm.model.common.MarkerType;
37
import eu.etaxonomy.cdm.model.description.CommonTaxonName;
38
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
39
import eu.etaxonomy.cdm.model.description.Feature;
40
import eu.etaxonomy.cdm.model.description.TaxonDescription;
41
import eu.etaxonomy.cdm.model.description.TextData;
42
import eu.etaxonomy.cdm.model.location.Country;
43
import eu.etaxonomy.cdm.model.location.NamedArea;
44
import eu.etaxonomy.cdm.model.media.Media;
45
import eu.etaxonomy.cdm.model.reference.Reference;
46
import eu.etaxonomy.cdm.model.taxon.Taxon;
47
import eu.etaxonomy.cdm.model.term.TermVocabulary;
48

    
49
/**
50
 * @author a.mueller
51
 * @since 30.05.2012
52
 */
53
public class MarkupFeatureImport extends MarkupImportBase {
54

    
55
    @SuppressWarnings("unused")
56
	private static final Logger logger = LogManager.getLogger(MarkupFeatureImport.class);
57

    
58
	protected static final String MODS_TITLEINFO = "titleInfo";
59

    
60
	private final MarkupSpecimenImport specimenImport;
61
	private final MarkupNomenclatureImport nomenclatureImport;
62
	private final MarkupKeyImport keyImport;
63

    
64
	public MarkupFeatureImport(MarkupDocumentImport docImport, MarkupSpecimenImport specimenImport,
65
			 MarkupNomenclatureImport nomenclatureImport, MarkupKeyImport keyImport) {
66
		super(docImport);
67
		this.specimenImport = specimenImport;
68
		this.nomenclatureImport = nomenclatureImport;
69
		this.keyImport = keyImport;
70
		this.featureImport = this;
71
	}
72

    
73
	public void handleFeature(MarkupImportState state, XMLEventReader readerOrig, XMLEvent parentEvent) throws XMLStreamException {
74
		Map<String, Attribute> attrs = getAttributes(parentEvent);
75
		Boolean isFreetext = getAndRemoveBooleanAttributeValue(parentEvent, attrs, IS_FREETEXT, false);
76
		String classValue =getAndRemoveRequiredAttributeValue(parentEvent, attrs, CLASS);
77
		checkNoAttributes(attrs, parentEvent);
78

    
79
		Reference sourceReference = state.getConfig().getSourceReference();
80
		Feature feature = makeFeature(classValue, state, parentEvent, null);
81
		Taxon taxon = state.getCurrentTaxon();
82
		TaxonDescription taxonDescription = getDefaultTaxonDescription(taxon, NO_IMAGE_GALLERY, CREATE_NEW, sourceReference);
83
		if (!taxonDescription.isDefault()){
84
		    taxonDescription.setDefault(true);
85
		}
86
		// TextData figureHolderTextData = null; //for use with one TextData for
87
		// all figure only
88

    
89

    
90
		TaxonDescription structuredDescription = null;
91

    
92
		boolean isDescription = feature.equals(Feature.DESCRIPTION());
93

    
94
		XMLEventReader reader;
95
		if (isDescription){
96
		    LookAheadEventReader lookAhead = new LookAheadEventReader(parentEvent.asStartElement(), readerOrig);
97
		    String descriptionText = makeFullDescriptionText(lookAhead.getCachedEvents(true));
98
		    TextData descriptionTextData = TextData.NewInstance(Feature.DESCRIPTION(), descriptionText, getDefaultLanguage(state),null);
99
		    descriptionTextData.addPrimaryTaxonomicSource(sourceReference);
100
		    taxonDescription.addElement(descriptionTextData);
101
		    reader = lookAhead;
102
		}else{
103
		    reader = readerOrig;
104
		}
105

    
106
		DescriptionElementBase lastDescriptionElement = null;
107

    
108
		CharOrder charOrder= new CharOrder();
109
		while (reader.hasNext()) {
110
			XMLEvent next = readNoWhitespace(reader);
111
			if (isMyEndingElement(next, parentEvent)) {
112
				state.putFeatureToGeneralSorterList(feature);
113
				return;
114
			} else if (isEndingElement(next, DISTRIBUTION_LIST) || isEndingElement(next, HABITAT_LIST)) {
115
				// only handle list elements
116
			} else if (isStartingElement(next, HEADING)) {
117
				makeFeatureHeading(state, reader, classValue, feature, next);
118
			} else if (isStartingElement(next, WRITER)) {
119
				makeFeatureWriter(state, reader, feature, taxon, next);
120
//			} else if (isStartingElement(next, DISTRIBUTION_LOCALITY)) {
121
//				if (!feature.equals(Feature.DISTRIBUTION())) {
122
//					String message = "Distribution locality only allowed for feature of type 'distribution'";
123
//					fireWarningEvent(message, next, 4);
124
//				}
125
//				handleDistributionLocality(state, reader, next);
126
			} else if (isStartingElement(next, DISTRIBUTION_LIST) || isStartingElement(next, HABITAT_LIST)) {
127
				// only handle single list elements
128
			} else if (isStartingElement(next, HABITAT)) {
129
				if (!(feature.equals(Feature.HABITAT())
130
						|| feature.equals(Feature.HABITAT_ECOLOGY())
131
						|| feature.equals(Feature.ECOLOGY()))) {
132
					String message = "Habitat only allowed for feature of type 'habitat','habitat ecology' or 'ecology'";
133
					fireWarningEvent(message, next, 4);
134
				}
135
				String habitatString = handleHabitat(state, reader, next);
136
				fireWarningEvent("Return value from habitat tag not yet handled: " + habitatString, next, 4);
137
			} else if (isStartingElement(next, CHAR)) {
138
			    if (structuredDescription == null){
139
			        MarkerType descriptionMarker;
140
			        try {
141
			            descriptionMarker = getMarkerType(state, state.getTransformer().getMarkerTypeUuid("structured description"),
142
			                    "Structured Descriptions", "Marker to mark descriptions used for more structured descriptions", null, null);
143
			        } catch (UndefinedTransformerMethodException e) {
144
			            throw new RuntimeException(e);
145
			        }
146
			        String title = "Structured descriptive data for " + taxon.getName().getTitleCache();
147
			        structuredDescription = getMarkedTaxonDescription(taxon, descriptionMarker, NO_IMAGE_GALLERY, CREATE_NEW, state.getConfig().getSourceReference(), title);
148
			    }
149
			    List<TextData> textDataList = handleChar(state, reader, next, null, charOrder);
150
				charOrder = charOrder.next();
151
				for (TextData textData : textDataList){
152
				    structuredDescription.addElement(textData);
153
				}
154
			} else if (isStartingElement(next, STRING)) {
155
				lastDescriptionElement = makeFeatureString(state, reader, feature,
156
				        taxonDescription, lastDescriptionElement, next, isFreetext);
157
			} else if (isStartingElement(next, FIGURE_REF)) {
158
				lastDescriptionElement = makeFeatureFigureRef(state, reader,
159
				        taxonDescription, isDescription, lastDescriptionElement, sourceReference, next);
160
			} else if (isStartingElement(next, REFERENCES)) {
161
				fireWarningEvent("Check correct handling of feature references", next, 4);
162
				List<Reference> refs = handleReferences(state, reader, next);
163
				if (!refs.isEmpty()) {
164
					// TODO
165
					Reference descriptionRef = state.getConfig().getSourceReference();
166
					TaxonDescription description = getDefaultTaxonDescription(taxon, false, true, descriptionRef);
167
					TextData featurePlaceholder = docImport.getFeaturePlaceholder(state, description, feature, true);
168
					for (Reference citation : refs) {
169
						featurePlaceholder.addPrimaryTaxonomicSource(citation);
170
					}
171
				} else {
172
					String message = "No reference found in references";
173
					fireWarningEvent(message, next, 6);
174
				}
175
			} else if (isStartingElement(next, NUM)) {
176
				//TODO
177
				handleNotYetImplementedElement(next);
178
			} else if (isStartingElement(next, KEY)) {
179
			    keyImport.handleKey(state, reader, next);
180
			} else {
181
				handleUnexpectedElement(next);
182
			}
183
		}
184
		throw new IllegalStateException("<Feature> has no closing tag");
185
	}
186

    
187

    
188
	/**
189
     * Creates a full description text from the mark
190
     */
191
    private String makeFullDescriptionText(List<XMLEvent> events) {
192
        String result = "";
193
        for (XMLEvent event : events){
194
            String text = normalize(event.asCharacters().getData());
195
            result = CdmUtils.concat(" ", result, text);
196
        }
197
        return result;
198
    }
199

    
200
	public DescriptionElementBase makeFeatureFigureRef(MarkupImportState state, XMLEventReader reader,TaxonDescription taxonDescription,
201
					boolean isDescription, DescriptionElementBase lastDescriptionElement, Reference sourceReference, XMLEvent next) throws XMLStreamException {
202
		FigureDataHolder figureHolder = handleFigureRef(state, reader, next);
203
		Feature figureFeature = getFeature(state, MarkupTransformer.uuidFigures, "Figures", "Figures", "Fig.",null);
204
		if (isDescription) {
205
			TextData figureHolderTextData = null;
206
			// if (figureHolderTextData == null){
207
			figureHolderTextData = TextData.NewInstance(figureFeature);
208
			figureHolderTextData.addPrimaryTaxonomicSource(sourceReference);
209

    
210
			if (StringUtils.isNotBlank(figureHolder.num)) {
211
				String annotationText = "<num>" + figureHolder.num.trim() + "</num>";
212
				Annotation annotation = Annotation.NewInstance(annotationText, AnnotationType.TECHNICAL(), getDefaultLanguage(state));
213
				figureHolderTextData.addAnnotation(annotation);
214
			}
215
			if (StringUtils.isNotBlank(figureHolder.figurePart)) {
216
				String annotationText = "<figurePart>"+ figureHolder.figurePart.trim() + "</figurePart>";
217
				Annotation annotation = Annotation.NewInstance(annotationText,AnnotationType.EDITORIAL(), getDefaultLanguage(state));
218
				figureHolderTextData.addAnnotation(annotation);
219
			}
220
			// if (StringUtils.isNotBlank(figureText)){
221
			// figureHolderTextData.putText(language, figureText);
222
			// }
223
			taxonDescription.addElement(figureHolderTextData);
224
			// }
225
			registerFigureDemand(state, next, figureHolderTextData, figureHolder.ref);
226
		} else {
227
			if (lastDescriptionElement == null) {
228
				String message = "No description element created yet that can be referred by figure. Create new TextData instead";
229
				fireWarningEvent(message, next, 4);
230
				lastDescriptionElement = TextData.NewInstance(figureFeature);
231
				lastDescriptionElement.addPrimaryTaxonomicSource(sourceReference);
232
				taxonDescription.addElement(lastDescriptionElement);
233
			}
234
			registerFigureDemand(state, next, lastDescriptionElement,	figureHolder.ref);
235
		}
236
		return lastDescriptionElement;
237
	}
238

    
239
	private DescriptionElementBase makeFeatureString(MarkupImportState state,XMLEventReader reader, Feature feature,
240
				TaxonDescription taxonDescription, DescriptionElementBase lastDescriptionElement, XMLEvent next, Boolean isFreetext) throws XMLStreamException {
241

    
242
		//for specimen only
243
		if (feature.equals(Feature.SPECIMEN()) || feature.equals(Feature.MATERIALS_EXAMINED())
244
				|| feature.getUuid().equals(MarkupTransformer.uuidWoodSpecimens)){
245

    
246
			List<DescriptionElementBase> specimens = specimenImport.handleMaterialsExamined(state, reader, next, feature, taxonDescription);
247
			for (DescriptionElementBase specimen : specimens){
248
				if (specimen.getInDescription() == null){
249
					taxonDescription.addElement(specimen);
250
				}
251
				lastDescriptionElement = specimen;
252
			}
253
			state.setCurrentCollector(null);
254

    
255
			return lastDescriptionElement;
256
		}else if (feature.equals(Feature.COMMON_NAME()) && (isFreetext == null || !isFreetext)){
257
			List<DescriptionElementBase> commonNames = makeCommonNameString(state, reader, next);
258
			//NOTE: we do also have the old version makeVernacular, which was called from "others" below
259
			for (DescriptionElementBase commonName : commonNames){
260
				taxonDescription.addElement(commonName);
261
				lastDescriptionElement = commonName;
262
			}
263
			return lastDescriptionElement;
264
		}
265
		else{
266

    
267
			//others
268
			Map<String, SubheadingResult> subheadingMap = handleString(state, reader, next, feature);
269
			for (String subheading : subheadingMap.keySet()) {
270
				Feature subheadingFeature = feature;
271
				if (StringUtils.isNotBlank(subheading) && subheadingMap.size() > 1) {
272
					subheadingFeature = makeFeature(subheading, state, next, null);
273
				}
274
				if (feature.equals(Feature.COMMON_NAME()) && (isFreetext == null || !isFreetext)){
275
					//NOTE: see above
276
//					List<DescriptionElementBase> commonNames = makeVernacular(state, subheading, subheadingMap.get(subheading));
277
//					for (DescriptionElementBase commonName : commonNames){
278
//						taxonDescription.addElement(commonName);
279
//						lastDescriptionElement = commonName;
280
//					}
281
				}else {
282
					TextData textData = TextData.NewInstance(subheadingFeature);
283
					SubheadingResult subHeadingResult = subheadingMap.get(subheading);
284
					LanguageString languageString = textData.putText(getDefaultLanguage(state), subHeadingResult.text);
285
					if (isNotEmptyCollection(subHeadingResult.references.getReferences())){
286
					    for (LabeledReference reference : subHeadingResult.references.getReferences()){
287
					        textData.addPrimaryTaxonomicSource(reference.ref, reference.detail);
288
					    }
289
                        textData.addImportSource(null, null, state.getConfig().getSourceReference(), null);
290
					}else{
291
					    textData.addPrimaryTaxonomicSource(state.getConfig().getSourceReference());
292
					}
293
					//intext references
294
				    for (IntextReference intext : subHeadingResult.inlineReferences){
295
				        languageString.addIntextReference(intext);
296
                    }
297
                	taxonDescription.addElement(textData);
298
					lastDescriptionElement = textData;
299
					// TODO how to handle figures when these data are split in
300
					// subheadings
301
				}
302
			}
303
			return lastDescriptionElement;
304
		}
305
	}
306

    
307
	private Feature makeFeature(String classValue, MarkupImportState state, XMLEvent parentEvent, Feature parentFeature) {
308
		UUID uuid;
309
		try {
310
			String featureText = StringUtils.capitalize(classValue);
311
			if (parentFeature != null){
312
				featureText = "<%s>" + featureText;
313
				featureText = String.format(featureText, parentFeature.getTitleCache());
314
				classValue = "<%s>" + classValue;
315
				classValue = String.format(classValue, parentFeature.getTitleCache());
316
			}
317

    
318
			//get existing feature
319
			if (classValue.endsWith(".")){
320
			    classValue = classValue.substring(0, classValue.length() - 1);
321
			}
322
			Feature feature = state.getTransformer().getFeatureByKey(classValue);
323
			if (feature != null) {
324
				return feature;
325
			}
326
			uuid = state.getTransformer().getFeatureUuid(classValue);
327

    
328
			if (uuid == null){
329
				uuid = state.getUnknownFeatureUuid(classValue);
330
			}
331

    
332
			if (uuid == null) {
333
				// TODO
334
				String message = "Uuid is not defined for '%s'";
335
				message = String.format(message, classValue);
336
				if (! message.contains("<")){
337
					//log only top level features
338
					fireWarningEvent(message, parentEvent, 8);
339
				}
340
				uuid = UUID.randomUUID();
341
				state.putUnknownFeatureUuid(classValue, uuid);
342
			}
343

    
344
			// TODO eFlora vocabulary
345
			TermVocabulary<Feature> voc = null;
346
			feature = getFeature(state, uuid, featureText, featureText, classValue, voc);
347
			if (parentFeature != null){
348
				parentFeature.addIncludes(feature);
349
				save(parentFeature, state);
350
			}
351
			save(feature, state);
352

    
353
			if (feature == null) {
354
				throw new NullPointerException(classValue + " not recognized as a feature");
355
			}
356
//			state.putFeatureToCurrentList(feature);
357
			return feature;
358
		} catch (Exception e) {
359
			String message = "Could not create feature for %s: %s";
360
			message = String.format(message, classValue, e.getMessage());
361
			fireWarningEvent(message, parentEvent, 4);
362
			state.putUnknownFeatureUuid(classValue, null);
363
//			e.printStackTrace();
364
			return Feature.UNKNOWN();
365
		}
366
	}
367

    
368
	public class CharOrder{
369
		static final int strlength = 3;
370
		private int order = 1;
371
		private CharOrder parent;
372
		private final List<CharOrder> children = new ArrayList<CharOrder>();
373

    
374
		public CharOrder nextChild(){
375
			CharOrder result = new CharOrder();
376
			if (! children.isEmpty()) {
377
				result.order = children.get(children.size() - 1).order + 1;
378
			}
379
			result.parent = this;
380
			children.add(result);
381
			return result;
382
		}
383

    
384
		public CharOrder next(){
385
			CharOrder result = new CharOrder();
386
			result.order = order + 1;
387
			result.parent = parent;
388
			if (parent != null){
389
				parent.children.add(result);
390
			}
391
			return result;
392
		}
393

    
394
		public String orderString(){
395
			String parentString = parent == null ? "" : parent.orderString();
396
			String result = CdmUtils.concat("-", parentString, StringUtils.leftPad(String.valueOf(order), strlength, '0'));
397
			return result;
398
		}
399

    
400
		@Override
401
        public String toString(){
402
			return orderString();
403
		}
404
	}
405

    
406

    
407
	/**
408
	 * Handle the char or subchar element. As
409
	 * @param state the import state
410
	 * @param reader
411
	 * @param parentEvent
412
	 * @param parentFeature in case of subchars we need to attache the newly created feature to a parent feature, should be <code>null</code>
413
	 * for top level chars.
414
	 * @return List of TextData. Not a single one as the recursive TextData will also be returned
415
	 * @throws XMLStreamException
416
	 */
417
	private List<TextData> handleChar(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent, Feature parentFeature, CharOrder myCharOrder) throws XMLStreamException {
418
		List<TextData> result = new ArrayList<>();
419
		String classValue = getClassOnlyAttribute(parentEvent);
420
		Feature feature = makeFeature(classValue, state, parentEvent, parentFeature);
421
		if(parentFeature == null){
422
			state.putFeatureToCharSorterList(feature);
423
		}else{
424
			FeatureSorterInfo parentInfo = state.getLatestCharFeatureSorterInfo();
425
//			if (! parentInfo.getUuid().equals(parentFeature.getUuid())){
426
//				String message = "The parent char feature is not the same as the latest feature. This is the case for char hierarchies with > 2 levels, which is not yet handled by the import";
427
//				fireWarningEvent(message, parentEvent, 6);
428
//			}else{
429
				state.getLatestCharFeatureSorterInfo().addSubFeature(new FeatureSorterInfo(feature));
430
//			}
431
		}
432

    
433
		TextData textData = TextData.NewInstance(feature);
434
		textData.addPrimaryTaxonomicSource(state.getConfig().getSourceReference());
435
		result.add(textData);
436

    
437
		AnnotationType annType = getAnnotationType(state, MarkupTransformer.uuidOriginalOrder, "Original order", "Order in original treatment", null, AnnotationType.TECHNICAL().getVocabulary());
438
		textData.addAnnotation(Annotation.NewInstance(myCharOrder.orderString(), annType, Language.ENGLISH()));
439

    
440
		boolean isTextMode = true;
441
		String text = "";
442
		while (reader.hasNext()) {
443
			XMLEvent next = readNoWhitespace(reader);
444
			if (isMyEndingElement(next, parentEvent)) {
445
				text = text.trim();
446
				textData.putText(getDefaultLanguage(state), text);
447
				return result;
448
			} else if (isStartingElement(next, FIGURE_REF)) {
449
				//TODO
450
				handleNotYetImplementedElement(next);
451
			} else if (isStartingElement(next, FOOTNOTE_REF)) {
452
				//TODO
453
				handleNotYetImplementedElement(next);
454
			} else if (isStartingElement(next, BR)) {
455
				text += "<br/>";
456
				isTextMode = false;
457
			} else if (isEndingElement(next, BR)) {
458
				isTextMode = true;
459
			} else if (isHtml(next)) {
460
				text += getXmlTag(next);
461
			} else if (next.isStartElement()) {
462
				if (isStartingElement(next, ANNOTATION)) {
463
					handleNotYetImplementedElement(next); //TODO test handleSimpleAnnotation
464
				} else if (isStartingElement(next, ITALICS)) {
465
					handleNotYetImplementedElement(next);
466
				} else if (isStartingElement(next, BOLD)) {
467
					handleNotYetImplementedElement(next);
468
				} else if (isStartingElement(next, FIGURE)) {
469
					handleFigure(state, reader, next, specimenImport, nomenclatureImport);
470
				} else if (isStartingElement(next, SUB_CHAR)) {
471
					List<TextData> subTextData = handleChar(state, reader, next, feature, myCharOrder.nextChild());
472
					result.addAll(subTextData);
473
				} else if (isStartingElement(next, FOOTNOTE)) {
474
					FootnoteDataHolder footnote = handleFootnote(state, reader,	next, specimenImport, nomenclatureImport);
475
					if (footnote.isRef()) {
476
						String message = "Ref footnote not implemented here";
477
						fireWarningEvent(message, next, 4);
478
					} else {
479
						registerGivenFootnote(state, footnote);
480
					}
481
				} else {
482
					handleUnexpectedStartElement(next.asStartElement());
483
				}
484
			} else if (next.isCharacters()) {
485
				if (!isTextMode) {
486
					String message = "String is not in text mode";
487
					fireWarningEvent(message, next, 6);
488
				} else {
489
					text += next.asCharacters().getData();
490
				}
491
			} else {
492
				handleUnexpectedEndElement(next.asEndElement());
493
			}
494
		}
495
		throw new IllegalStateException("RefPart has no closing tag");
496
	}
497

    
498
	private void makeFeatureHeading(MarkupImportState state, XMLEventReader reader, String classValue, Feature feature, XMLEvent next) throws XMLStreamException {
499
		String heading = handleHeading(state, reader, next);
500
		if (StringUtils.isNotBlank(heading)) {
501
			if (!heading.equalsIgnoreCase(classValue)) {
502
				try {
503
					if (!feature.equals(state.getTransformer().getFeatureByKey(heading))) {
504
						UUID headerFeatureUuid = state.getTransformer().getFeatureUuid(heading);
505
						if (!feature.getUuid().equals(headerFeatureUuid)) {
506
							String message = "Feature heading '%s' differs from feature class '%s' and can not be transformed to feature";
507
							message = String.format(message, heading, classValue);
508
							fireWarningEvent(message, next, 1);
509
						}
510
					}
511
				} catch (UndefinedTransformerMethodException e) {
512
					throw new RuntimeException(e);
513
				}
514
			} else {
515
				// do nothing
516
			}
517
		}
518
	}
519

    
520
	private void makeFeatureWriter(MarkupImportState state,XMLEventReader reader, Feature feature, Taxon taxon, XMLEvent next) throws XMLStreamException {
521
		WriterDataHolder writer = handleWriter(state, reader, next);
522
		if (isNotBlank(writer.writer)) {
523
			// TODO
524
			Reference ref = state.getConfig().getSourceReference();
525
			TaxonDescription description = getDefaultTaxonDescription(taxon, false, true, ref);
526
			TextData featurePlaceholder = docImport.getFeaturePlaceholder(state,
527
					description, feature, true);
528
			featurePlaceholder.addAnnotation(writer.annotation);
529
			registerFootnotes(state, featurePlaceholder, writer.footnotes);
530
		} else {
531
			String message = "Writer element is empty";
532
			fireWarningEvent(message, next, 4);
533
		}
534
	}
535

    
536

    
537
	protected String handleHabitat(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
538
		checkNoAttributes(parentEvent);
539
		Taxon taxon = state.getCurrentTaxon();
540
		// TODO which ref to take?
541
		Reference sourceReference = state.getConfig().getSourceReference();
542

    
543

    
544
		boolean isTextMode = true;
545
		String text = "";
546
		while (reader.hasNext()) {
547
			XMLEvent next = readNoWhitespace(reader);
548
			if (isMyEndingElement(next, parentEvent)) {
549
				Feature feature = getFeature(
550
						state,
551
						MarkupTransformer.uuidExtractedHabitat,
552
						"Extracted Habitat",
553
						"An structured habitat that was extracted from a habitat text",
554
						"extr. habit.", null);
555
				TextData habitat = TextData.NewInstance(feature);
556
				habitat.addPrimaryTaxonomicSource(sourceReference);
557
				habitat.putText(getDefaultLanguage(state), text);
558
				TaxonDescription description = getExtractedMarkupMarkedDescription(state, taxon, sourceReference);
559

    
560
				description.addElement(habitat);
561

    
562
				return text;
563
			} else if (isStartingElement(next, ALTITUDE)) {
564
//				OLD: text = text.trim() + getTaggedCData(state, reader, next);
565
			    text += handleAltitude(state, reader, next);
566
			} else if (isStartingElement(next, LIFE_CYCLE_PERIODS)) {
567
				handleNotYetImplementedElement(next);
568
			} else if (next.isCharacters()) {
569
			    if (! isTextMode) {
570
                    String message = "String is not in text mode";
571
                    fireWarningEvent(message, next, 6);
572
                } else {
573
                    text += next.asCharacters().getData();
574
                }
575
	         } else if (isStartingElement(next, BR)) {
576
	                text += "<br/>";
577
	                isTextMode = false;
578
	        } else if (isEndingElement(next, BR)) {
579
	                isTextMode = true;
580
	        } else if (isStartingElement(next, REFERENCES)) {
581
	            handleNotYetImplementedElement(next);
582
	        } else if (isStartingElement(next, FIGURE_REF)) {
583
                handleNotYetImplementedElement(next);
584
            } else {
585
                String type = next.toString();
586
                String location = String.valueOf(next.getLocation().getLineNumber());
587
                System.out.println("MarkupFeature.handleHabitat: Unexpected element in habitat: " + type + ":  " + location);
588
				handleUnexpectedElement(next);
589
			}
590
		}
591
		throw new IllegalStateException("<Habitat> has no closing tag");
592
	}
593

    
594
	/**
595
     * Creates "Extracted factual data" with feature altitude and returns the original text as string
596
     * to be used in parent element.
597
     * @see #handleHabitat(MarkupImportState, XMLEventReader, XMLEvent)
598
	 */
599
	private String handleAltitude(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
600
        checkNoAttributes(parentEvent);
601
        Taxon taxon = state.getCurrentTaxon();
602
        // TODO which ref to take?
603
        Reference sourceReference = state.getConfig().getSourceReference();
604

    
605
        boolean isTextMode = true;
606
        String text = "";
607
        while (reader.hasNext()) {
608
            XMLEvent next = readNoWhitespace(reader);
609
            if (isMyEndingElement(next, parentEvent)) {
610
                Feature feature = getFeature(
611
                        state,
612
                        MarkupTransformer.uuidExtractedAltitude,
613
                        "Extracted Altitude",
614
                        "An altitude that was extracted from a habitat text",
615
                        "extr. alt.", null);
616
                //TODO try to make quantitative data
617
                TextData altitude = TextData.NewInstance(feature);
618
                altitude.putText(getDefaultLanguage(state), text);
619
                altitude.addPrimaryTaxonomicSource(sourceReference);
620
                TaxonDescription description = getExtractedMarkupMarkedDescription(state, taxon, sourceReference);
621

    
622
                description.addElement(altitude);
623

    
624
                return text;
625
            } else if (next.isCharacters()) {
626
                if (! isTextMode) {
627
                    String message = "String is not in text mode";
628
                    fireWarningEvent(message, next, 6);
629
                } else {
630
                    text += next.asCharacters().getData();
631
                }
632
            } else if (isStartingElement(next, BR)) {
633
                    text += "<br/>";
634
                    isTextMode = false;
635
            } else if (isEndingElement(next, BR)) {
636
                    isTextMode = true;
637
            } else {
638
                String type = next.toString();
639
                String location = String.valueOf(next.getLocation().getLineNumber());
640
                System.out.println("MarkupFeatureImport.handleAltitude: Unexpected element in habitat: " + type + ":  " + location);
641
                handleUnexpectedElement(next);
642
            }
643
        }
644
        throw new IllegalStateException("<Habitat> has no closing tag");
645
    }
646

    
647
	private FigureDataHolder handleFigureRef(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent)
648
			throws XMLStreamException {
649
		FigureDataHolder result = new FigureDataHolder();
650
		Map<String, Attribute> attributes = getAttributes(parentEvent);
651
		result.ref = getAndRemoveAttributeValue(attributes, REF);
652
		checkNoAttributes(attributes, parentEvent);
653

    
654
		// text is not handled, needed only for debugging purposes
655
		String text = "";
656
		while (reader.hasNext()) {
657
			XMLEvent next = readNoWhitespace(reader);
658
			if (isMyEndingElement(next, parentEvent)) {
659
				return result;
660
			} else if (isStartingElement(next, NUM)) {
661
				String num = getCData(state, reader, next);
662
				result.num = num; // num is not handled during import
663
			} else if (isStartingElement(next, FIGURE_PART)) {
664
				result.figurePart = getCData(state, reader, next);
665
			} else if (next.isCharacters()) {
666
				text += next.asCharacters().getData();
667
			} else {
668
				fireUnexpectedEvent(next, 0);
669
			}
670
		}
671
		throw new IllegalStateException("<figureRef> has no end tag");
672
	}
673

    
674

    
675
	private void registerFigureDemand(MarkupImportState state, XMLEvent next, AnnotatableEntity entity, String figureRef) {
676
		Media existingFigure = state.getFigure(figureRef);
677
		if (existingFigure != null) {
678
			attachFigure(state, next, entity, existingFigure);
679
		} else {
680
			Set<AnnotatableEntity> demands = state.getFigureDemands(figureRef);
681
			if (demands == null) {
682
				demands = new HashSet<AnnotatableEntity>();
683
				state.putFigureDemands(figureRef, demands);
684
			}
685
			demands.add(entity);
686
		}
687
	}
688

    
689
	private List<DescriptionElementBase> makeCommonNameString(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException{
690

    
691
		List<DescriptionElementBase> result = new ArrayList<>();
692

    
693
		checkNoAttributes(parentEvent);
694

    
695
		while (reader.hasNext()) {
696
			XMLEvent next = readNoWhitespace(reader);
697
			if (isMyEndingElement(next, parentEvent)) {
698
				if (result.isEmpty()){
699
					fireWarningEvent("Common name was not created", next, 4);
700
				}
701
				return result;
702
			} else if (isStartingElement(next, VERNACULAR_NAMES)) {
703
				result = makeVernacularNames(state, reader, next);
704
			} else if (isStartingElement(next, SUB_HEADING)) {
705
				String subheading = getCData(state, reader, next);
706
				if (! subheading.matches("(Nom(s)? vernaculaire(s)?\\:|Vern.)")){
707
					fireWarningEvent("Subheading for vernacular name not recognized: " + subheading, next, 4);
708
				}
709
			} else if (next.isCharacters()) {
710
				String chars = next.asCharacters().toString().trim();
711
				if (chars.equals(".")){
712
					//do nothing
713
				}else{
714
					fireWarningEvent("Character not handled in vernacular name: " + chars, next, 4);
715
				}
716
			} else if (isStartingElement(next, REFERENCES)) {
717
			    handleNotYetImplementedElement(next);
718
            }else {
719
				handleUnexpectedElement(next);
720
			}
721
		}
722
		throw new IllegalStateException("closing tag is missing");
723
	}
724

    
725
	private List<DescriptionElementBase> makeVernacularNames(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException{
726

    
727
	    List<DescriptionElementBase> result = new ArrayList<>();
728
		checkNoAttributes(parentEvent);
729

    
730
		while (reader.hasNext()) {
731
			XMLEvent next = readNoWhitespace(reader);
732
			if (isMyEndingElement(next, parentEvent)) {
733
				state.removeCurrentAreas();
734
				return result;
735
			} else if (isStartingElement(next, VERNACULAR_NAME)) {
736
				List<CommonTaxonName> names = makeSingleVernacularName(state, reader, next);
737
				result.addAll(names);
738
			} else if (isStartingElement(next, SUB_HEADING)) {
739
				makeVernacularNamesSubHeading(state, reader, next);
740
			} else {
741
				handleUnexpectedElement(next);
742
			}
743
		}
744
		throw new IllegalStateException("closing tag is missing");
745
	}
746

    
747
	private void makeVernacularNamesSubHeading(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
748
		checkNoAttributes(parentEvent);
749

    
750
		String text = "";
751
		while (reader.hasNext()) {
752
			XMLEvent next = readNoWhitespace(reader);
753
			if (isMyEndingElement(next, parentEvent)) {
754
				if (StringUtils.isNotBlank(text)){
755
					NamedArea area = getCommonNameArea(text);
756
					if (area != null){
757
						state.removeCurrentAreas();
758
						state.addCurrentArea(area);
759
					}else{
760
						fireWarningEvent("Vernacular subheading not recognized", next, 8);
761
					}
762
				}
763

    
764
				return ;
765
			} else if (next.isCharacters()) {
766
				text += next.asCharacters().getData();
767
			} else {
768
				handleUnexpectedElement(next);
769
			}
770
		}
771
		throw new IllegalStateException("closing tag is missing");
772
	}
773

    
774
	private NamedArea getCommonNameArea(String text) {
775
		if (text.endsWith(":")){
776
			text = text.substring(0, text.length()-1);
777
		}
778

    
779
		// for now we do it hardcoded
780
		if (text.equalsIgnoreCase("Guyana")){
781
			return Country.GUYANAREPUBLICOF();
782
		}else if (text.equalsIgnoreCase("Suriname")){
783
			return Country.SURINAMEREPUBLICOF();
784
		}else if (text.equalsIgnoreCase("French Guiana")){
785
			return Country.FRENCHGUIANA();
786
		}
787
		return null;
788
	}
789

    
790
	private List<CommonTaxonName> makeSingleVernacularName(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException{
791

    
792
	    checkNoAttributes(parentEvent);
793
		List<CommonTaxonName> result = new ArrayList<>();
794

    
795
		Language language = state.getDefaultLanguage();
796
		while (reader.hasNext()) {
797
			XMLEvent next = readNoWhitespace(reader);
798
			if (isMyEndingElement(next, parentEvent)) {
799
				for (CommonTaxonName commonName : result){
800
					commonName.setLanguage(language);
801
				}
802
//				if (isNotBlank(name)){
803
//					result.setName(name);
804
//				}else{
805
//					fireWarningEvent("No name string for common name", parentEvent, 4);
806
//				}
807

    
808
				return result;
809
			} else if (isStartingElement(next, NAME)) {
810
				//TODO test
811
				CommonTaxonName name = handleVernacularNameName(state, reader, next);
812
				if (name != null){
813
					result.add(name);
814
				}
815
			} else if (isStartingElement(next, LOCAL_LANGUAGE)) {
816
				Language localLanguage = handleLocalLanguage(state, reader, next);
817
				if (localLanguage != null){
818
					language = localLanguage;
819
				}
820
			} else if (isStartingElement(next, TRANSLATION)) {
821
				//TODO
822
				handleNotYetImplementedElement(next);
823
			} else if (isStartingElement(next, LOCALITY)) {
824
				//TODO
825
				handleNotYetImplementedElement(next);
826
			} else if (isStartingElement(next, ANNOTATION)){
827
				//TODO
828
				handleNotYetImplementedElement(next);
829
			} else if (isStartingElement(next, FOOTNOTE_REF)) {
830
				//TODO
831
				handleNotYetImplementedElement(next);
832
			} else if (next.isCharacters()) {
833
				String chars = next.asCharacters().toString().trim();
834
				if (chars.equals("(") || chars.equals(")") || chars.equals(",")){
835
					//do nothing
836
				}else{
837
					fireWarningEvent("Character not handled in vernacular name: " + chars, next, 4);
838
				}
839
			} else {
840
				handleUnexpectedElement(next);
841
			}
842
		}
843
		throw new IllegalStateException("closing tag is missing");
844
	}
845

    
846
	private CommonTaxonName handleVernacularNameName(MarkupImportState state, XMLEventReader reader,
847
				XMLEvent parentEvent) throws XMLStreamException {
848
		//attributes
849
		Map<String, Attribute> attributes = getAttributes(parentEvent);
850
		this.checkAndRemoveAttributeValue(attributes, CLASS, "vernacular");
851
		this.checkNoAttributes(attributes, parentEvent);
852

    
853
		//
854
		String text = getCData(state, reader, parentEvent, false);
855
		CommonTaxonName name = CommonTaxonName.NewInstance(text, null);
856
		if (! state.getCurrentAreas().isEmpty()){
857
			if (state.getCurrentAreas().size() > 1){
858
				fireWarningEvent("Multiple areas for common name not yet covered by CDM", parentEvent , 8);
859
			}else{
860
				name.setArea(state.getCurrentAreas().iterator().next());
861
			}
862
		}
863
		return name;
864
	}
865

    
866
	private Language handleLocalLanguage(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
867
		//attributes
868
		Map<String, Attribute> attributes = getAttributes(parentEvent);
869
		boolean doubtful = getAndRemoveBooleanAttributeValue(parentEvent, attributes, DOUBTFUL, false);
870
		boolean unknown = getAndRemoveBooleanAttributeValue(parentEvent, attributes, UNKNOWN, false);
871
		this.checkNoAttributes(attributes, parentEvent);
872

    
873
		if (doubtful == true){
874
			fireWarningEvent("Doubtful not yet implemented for local language", parentEvent, 2);
875
		}
876
		if (unknown == true){
877
			fireWarningEvent("Unknown not yet implemented for local language ", parentEvent, 2);
878
		}
879

    
880
		//
881
		String text = getCData(state, reader, parentEvent);
882
		Language lang = makeLanguageByLangStr(state, text);
883
		return lang;
884

    
885
	}
886

    
887
	private List<DescriptionElementBase> makeVernacular(MarkupImportState state, String subheading, String commonNameString) throws XMLStreamException {
888
		List<DescriptionElementBase> result = new ArrayList<>();
889
		Reference sourceReference = state.getConfig().getSourceReference();
890
		String[] splits = commonNameString.split(",");
891
		for (String split : splits){
892
			split = split.trim();
893
			if (! split.matches(".*\\(.*\\)\\.?")){
894
				fireWarningEvent("Common name string '"+split+"' does not match given pattern", state.getReader().peek(), 4);
895
			}
896

    
897
			String name = split.replaceAll("\\(.*\\)", "").replace(".", "").trim();
898
			String languageStr = split.replaceFirst(".*\\(", "").replaceAll("\\)\\.?", "").trim();
899

    
900
			Language language = null;
901
			if (StringUtils.isNotBlank(languageStr)){
902
				language = makeLanguageByLangStr(state, languageStr);
903
			}
904
			DescriptionElementBase commonName;
905
			if (name != null && name.length() < 255 ){
906
				NamedArea area = null;
907
				commonName = CommonTaxonName.NewInstance(name, language, area);
908
				commonName.addPrimaryTaxonomicSource(sourceReference);
909
			}else{
910
				if (language == null){
911
					language = getDefaultLanguage(state);
912
				}
913
				commonName = TextData.NewInstance(Feature.COMMON_NAME(), name, language, null);
914
				commonName.addPrimaryTaxonomicSource(sourceReference);
915
				String warning = "Vernacular feature is >255 size. Therefore it is handled as TextData, not CommonTaxonName: " + name;
916
				fireWarningEvent(warning, state.getReader().peek(), 1);
917
			}
918
			result.add(commonName);
919
		}
920

    
921
		return result;
922
	}
923

    
924
	private Language makeLanguageByLangStr(MarkupImportState state, String languageStr) throws XMLStreamException {
925
		try {
926
			Language language = state.getTransformer().getLanguageByKey(languageStr);
927
			if (language == null){
928
				UUID langUuid = state.getTransformer().getLanguageUuid(languageStr);
929
				TermVocabulary<?> voc = null;
930
				language = getLanguage(state, langUuid, languageStr, languageStr, null, voc);
931
			}
932
			if (language == null){
933
				String warning = "Language " + languageStr + " not recognized by transformer";
934
				fireWarningEvent(warning, state.getReader().peek(), 4);
935
			}
936
			return language;
937
		} catch (UndefinedTransformerMethodException e) {
938
			throw new RuntimeException(e);
939
		}
940
	}
941

    
942
	private String handleHeading(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent)throws XMLStreamException {
943
		checkNoAttributes(parentEvent);
944

    
945
		String text = "";
946
		while (reader.hasNext()) {
947
			XMLEvent next = readNoWhitespace(reader);
948
			if (isMyEndingElement(next, parentEvent)) {
949
				return text;
950
			} else if (next.isStartElement()) {
951
				if (isStartingElement(next, FOOTNOTE)) {
952
					handleNotYetImplementedElement(next);
953
				} else {
954
					handleUnexpectedStartElement(next.asStartElement());
955
				}
956
			} else if (next.isCharacters()) {
957
				text += next.asCharacters().getData();
958
			} else {
959
				handleUnexpectedEndElement(next.asEndElement());
960
			}
961
		}
962
		throw new IllegalStateException("<String> has no closing tag");
963
	}
964

    
965
	private List<Reference> handleReferences(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
966
		// attributes
967
		Map<String, Attribute> attributes = getAttributes(parentEvent);
968
		String bibliography = getAndRemoveAttributeValue(attributes,
969
				BIBLIOGRAPHY);
970
		String serialsAbbreviations = getAndRemoveAttributeValue(attributes,
971
				SERIALS_ABBREVIATIONS);
972
		if (isNotBlank(bibliography) || isNotBlank(serialsAbbreviations)) {
973
			String message = "Attributes not yet implemented for <references>";
974
			fireWarningEvent(message, parentEvent, 4);
975
		}
976

    
977
		List<Reference> result = new ArrayList<>();
978

    
979
		// elements
980
		while (reader.hasNext()) {
981
			XMLEvent next = readNoWhitespace(reader);
982
			if (next.isEndElement()) {
983
				if (isMyEndingElement(next, parentEvent)) {
984
					return result;
985
				} else {
986
					if (isEndingElement(next, HEADING)) {
987
						// NOT YET IMPLEMENTED
988
						popUnimplemented(next.asEndElement());
989
					} else if (isEndingElement(next, WRITER)) {
990
						// NOT YET IMPLEMENTED
991
						popUnimplemented(next.asEndElement());
992
					} else if (isEndingElement(next, FOOTNOTE)) {
993
						// NOT YET IMPLEMENTED
994
						popUnimplemented(next.asEndElement());
995
					} else if (isEndingElement(next, STRING)) {
996
						// NOT YET IMPLEMENTED
997
						popUnimplemented(next.asEndElement());
998
					} else if (isEndingElement(next, REF_NUM)) {
999
						// NOT YET IMPLEMENTED
1000
						popUnimplemented(next.asEndElement());
1001
					} else {
1002
						handleUnexpectedEndElement(next.asEndElement());
1003
					}
1004
				}
1005
			} else if (next.isStartElement()) {
1006
				if (isStartingElement(next, HEADING)) {
1007
					handleNotYetImplementedElement(next);
1008
				} else if (isStartingElement(next, SUB_HEADING)) {
1009
					String subheading = getCData(state, reader, next).trim();
1010
					String excludePattern = "(i?)(References?|Literature):?";
1011
					if (!subheading.matches(excludePattern)) {
1012
						fireNotYetImplementedElement(next.getLocation(), next.asStartElement().getName(), 0);
1013
					}
1014
				} else if (isStartingElement(next, WRITER)) {
1015
					handleNotYetImplementedElement(next);
1016
				} else if (isStartingElement(next, FOOTNOTE)) {
1017
					handleNotYetImplementedElement(next);
1018
				} else if (isStartingElement(next, STRING)) {
1019
					handleNotYetImplementedElement(next);
1020
				} else if (isStartingElement(next, REF_NUM)) {
1021
					handleNotYetImplementedElement(next);
1022
				} else if (isStartingElement(next, REFERENCE)) {
1023
					Reference ref = nomenclatureImport.handleReference(state, reader, next);
1024
					result.add(ref);
1025
				} else {
1026
					handleUnexpectedStartElement(next);
1027
				}
1028
			} else {
1029
				handleUnexpectedElement(next);
1030
			}
1031
		}
1032
		throw new IllegalStateException("<References> has no closing tag");
1033
	}
1034

    
1035

    
1036
	/**
1037
	 * Returns all the included text and tags as string. The result should look
1038
	 * similar to the original xml part.
1039
	 */
1040
	private String getTaggedCData(MarkupImportState state, XMLEventReader reader, XMLEvent parentEvent) throws XMLStreamException {
1041
		checkNoAttributes(parentEvent);
1042

    
1043
		String text = getXmlTag(parentEvent);
1044
		while (reader.hasNext()) {
1045
			XMLEvent next = readNoWhitespace(reader);
1046
			if (isMyEndingElement(next, parentEvent)) {
1047
				text += getXmlTag(next);
1048
				return text;
1049
			} else if (next.isStartElement()) {
1050
				text += getTaggedCData(state, reader, next);
1051
			} else if (next.isEndElement()) {
1052
				//is this needed?
1053
			    text += getTaggedCData(state, reader, next);
1054
			} else if (next.isCharacters()) {
1055
				text += next.asCharacters().getData();
1056
			} else {
1057
				handleUnexpectedEndElement(next.asEndElement());
1058
			}
1059
		}
1060
		throw new IllegalStateException("Some tag has no closing tag");
1061
	}
1062
}
(8-8/19)