Project

General

Profile

Download (59.6 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

    
10
package eu.etaxonomy.cdm.strategy.parser;
11

    
12
import java.util.HashSet;
13
import java.util.Set;
14
import java.util.regex.Matcher;
15
import java.util.regex.Pattern;
16

    
17
import org.apache.commons.lang.StringUtils;
18
import org.apache.log4j.Logger;
19
import org.joda.time.DateTimeFieldType;
20
import org.joda.time.Partial;
21

    
22
import eu.etaxonomy.cdm.common.CdmUtils;
23
import eu.etaxonomy.cdm.common.UTF8;
24
import eu.etaxonomy.cdm.model.agent.Person;
25
import eu.etaxonomy.cdm.model.agent.Team;
26
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
27
import eu.etaxonomy.cdm.model.common.CdmBase;
28
import eu.etaxonomy.cdm.model.common.IParsable;
29
import eu.etaxonomy.cdm.model.common.VerbatimTimePeriod;
30
import eu.etaxonomy.cdm.model.name.HybridRelationship;
31
import eu.etaxonomy.cdm.model.name.HybridRelationshipType;
32
import eu.etaxonomy.cdm.model.name.IBotanicalName;
33
import eu.etaxonomy.cdm.model.name.ICultivarPlantName;
34
import eu.etaxonomy.cdm.model.name.INonViralName;
35
import eu.etaxonomy.cdm.model.name.IZoologicalName;
36
import eu.etaxonomy.cdm.model.name.NomenclaturalCode;
37
import eu.etaxonomy.cdm.model.name.NomenclaturalStatus;
38
import eu.etaxonomy.cdm.model.name.NomenclaturalStatusType;
39
import eu.etaxonomy.cdm.model.name.Rank;
40
import eu.etaxonomy.cdm.model.name.TaxonName;
41
import eu.etaxonomy.cdm.model.name.TaxonNameFactory;
42
import eu.etaxonomy.cdm.model.reference.IBook;
43
import eu.etaxonomy.cdm.model.reference.IBookSection;
44
import eu.etaxonomy.cdm.model.reference.INomenclaturalReference;
45
import eu.etaxonomy.cdm.model.reference.IVolumeReference;
46
import eu.etaxonomy.cdm.model.reference.Reference;
47
import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
48
import eu.etaxonomy.cdm.model.reference.ReferenceType;
49
import eu.etaxonomy.cdm.strategy.exceptions.StringNotParsableException;
50
import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
51

    
52

    
53
/**
54
 * Parser for {@link TaxonName}s of type NonViralName and below.
55
 *
56
 * @author a.mueller
57
 */
58
public class NonViralNameParserImpl
59
            extends NonViralNameParserImplRegExBase
60
            implements INonViralNameParser<INonViralName> {
61

    
62
    private static final Logger logger = Logger.getLogger(NonViralNameParserImpl.class);
63

    
64
	// good intro: http://java.sun.com/docs/books/tutorial/essential/regex/index.html
65

    
66
	final static boolean MAKE_EMPTY = true;
67
	final static boolean MAKE_NOT_EMPTY = false;
68

    
69
	private final boolean authorIsAlwaysTeam = false;
70
	private boolean removeSpaceAfterDot = false;
71

    
72
    public static NonViralNameParserImpl NewInstance(){
73
		return new NonViralNameParserImpl();
74
	}
75

    
76
	@Override
77
    public INonViralName parseSimpleName(String simpleName){
78
		return parseSimpleName(simpleName, null, null);
79
	}
80

    
81
	@Override
82
    public INonViralName parseSimpleName(String simpleName, NomenclaturalCode code, Rank rank){
83
		//"parseSimpleName() not yet implemented. Uses parseFullName() instead");
84
		return parseFullName(simpleName, code, rank);
85
	}
86

    
87
	public void parseSimpleName(INonViralName nameToBeFilled, String simpleNameString, Rank rank, boolean makeEmpty){
88
		//"parseSimpleName() not yet implemented. Uses parseFullName() instead");
89
		parseFullName(nameToBeFilled, simpleNameString, rank, makeEmpty);
90
	}
91

    
92
	public INonViralName getNonViralNameInstance(String fullString, NomenclaturalCode code){
93
		return getNonViralNameInstance(fullString, code, null);
94
	}
95

    
96
	public INonViralName getNonViralNameInstance(String fullString, NomenclaturalCode code, Rank rank){
97
		INonViralName result = null;
98
		if(code ==null) {
99
			boolean isBotanicalName = anyBotanicFullNamePattern.matcher(fullString).find();
100
			boolean isZoologicalName = anyZooFullNamePattern.matcher(fullString).find();;
101
			boolean isBacteriologicalName = false;
102
			boolean isCultivatedPlantName = false;
103
			if ( (isBotanicalName || isCultivatedPlantName) && ! isZoologicalName && !isBacteriologicalName){
104
				if (isBotanicalName){
105
					result = TaxonNameFactory.NewBotanicalInstance(rank);
106
				}else{
107
					result = TaxonNameFactory.NewCultivarInstance(rank);
108
				}
109
			}else if ( isZoologicalName /*&& ! isBotanicalName*/ && !isBacteriologicalName && !isCultivatedPlantName){
110
				result = TaxonNameFactory.NewZoologicalInstance(rank);
111
			}else if ( isZoologicalName && ! isBotanicalName && !isBacteriologicalName && !isCultivatedPlantName){
112
				result = TaxonNameFactory.NewBacterialInstance(rank);
113
			}else {
114
				result =  TaxonNameFactory.NewNonViralInstance(rank);
115
			}
116
		} else {
117
			switch (code) {
118
			case ICNAFP:
119
				result = TaxonNameFactory.NewBotanicalInstance(rank);
120
				break;
121
			case ICZN:
122
				result = TaxonNameFactory.NewZoologicalInstance(rank);
123
				break;
124
			case ICNCP:
125
				logger.warn("ICNCP parsing not yet implemented");
126
				result = TaxonNameFactory.NewCultivarInstance(rank);
127
				break;
128
			case ICNB:
129
				logger.warn("ICNB not yet implemented");
130
				result = TaxonNameFactory.NewBacterialInstance(rank);
131
				break;
132
			case ICVCN:
133
				logger.error("Viral name is not a NonViralName !!");
134
				break;
135
			default:
136
				// FIXME Unreachable code
137
				logger.error("Unknown Nomenclatural Code !!");
138
			}
139
		}
140
		return result;
141
	}
142

    
143
	@Override
144
    public TaxonName parseReferencedName(String fullReferenceString) {
145
		return parseReferencedName(fullReferenceString, null, null);
146
	}
147

    
148
	@Override
149
    public TaxonName parseReferencedName(String fullReferenceString, NomenclaturalCode nomCode, Rank rank) {
150
		if (fullReferenceString == null){
151
			return null;
152
		}else{
153
		    INonViralName result = getNonViralNameInstance(fullReferenceString, nomCode, rank);
154
			parseReferencedName(result, fullReferenceString, rank, MAKE_EMPTY);
155
			return TaxonName.castAndDeproxy(result);
156
		}
157
	}
158

    
159
	private String standardize(INonViralName nameToBeFilled, String fullReferenceString, boolean makeEmpty){
160
		//Check null and standardize
161
		if (fullReferenceString == null){
162
			//return null;
163
			return null;
164
		}
165
		if (makeEmpty){
166
			makeEmpty(nameToBeFilled);
167
		}
168
		fullReferenceString = fullReferenceString.replaceAll(oWs , " ");
169
		fullReferenceString = fullReferenceString.trim();
170
		if ("".equals(fullReferenceString)){
171
			fullReferenceString = null;
172
		}
173
		return fullReferenceString;
174
	}
175

    
176
	/**
177
	 * Returns the regEx to be used for the full-name depending on the code
178
	 * @param nameToBeFilled
179
	 * @return
180
	 */
181
	private String getCodeSpecificFullNameRegEx(INonViralName nameToBeFilledOrig){
182
	    INonViralName nameToBeFilled = CdmBase.deproxy(nameToBeFilledOrig);
183
		if (nameToBeFilled.isZoological()){
184
			return anyZooFullName;
185
		}else if (nameToBeFilled.isBotanical()) {
186
			return anyBotanicFullName;
187
		}else if (nameToBeFilled.isNonViral()) {
188
			return anyBotanicFullName;  //TODO ?
189
		}else{
190
			logger.warn("nameToBeFilled class not supported ("+nameToBeFilled.getClass()+")");
191
			return null;
192
		}
193
	}
194

    
195
	/**
196
	 * Returns the regEx to be used for the fsimple-name depending on the code
197
	 * @param nameToBeFilled
198
	 * @return
199
	 */
200
	private String getCodeSpecificSimpleNameRegEx(INonViralName nameToBeFilled){
201
		nameToBeFilled = CdmBase.deproxy(nameToBeFilled);
202

    
203
		if (nameToBeFilled.isZoological()){
204
			return anyZooName;
205
		}else if (nameToBeFilled.isBotanical()) {
206
		    return anyBotanicName;
207
		}else if (nameToBeFilled.isNonViral()){
208
			return anyZooName;  //TODO ?
209
		}else{
210
			logger.warn("nameToBeFilled class not supported ("+nameToBeFilled.getClass()+")");
211
			return null;
212
		}
213
	}
214

    
215
	private Matcher getMatcher(String regEx, String matchString){
216
		Pattern pattern = Pattern.compile(regEx);
217
		Matcher matcher = pattern.matcher(matchString);
218
		return matcher;
219
	}
220

    
221
	@Override
222
    public void parseReferencedName(INonViralName nameToBeFilled, String fullReferenceStringOrig, Rank rank, boolean makeEmpty) {
223
		//standardize
224
		String fullReferenceString = standardize(nameToBeFilled, fullReferenceStringOrig, makeEmpty);
225
		if (fullReferenceString == null){
226
			return;
227
		}
228
		// happens already in standardize(...)
229
//		makeProblemEmpty(nameToBeFilled);
230

    
231
		//make nomenclatural status and replace it by empty string
232
	    fullReferenceString = parseNomStatus(fullReferenceString, nameToBeFilled, makeEmpty);
233
	    nameToBeFilled.setProblemEnds(fullReferenceString.length());
234

    
235
	    //get full name reg
236
		String localFullNameRegEx = getCodeSpecificFullNameRegEx(nameToBeFilled);
237
		//get full name reg
238
		String localSimpleNameRegEx = getCodeSpecificSimpleNameRegEx(nameToBeFilled);
239

    
240
		//separate name and reference part
241
		String nameAndRefSeparatorRegEx = "(^" + localFullNameRegEx + ")("+ referenceSeperator + ")";
242
		Matcher nameAndRefSeparatorMatcher = getMatcher (nameAndRefSeparatorRegEx, fullReferenceString);
243

    
244
		Matcher onlyNameMatcher = getMatcher (localFullNameRegEx, fullReferenceString);
245
		Matcher hybridMatcher = hybridFormulaPattern.matcher(fullReferenceString);
246
		Matcher onlySimpleNameMatcher = getMatcher (localSimpleNameRegEx, fullReferenceString);
247

    
248
		if (onlyNameMatcher.matches()){
249
			makeEmpty = false;
250
			parseFullName(nameToBeFilled, fullReferenceString, rank, makeEmpty);
251
		} else if (nameAndRefSeparatorMatcher.find()){
252
			makeNameWithReference(nameToBeFilled, fullReferenceString, nameAndRefSeparatorMatcher, rank, makeEmpty);
253
		}else if (hybridMatcher.matches() ){
254
		    //I do not remember why we need makeEmpty = false for onlyNameMatcher,
255
		    //but for hybridMatcher we need to remove old Hybrid Relationships if necessary, therefore
256
		    //I removed it from here
257
            parseFullName(nameToBeFilled, fullReferenceString, rank, makeEmpty);
258
        }else if (onlySimpleNameMatcher.matches()){
259
			makeEmpty = false;
260
			parseFullName(nameToBeFilled, fullReferenceString, rank, makeEmpty);	//simpleName not yet implemented
261
		}else{
262
			makeNoFullRefMatch(nameToBeFilled, fullReferenceString, rank);
263
		}
264
		//problem handling. Start and end solved in subroutines
265
		if (! nameToBeFilled.hasProblem()){
266
			makeProblemEmpty(nameToBeFilled);
267
		}
268
	}
269

    
270
	private void makeProblemEmpty(IParsable parsable){
271
		boolean hasCheckRank = parsable.hasProblem(ParserProblem.CheckRank);
272
		parsable.setParsingProblem(0);
273
		if (hasCheckRank){
274
			parsable.addParsingProblem(ParserProblem.CheckRank);
275
		}
276
		parsable.setProblemStarts(-1);
277
		parsable.setProblemEnds(-1);
278
	}
279

    
280
	private void makeNoFullRefMatch(INonViralName nameToBeFilled, String fullReferenceString, Rank rank){
281
	    //try to parse first part as name, but keep in mind full string is not parsable
282
		int start = 0;
283

    
284
		String localFullName = getCodeSpecificFullNameRegEx(nameToBeFilled);
285
		Matcher fullNameMatcher = getMatcher (pStart + localFullName, fullReferenceString);
286
		if (fullNameMatcher.find()){
287
			String fullNameString = fullNameMatcher.group(0);
288
			nameToBeFilled.setProtectedNameCache(false);
289
			parseFullName(nameToBeFilled, fullNameString, rank, false);
290
			String sure = nameToBeFilled.getNameCache();
291
			start = sure.length();
292
		}
293

    
294
//		String localSimpleName = getLocalSimpleName(nameToBeFilled);
295
//		Matcher simpleNameMatcher = getMatcher (start + localSimpleName, fullReferenceString);
296
//		if (simpleNameMatcher.find()){
297
//			String simpleNameString = simpleNameMatcher.group(0);
298
//			parseFullName(nameToBeFilled, simpleNameString, rank, false);
299
//			start = simpleNameString.length();
300
//		}
301

    
302
		//don't parse if name can't be separated
303
		nameToBeFilled.addParsingProblem(ParserProblem.NameReferenceSeparation);
304
		nameToBeFilled.setTitleCache(fullReferenceString, true);
305
		nameToBeFilled.setFullTitleCache(fullReferenceString, true);
306
		// FIXME Quick fix, otherwise search would not deliver results for unparsable names
307
		nameToBeFilled.setNameCache(fullReferenceString, true);
308
		// END
309
		nameToBeFilled.setProblemStarts(start);
310
		nameToBeFilled.setProblemEnds(fullReferenceString.length());
311
		logger.info("no applicable parsing rule could be found for \"" + fullReferenceString + "\"");
312
	}
313

    
314
	private void makeNameWithReference(INonViralName nameToBeFilled,
315
			String fullReferenceString,
316
			Matcher nameAndRefSeparatorMatcher,
317
			Rank rank,
318
			boolean makeEmpty){
319

    
320
		String nameAndSeparator = nameAndRefSeparatorMatcher.group(0);
321
	    String name = nameAndRefSeparatorMatcher.group(1);
322
	    String referenceString = fullReferenceString.substring(nameAndRefSeparatorMatcher.end());
323

    
324
	    // is reference an in ref?
325
	    String separator = nameAndSeparator.substring(name.length());
326
		boolean isInReference = separator.matches(inReferenceSeparator);
327

    
328
	    //parse subparts
329

    
330
		int oldProblemEnds = nameToBeFilled.getProblemEnds();
331
		parseFullName(nameToBeFilled, name, rank, makeEmpty);
332
	    nameToBeFilled.setProblemEnds(oldProblemEnds);
333

    
334
		//zoological new combinations should not have a nom. reference to be parsed
335
	    if (nameToBeFilled.isZoological()){
336
			IZoologicalName zooName = (IZoologicalName)CdmBase.deproxy(nameToBeFilled);
337
			//is name new combination?
338
			if (zooName.getBasionymAuthorship() != null || zooName.getOriginalPublicationYear() != null){
339
				ParserProblem parserProblem = ParserProblem.NewCombinationHasPublication;
340
				zooName.addParsingProblem(parserProblem);
341
				nameToBeFilled.setProblemStarts((nameToBeFilled.getProblemStarts()> -1) ? nameToBeFilled.getProblemStarts(): name.length());
342
				nameToBeFilled.setProblemEnds(Math.max(fullReferenceString.length(), nameToBeFilled.getProblemEnds()));
343
			}
344
		}
345

    
346
	    parseReference(nameToBeFilled, referenceString, isInReference);
347
	    INomenclaturalReference ref = nameToBeFilled.getNomenclaturalReference();
348

    
349
	    //problem start
350
	    int start = nameToBeFilled.getProblemStarts();
351
	    int nameLength = name.length();
352
	    int nameAndSeparatorLength = nameAndSeparator.length();
353
	    int fullRefLength = nameToBeFilled.getFullTitleCache().length();
354

    
355
	    if (nameToBeFilled.isProtectedTitleCache() || nameToBeFilled.getParsingProblems().contains(ParserProblem.CheckRank)){
356
	    	start = Math.max(0, start);
357
		}else{
358
			if (ref != null && ref.getParsingProblem()!=0){
359
				start = Math.max(nameAndSeparatorLength, start);
360
		    	//TODO search within ref
361
			}
362
		}
363

    
364
	    //end
365
	    int end = nameToBeFilled.getProblemEnds();
366

    
367
	    if (ref != null && ref.getParsingProblem()!=0){
368
	    	end = Math.min(nameAndSeparatorLength + ref.getProblemEnds(), end);
369
	    }else{
370
	    	if (nameToBeFilled.isProtectedTitleCache() ){
371
	    		end = Math.min(end, nameAndSeparatorLength);
372
	    		//TODO search within name
373
			}
374
	    }
375
	    nameToBeFilled.setProblemStarts(start);
376
	    nameToBeFilled.setProblemEnds(end);
377

    
378
	    //delegate has problem to name
379
	    if (ref != null && ref.getParsingProblem()!=0){
380
	    	nameToBeFilled.addParsingProblems(ref.getParsingProblem());
381
	    }
382

    
383
	    Reference nomRef;
384
		if ( (nomRef = nameToBeFilled.getNomenclaturalReference()) != null ){
385
			nomRef.setAuthorship(nameToBeFilled.getCombinationAuthorship());
386
		}
387
	}
388

    
389
	//TODO make it an Array of status
390
	/**
391
	 * Extracts a {@link NomenclaturalStatus} from the reference String and adds it to the @link {@link TaxonName}.
392
	 * The nomenclatural status part ist deleted from the reference String.
393
	 * @return  String the new (shortend) reference String
394
	 */
395
	public String parseNomStatus(String fullString, INonViralName nameToBeFilled, boolean makeEmpty) {
396
		Set<NomenclaturalStatusType> existingStatusTypeSet = new HashSet<NomenclaturalStatusType>();
397
		Set<NomenclaturalStatusType> newStatusTypeSet = new HashSet<NomenclaturalStatusType>();
398
		for (NomenclaturalStatus existingStatus : nameToBeFilled.getStatus()){
399
			existingStatusTypeSet.add(existingStatus.getType());
400
		}
401

    
402
		String statusString;
403
		Pattern hasStatusPattern = Pattern.compile("(" + pNomStatusPhrase + ")");
404
		Matcher hasStatusMatcher = hasStatusPattern.matcher(fullString);
405

    
406
		if (hasStatusMatcher.find()) {
407
			String statusPhrase = hasStatusMatcher.group(0);
408

    
409
			Pattern statusPattern = Pattern.compile(pNomStatus);
410
			Matcher statusMatcher = statusPattern.matcher(statusPhrase);
411
			statusMatcher.find();
412
			statusString = statusMatcher.group(0);
413
			try {
414
			    TaxonName nameToBeFilledCasted =  TaxonName.castAndDeproxy(nameToBeFilled);
415
				NomenclaturalStatusType nomStatusType = NomenclaturalStatusType.getNomenclaturalStatusTypeByAbbreviation(statusString, nameToBeFilledCasted);
416
				if (! existingStatusTypeSet.contains(nomStatusType)){
417
					NomenclaturalStatus nomStatus = NomenclaturalStatus.NewInstance(nomStatusType);
418
					nameToBeFilled.addStatus(nomStatus);
419
				}
420
				newStatusTypeSet.add(nomStatusType);
421
				fullString = fullString.replace(statusPhrase, "");
422
			} catch (UnknownCdmTypeException e) {
423
				//Do nothing
424
			}
425
		}
426
		//remove not existing nom status
427
		if (makeEmpty){
428
			Set<NomenclaturalStatus> tmpStatus = new HashSet<NomenclaturalStatus>();
429
			tmpStatus.addAll(nameToBeFilled.getStatus());
430
			for (NomenclaturalStatus status : tmpStatus){
431
				if (! newStatusTypeSet.contains(status.getType())){
432
					nameToBeFilled.removeStatus(status);
433
				}
434
			}
435
		}
436

    
437
		return fullString;
438
	}
439

    
440

    
441
	private void parseReference(INonViralName nameToBeFilled, String strReference, boolean isInReference){
442

    
443
		INomenclaturalReference ref;
444
		String originalStrReference = strReference;
445

    
446
		//End (just delete end (e.g. '.', may be ambigous for yearPhrase, but no real information gets lost
447
		Matcher endMatcher = getMatcher(referenceEnd + end, strReference);
448
		if (endMatcher.find()){
449
			String endPart = endMatcher.group(0);
450
			strReference = strReference.substring(0, strReference.length() - endPart.length());
451
		}
452

    
453
//		String pDetailYear = ".*" + detailSeparator + detail + fWs + yearSeperator + fWs + yearPhrase + fWs + end;
454
//		Matcher detailYearMatcher = getMatcher(pDetailYear, strReference);
455

    
456
		String strReferenceWithYear = strReference;
457
		//year
458
		String yearPart = null;
459
		String pYearPhrase = yearSeperator + fWs + yearPhrase + fWs + end;
460
		Matcher yearPhraseMatcher = getMatcher(pYearPhrase, strReference);
461
		if (yearPhraseMatcher.find()){
462
			yearPart = yearPhraseMatcher.group(0);
463
			strReference = strReference.substring(0, strReference.length() - yearPart.length());
464
			yearPart = yearPart.replaceFirst(pStart + yearSeperator, "").trim();
465
		}else{
466
			if (nameToBeFilled.isZoological()){
467
				IZoologicalName zooName = (IZoologicalName)CdmBase.deproxy(nameToBeFilled);
468
				yearPart = String.valueOf(zooName.getPublicationYear());
469
				//continue
470
			}else{
471
				ref = makeDetailYearUnparsable(nameToBeFilled,strReference);
472
				ref.setDatePublished(TimePeriodParser.parseStringVerbatim(yearPart));
473
				return;
474
			}
475
		}
476

    
477

    
478
		//detail
479
		String pDetailPhrase = detailSeparator + fWs + detail + fWs + end;
480
		Matcher detailPhraseMatcher = getMatcher(pDetailPhrase, strReference);
481
		if (detailPhraseMatcher.find()){
482
			String detailPart = detailPhraseMatcher.group(0);
483
			strReference = strReference.substring(0, strReference.length() - detailPart.length());
484
			detailPart = detailPart.replaceFirst(pStart + detailSeparator, "").trim();
485
			nameToBeFilled.setNomenclaturalMicroReference(detailPart);
486
		}else{
487
			makeDetailYearUnparsable(nameToBeFilled, strReferenceWithYear);
488
			return;
489
		}
490
		//parse title and author
491
		ref = parseReferenceTitle(strReference, yearPart, isInReference);
492
		if (ref.hasProblem()){
493
		    //we need to protect both caches otherwise the titleCache is incorrectly build from atomized parts
494
			ref.setTitleCache( (isInReference ? "in ":"") +  originalStrReference, true);
495
			ref.setAbbrevTitleCache( (isInReference ? "in ":"") +  originalStrReference, true);
496
		}
497
		nameToBeFilled.setNomenclaturalReference(ref);
498
		int end = Math.min(strReference.length(), ref.getProblemEnds());
499
		ref.setProblemEnds(end);
500
	}
501

    
502
	/**
503
	 * @param nameToBeFilled
504
	 * @param strReference
505
	 * @return
506
	 */
507
	private Reference makeDetailYearUnparsable(INonViralName nameToBeFilled, String strReference) {
508
		Reference ref;
509

    
510
		ref = ReferenceFactory.newGeneric();
511
		ref.setTitleCache(strReference, true);
512
        ref.setAbbrevTitleCache(strReference, true);
513
		ref.setProblemEnds(strReference.length());
514
		ref.addParsingProblem(ParserProblem.CheckDetailOrYear);
515
		nameToBeFilled.addParsingProblem(ParserProblem.CheckDetailOrYear);
516
		nameToBeFilled.setNomenclaturalReference(ref);
517
		return ref;
518
	}
519

    
520
	/**
521
	 * Parses the referenceTitlePart, including the author volume and edition.
522
	 * @param reference
523
	 * @param year
524
	 * @return
525
	 */
526
	public INomenclaturalReference parseReferenceTitle(String strReference, String year, boolean isInReference){
527
		IBook result = null;
528

    
529
		Matcher refSineDetailMatcher = referenceSineDetailPattern.matcher(strReference);
530
		if (! refSineDetailMatcher.matches()){
531
			//TODO ?
532
		}
533

    
534
		Matcher articleMatcher = getMatcher(pArticleReference, strReference);
535
		Matcher bookMatcher = getMatcher(pBookReference, strReference);
536

    
537
		Matcher softArticleMatcher = getMatcher(pSoftArticleReference, strReference);
538
		Matcher bookSectionMatcher = getMatcher(pBookSectionReference, strReference);
539

    
540

    
541
		if(isInReference == false){
542
			if (bookMatcher.matches() ){
543
				result = parseBook(strReference);
544
			}else{
545
				logger.info("Non-InRef must be book but does not match book: "+ strReference);
546
				result = ReferenceFactory.newBook();
547
				makeUnparsableRefTitle(result, strReference);
548
			}
549
		}else{  //inRef
550
			if (articleMatcher.matches()){
551
				//article without separators like ","
552
				result = parseArticle(strReference);
553
			}else if (softArticleMatcher.matches()){
554
				result = parseArticle(strReference);
555
			}else if (bookSectionMatcher.matches()){
556
				result = parseBookSection(strReference);
557
			}else{
558
				result =  ReferenceFactory.newGeneric();
559
				makeUnparsableRefTitle(result, "in " + strReference);
560
			}
561
		}
562
		//make year
563
		if (makeYear(result, year) == false){
564
			//TODO
565
			logger.warn("Year could not be parsed");
566
		}
567
		result.setProblemStarts(0);
568
		result.setProblemEnds(strReference.length());
569
		return result;
570
	}
571

    
572
	private void makeUnparsableRefTitle(INomenclaturalReference result, String reference){
573
	    //need to set both to protected otherwise titleCache is created from atomized parts
574
	    result.setTitleCache(reference, true);
575
		result.setAbbrevTitleCache(reference, true);
576
		result.addParsingProblem(ParserProblem.UnparsableReferenceTitle);
577
	}
578

    
579
	/**
580
	 * Parses a single date string. If the string is not parsable a StringNotParsableException is thrown
581
	 * @param singleDateString
582
	 * @return
583
	 * @throws StringNotParsableException
584
	 */
585
	private static Partial parseSingleDate(String singleDateString)
586
			throws StringNotParsableException{
587
		Partial dt = new Partial();
588
		if (CdmUtils.isNumeric(singleDateString)){
589
			try {
590
				Integer year = Integer.valueOf(singleDateString.trim());
591
				if (year > 1750 && year < 2050){
592
					dt = dt.with(DateTimeFieldType.year(), year);
593
				}else{
594
					dt = null;
595
				}
596
			} catch (NumberFormatException e) {
597
				logger.debug("Not a Integer format in getCalendar()");
598
				throw new StringNotParsableException(singleDateString + "is not parsable as a single Date");
599
			}
600
		}
601
		return dt;
602
	}
603

    
604

    
605
	/**
606
	 * Parses the publication date part.
607
	 * @param nomRef
608
	 * @param year
609
	 * @return If the string is not parsable <code>false</code>
610
	 * is returned. <code>True</code> otherwise
611
	 */
612
	private boolean makeYear(INomenclaturalReference nomRef, String year){
613
		boolean result = true;
614
		if (year == null){
615
			return false;
616
		}
617
		if ("".equals(year.trim())){
618
			return true;
619
		}
620
		VerbatimTimePeriod datePublished = TimePeriodParser.parseStringVerbatim(year);
621

    
622
		if (nomRef.getType().equals(ReferenceType.BookSection)){
623
			handleBookSectionYear((IBookSection)nomRef, datePublished);
624
		}else if (nomRef instanceof Reference){
625
			((Reference)nomRef).setDatePublished(datePublished);
626
		}else{
627
			throw new ClassCastException("nom Ref is not of type Reference but " + (nomRef == null? "(null)" : nomRef.getClass()));
628
		}
629
		return result;
630
	}
631

    
632
	private String makeVolume(IVolumeReference nomRef, String strReference){
633
		//volume
634
		String volPart = null;
635
		String pVolPhrase = volumeSeparator +  volume + end;
636
		Matcher volPhraseMatcher = getMatcher(pVolPhrase, strReference);
637
		if (volPhraseMatcher.find()){
638
			volPart = volPhraseMatcher.group(0);
639
			strReference = strReference.substring(0, strReference.length() - volPart.length());
640
			volPart = volPart.replaceFirst(pStart + volumeSeparator, "").trim();
641
			nomRef.setVolume(volPart);
642
		}
643
		return strReference;
644
	}
645

    
646
	private String makeEdition(IBook book, String strReference){
647

    
648
		String editionPart = null;
649
		Matcher editionPhraseMatcher = getMatcher(pEditionPart, strReference);
650

    
651
		Matcher editionVolumeMatcher = getMatcher(pEditionVolPart, strReference);
652
		boolean isEditionAndVol = editionVolumeMatcher.find();
653

    
654
		if (editionPhraseMatcher.find()){
655
			editionPart = editionPhraseMatcher.group(0);
656
			int pos = strReference.indexOf(editionPart);
657
			int posEnd = pos + editionPart.length();
658
			if (isEditionAndVol){
659
				posEnd++;  //delete also comma
660
			}
661
			strReference = strReference.substring(0, pos) + strReference.substring(posEnd);
662
			editionPart = editionPart.replaceFirst(pStart + editionSeparator, "").trim();
663
			if (editionPart.startsWith(",")){
664
			    //, jubilee ed. case
665
			    editionPart = editionPart.substring(1).trim();
666
			}else if (!editionPart.matches(pEdition)){
667
			    editionPart = "ed. " + editionPart;
668
			}
669
			book.setEdition(editionPart);
670
		}
671
		return strReference;
672
	}
673

    
674
	private IBook parseBook(String reference){
675
		IBook result = ReferenceFactory.newBook();
676
		reference = makeEdition(result, reference);
677
		reference = makeVolume(result, reference);
678
		result.setAbbrevTitle(reference);
679
		return result;
680
	}
681

    
682

    
683
	private Reference parseArticle(String reference){
684
		//if (articlePatter)
685
		//(type, author, title, volume, editor, series;
686
		Reference result = ReferenceFactory.newArticle();
687
		reference = makeVolume(result, reference);
688
		Reference inJournal = ReferenceFactory.newJournal();
689
		inJournal.setAbbrevTitle(reference);
690
		result.setInReference(inJournal);
691
		return result;
692
	}
693

    
694
	private Reference parseBookSection(String reference){
695
		Reference result = ReferenceFactory.newBookSection();
696

    
697
		Pattern authorPattern = Pattern.compile("^" + authorTeam + referenceAuthorSeparator);
698
		Matcher authorMatcher = authorPattern.matcher(reference);
699
		boolean find = authorMatcher.find();
700
		if (find){
701
			String authorString = authorMatcher.group(0).trim();
702
			String bookString = reference.substring(authorString.length()).trim();
703
			authorString = authorString.substring(0, authorString.length() -1);
704

    
705
			TeamOrPersonBase<?> authorTeam = author(authorString);
706
			IBook inBook = parseBook(bookString);
707
			inBook.setAuthorship(authorTeam);
708
			result.setInBook(inBook);
709
		}else{
710
			logger.warn("Unexpected non matching book section author part");
711
			//TODO do we want to record a 'problem' here?
712
			result.setTitleCache(reference, true);
713
			result.setAbbrevTitleCache(reference, true);
714
		}
715

    
716
		return result;
717
	}
718

    
719
	/**
720
	 * If the publication date of a book section and it's inBook do differ this is usually
721
	 * caused by the fact that a book has been published during a period, because originally
722
	 * it consisted of several parts that only later where put together to one book.
723
	 * If so, the book section's publication date may be a point in time (year or month of year)
724
	 * whereas the books publication date may be a period of several years.
725
	 * Therefore a valid nomenclatural reference string should use the book sections
726
	 * publication date rather then the book's publication date.<BR>
727
	 * This method in general adds the publication date to the book section.
728
	 * An exception exists if the publication date is a period. Then the parser
729
	 * assumes that the nomenclatural reference string does not follow the above rule but
730
	 * the books publication date is set.
731
	 * @param bookSection
732
	 * @param datePublished
733
	 */
734
	private void handleBookSectionYear(IBookSection bookSection, VerbatimTimePeriod datePublished){
735
		if (datePublished == null || datePublished.getStart() == null || bookSection == null){
736
			return;
737
		}
738
		if (datePublished.isPeriod() && bookSection.getInBook() != null){
739
			bookSection.getInBook().setDatePublished(datePublished);
740
		}else{
741
			bookSection.setDatePublished(datePublished);
742
		}
743
	}
744

    
745
	@Override
746
    public INonViralName parseFullName(String fullNameString){
747
		return parseFullName(fullNameString, null, null);
748
	}
749

    
750
	@Override
751
    public INonViralName parseFullName(String fullNameString, NomenclaturalCode nomCode, Rank rank) {
752

    
753
		if (fullNameString == null){
754
			return null;
755
		}else{
756
			INonViralName result = getNonViralNameInstance(fullNameString, nomCode, rank);
757
			parseFullName(result, fullNameString, rank, false);
758
			return result;
759
		}
760
	}
761

    
762
	@Override
763
	public void parseFullName(INonViralName nameToBeFilledOrig, String fullNameStringOrig, Rank rank, boolean makeEmpty) {
764
	    INonViralName nameToBeFilled = nameToBeFilledOrig;
765

    
766
	    //TODO prol. etc.
767
		boolean hasCheckRankProblem = false; //was rank guessed in a previous parsing process?
768
		if (nameToBeFilled == null){
769
			throw new IllegalArgumentException("NameToBeFilled must not be null in name parser");
770
		}else{
771
			hasCheckRankProblem = nameToBeFilled.hasProblem(ParserProblem.CheckRank);
772
			nameToBeFilled.removeParsingProblem(ParserProblem.CheckRank);
773
		}
774
		String authorString = null;
775
		if (fullNameStringOrig == null){
776
			return;
777
		}
778
		if (makeEmpty){
779
			makeEmpty(nameToBeFilled);
780
		}
781

    
782
		String fullNameString = fullNameStringOrig.replaceAll(oWs , " ").trim();
783

    
784
		fullNameString = removeHybridBlanks(fullNameString);
785
		fullNameString = removeSpNovBlanks(fullNameString);
786
		String[] epi = pattern.split(fullNameString);
787
		try {
788
	    	//cultivars //TODO 2 implement cultivars
789
//		    if ( cultivarMarkerRE.match(fullName) ){ funktioniert noch nicht, da es z.B. auch Namen gibt, wie 't Hart
790
//		    	result = parseCultivar(fullName);
791
//		    }
792

    
793
		    if (genusOrSupraGenusPattern.matcher(fullNameString).matches()){
794
		    	//supraGeneric
795
				if (rank != null && ! hasCheckRankProblem  && (rank.isSupraGeneric()|| rank.isGenus())){
796
					nameToBeFilled.setRank(rank);
797
					nameToBeFilled.setGenusOrUninomial(epi[0]);
798
				}
799
				//genus or guess rank
800
				else {
801
					rank = guessUninomialRank(nameToBeFilled, epi[0]);
802
					nameToBeFilled.setRank(rank);
803
					nameToBeFilled.setGenusOrUninomial(epi[0]);
804
					nameToBeFilled.addParsingProblem(ParserProblem.CheckRank);
805
					nameToBeFilled.setProblemStarts(0);
806
					nameToBeFilled.setProblemEnds(epi[0].length());
807
				}
808
				authorString = fullNameString.substring(epi[0].length());
809
			}
810
			 //infra genus
811
			 else if (infraGenusPattern.matcher(fullNameString).matches()){
812
				Rank infraGenericRank;
813
				if ("[unranked]".equals(epi[1])||"[ranglos]".equals(epi[1])){
814
					infraGenericRank = Rank.INFRAGENERICTAXON();
815
				}else{
816
				    String infraGenericRankMarker = epi[1];
817
				    if (infraGenericRankMarker.startsWith(notho)){  //#3868
818
                        nameToBeFilled.setBinomHybrid(true);
819
                        infraGenericRankMarker = infraGenericRankMarker.substring(notho.length());
820
                    }else if(infraGenericRankMarker.startsWith("n")){
821
                        nameToBeFilled.setBinomHybrid(true);
822
                        infraGenericRankMarker = infraGenericRankMarker.substring(1);
823
                    }
824
                    infraGenericRank = Rank.getRankByIdInVoc(infraGenericRankMarker, nameToBeFilledOrig.getNameType());
825
				}
826
				nameToBeFilled.setRank(infraGenericRank);
827
				nameToBeFilled.setGenusOrUninomial(epi[0]);
828
				nameToBeFilled.setInfraGenericEpithet(epi[2]);
829
				authorString = fullNameString.substring(epi[0].length() + 1 + epi[1].length()+ 1 + epi[2].length());
830
			}
831
			 //aggr. or group
832
			 else if (aggrOrGroupPattern.matcher(fullNameString).matches()){
833
				nameToBeFilled.setRank(Rank.getRankByIdInVoc(epi[2]));
834
				nameToBeFilled.setGenusOrUninomial(epi[0]);
835
				nameToBeFilled.setSpecificEpithet(epi[1]);
836
			}
837
		     //species
838
			 else if (speciesPattern.matcher(fullNameString).matches()){
839
				nameToBeFilled.setRank(Rank.SPECIES());
840
				nameToBeFilled.setGenusOrUninomial(epi[0]);
841
				nameToBeFilled.setSpecificEpithet(normalizeSpNov(epi[1]));
842
				authorString = fullNameString.substring(epi[0].length() + 1 + epi[1].length());
843
			}
844
		    //species with infra generic epithet
845
			 else if (speciesWithInfraGenPattern.matcher(fullNameString).matches()){
846
			     nameToBeFilled.setRank(Rank.SPECIES());
847
	             nameToBeFilled.setGenusOrUninomial(epi[0]);
848
                 nameToBeFilled.setInfraGenericEpithet(epi[2]);
849
	             nameToBeFilled.setSpecificEpithet(epi[4]);
850
	             authorString = fullNameString.substring(epi[0].length() + 2 + epi[2].length() + 2 + epi[4].length());
851
			 }
852
			 //autonym
853
			 else if (autonymPattern.matcher(fullNameString.replace(UTF8.HYBRID.toString(), "")).matches()){
854
			    String infraSpecRankMarker = epi[epi.length - 2];
855
			    boolean isTriHybrid = false;
856
			    if (infraSpecRankMarker.startsWith(notho)){  //#3868
857
                    nameToBeFilled.setTrinomHybrid(true);
858
                    infraSpecRankMarker = infraSpecRankMarker.substring(notho.length());
859
                    isTriHybrid = true;
860
                }else if(infraSpecRankMarker.startsWith("n")){
861
                    nameToBeFilled.setTrinomHybrid(true);
862
                    infraSpecRankMarker = infraSpecRankMarker.substring(1);
863
                    isTriHybrid = true;
864
                }
865
			    if (epi[1].startsWith(UTF8.HYBRID.toString())) {
866
			        nameToBeFilled.setBinomHybrid(true);
867
                }
868
			    nameToBeFilled.setRank(Rank.getRankByIdInVoc(infraSpecRankMarker));
869
				nameToBeFilled.setGenusOrUninomial(epi[0]);
870
				nameToBeFilled.setSpecificEpithet(epi[1]);
871
				nameToBeFilled.setInfraSpecificEpithet(epi[epi.length - 1]);
872
				int lenSpecies = 2 + epi[0].length()+epi[1].length();
873
				int lenInfraSpecies =  2 + epi[epi.length - 2].length() + epi[epi.length - 1].length();
874
				authorString = fullNameString.substring(lenSpecies, fullNameString.length() - lenInfraSpecies);
875
			}
876
	        //genus autonym
877
            else if (genusAutonymPattern.matcher(fullNameString.replace(UTF8.HYBRID.toString(), "")).matches()){
878
                String infraGenRankMarker = epi[epi.length - 2];
879

    
880
                nameToBeFilled.setRank(Rank.getRankByIdInVoc(infraGenRankMarker));
881
                nameToBeFilled.setGenusOrUninomial(epi[0]);
882
                nameToBeFilled.setInfraGenericEpithet(epi[epi.length - 1]);
883
                int lenGenus = 1 + epi[0].length();
884
                int lenInfraGenus =  2 + epi[epi.length - 2].length() + epi[epi.length - 1].length();
885
                authorString = fullNameString.substring(lenGenus, fullNameString.length() - lenInfraGenus);
886
            }
887
			 //infraSpecies
888
			 else if (infraSpeciesPattern.matcher(fullNameString).matches()){
889
				String infraSpecRankMarker = epi[2];
890
				String infraSpecEpi = epi[3];
891
				if ("tax.".equals(infraSpecRankMarker)){
892
					infraSpecRankMarker += " " +  epi[3];
893
					infraSpecEpi = epi[4];
894
				}
895
				Rank infraSpecificRank;
896
				if ("[unranked]".equals(infraSpecRankMarker)||"[ranglos]".equals(infraSpecRankMarker)){
897
					infraSpecificRank = Rank.INFRASPECIFICTAXON();
898
				}else{
899
					String localInfraSpecRankMarker;
900
					if (infraSpecRankMarker.startsWith(notho)){  //#3868
901
	                    nameToBeFilled.setTrinomHybrid(true);
902
	                    localInfraSpecRankMarker = infraSpecRankMarker.substring(notho.length());
903
					}else if(infraSpecRankMarker.startsWith("n")){
904
	                    nameToBeFilled.setTrinomHybrid(true);
905
	                    localInfraSpecRankMarker = infraSpecRankMarker.substring(1);
906
                    }else{
907
                        localInfraSpecRankMarker = infraSpecRankMarker;
908
                    }
909
				    infraSpecificRank = Rank.getRankByIdInVoc(localInfraSpecRankMarker);
910
				}
911
				nameToBeFilled.setRank(infraSpecificRank);
912
				nameToBeFilled.setGenusOrUninomial(epi[0]);
913
				nameToBeFilled.setSpecificEpithet(epi[1]);
914
				nameToBeFilled.setInfraSpecificEpithet(infraSpecEpi);
915
				authorString = fullNameString.substring(epi[0].length()+ 1 + epi[1].length() +1 + infraSpecRankMarker.length() + 1 + infraSpecEpi.length());
916

    
917
			 }
918
		      //infraSpecies without marker
919
			 else if (zooInfraSpeciesPattern.matcher(fullNameString).matches()){
920
					String infraSpecEpi = epi[2];
921
					Rank infraSpecificRank = Rank.SUBSPECIES();
922
					nameToBeFilled.setRank(infraSpecificRank);
923
					nameToBeFilled.setGenusOrUninomial(epi[0]);
924
					nameToBeFilled.setSpecificEpithet(epi[1]);
925
					nameToBeFilled.setInfraSpecificEpithet(infraSpecEpi);
926
					authorString = fullNameString.substring(epi[0].length()+ 1 + epi[1].length() +1 + infraSpecEpi.length());
927

    
928
			 }//old infraSpecies
929
			 else if (oldInfraSpeciesPattern.matcher(fullNameString).matches()){
930
				boolean implemented = true;
931
				if (implemented){
932
				    String infraSpecRankMarker = epi[2];
933
	                String infraSpecEpi = epi[3];
934

    
935
	                Rank infraSpecificRank = Rank.getRankByNameOrIdInVoc(infraSpecRankMarker);
936
	                nameToBeFilled.setRank(infraSpecificRank);
937
	                nameToBeFilled.setGenusOrUninomial(epi[0]);
938
	                nameToBeFilled.setSpecificEpithet(epi[1]);
939
	                nameToBeFilled.setInfraSpecificEpithet(infraSpecEpi);
940
	                authorString = fullNameString.substring(epi[0].length()+ 1 + epi[1].length() +1 + infraSpecRankMarker.length() + 1 + infraSpecEpi.length());
941

    
942
				}else{
943
					nameToBeFilled.addParsingProblem(ParserProblem.OldInfraSpeciesNotSupported);
944
					nameToBeFilled.setTitleCache(fullNameString, true);
945
					// FIXME Quick fix, otherwise search would not deliver results for unparsable names
946
					nameToBeFilled.setNameCache(fullNameString,true);
947
					// END
948
					logger.info("Name string " + fullNameString + " could not be parsed because UnnnamedNamePhrase is not yet implemented!");
949
				}
950
			 }
951
		     //hybrid formula
952
			 else if (hybridFormulaPattern.matcher(fullNameString).matches()){
953
				 Set<HybridRelationship> existingRelations = new HashSet<>();
954
				 Set<HybridRelationship> notToBeDeleted = new HashSet<>();
955

    
956
				 for ( HybridRelationship rel : nameToBeFilled.getHybridChildRelations()){
957
				     existingRelations.add(rel);
958
				 }
959

    
960
			     String givenNameString = "";
961
				 String secondNameString = "";
962
				 boolean isGivenName = true;
963
				 for (String str : epi){
964
					 if (str.matches(hybridSign)){
965
						 isGivenName = false;
966
					 }else if(isGivenName){
967
						 givenNameString += " " + str;
968
					 }else {
969
						 secondNameString += " " + str;
970
					 }
971
				 }
972
				 givenNameString = givenNameString.trim();
973
				 secondNameString = secondNameString.trim();
974
				 nameToBeFilled.setHybridFormula(true);
975
				 NomenclaturalCode code = nameToBeFilled.getNameType();
976
				 INonViralName firstName = this.parseFullName(givenNameString, code, rank);
977
				 if (secondNameString.matches(abbrevHybridSecondPart)){
978
				     secondNameString = extendSecondHybridPart(firstName, secondNameString);
979
				 }
980
				 INonViralName secondName = this.parseFullName(secondNameString, code, rank);
981
				 HybridRelationship firstRel = nameToBeFilled.addHybridParent(firstName, HybridRelationshipType.FIRST_PARENT(), null);
982
				 HybridRelationship second = nameToBeFilled.addHybridParent(secondName, HybridRelationshipType.SECOND_PARENT(), null);
983
				 checkRelationExist(firstRel, existingRelations, notToBeDeleted);
984
				 checkRelationExist(second, existingRelations, notToBeDeleted);
985

    
986
				 Rank newRank;
987
				 Rank firstRank = firstName.getRank();
988
				 Rank secondRank = secondName.getRank();
989

    
990
				 if (firstRank == null || (secondRank != null && firstRank.isHigher(secondRank))){
991
					 newRank = secondRank;
992
				 }else{
993
					 newRank = firstRank;
994
				 }
995
				 nameToBeFilled.setRank(newRank);
996
				 //remove not existing hybrid relation
997
				 if (makeEmpty){
998
		            Set<HybridRelationship> tmpChildRels = new HashSet<HybridRelationship>();
999
		            tmpChildRels.addAll(nameToBeFilled.getHybridChildRelations());
1000
		            for (HybridRelationship rel : tmpChildRels){
1001
		                if (! notToBeDeleted.contains(rel)){
1002
		                    nameToBeFilled.removeHybridRelationship(rel);
1003
		                }
1004
		            }
1005
				 }
1006
			 }
1007
		    //none
1008
			else{
1009
				nameToBeFilled.addParsingProblem(ParserProblem.UnparsableNamePart);
1010
				nameToBeFilled.setTitleCache(fullNameString, true);
1011
				// FIXME Quick fix, otherwise search would not deilver results for unparsable names
1012
				nameToBeFilled.setNameCache(fullNameString, true);
1013
				// END
1014
				logger.info("no applicable parsing rule could be found for \"" + fullNameString + "\"");
1015
		    }
1016
		    //hybrid bits
1017
		    handleHybridBits(nameToBeFilled);
1018
		    if (!nameToBeFilled.isHybridFormula()){
1019
		        Set<HybridRelationship> hybridChildRelations = new HashSet<>();
1020
		        hybridChildRelations.addAll(nameToBeFilled.getHybridChildRelations());
1021

    
1022
		        for (HybridRelationship hybridRelationship: hybridChildRelations){
1023
		        	nameToBeFilled.removeHybridRelationship(hybridRelationship);
1024
		        }
1025
		    }
1026

    
1027
			//authors
1028
		    if (StringUtils.isNotBlank(authorString) ){
1029
				handleAuthors(nameToBeFilled, fullNameString, authorString);
1030
			}
1031
		    return;
1032
		} catch (UnknownCdmTypeException e) {
1033
			nameToBeFilled.addParsingProblem(ParserProblem.RankNotSupported);
1034
			nameToBeFilled.setTitleCache(fullNameString, true);
1035
			// FIXME Quick fix, otherwise search would not deilver results for unparsable names
1036
			nameToBeFilled.setNameCache(fullNameString,true);
1037
			// END
1038
			logger.info("unknown rank (" + (rank == null? "null":rank) + ") or abbreviation in string " +  fullNameString);
1039
			//return result;
1040
			return;
1041
		}
1042
	}
1043

    
1044
	/**
1045
     * @param string
1046
     * @return
1047
     */
1048
    private String normalizeSpNov(String epi) {
1049
        if (spNovPattern.matcher(epi).matches()){
1050
            epi = epi.replace(".", ". ").replace("\\s+", " ").trim();
1051
        }
1052
        return epi;
1053
    }
1054

    
1055
    /**
1056
     * @param givenName
1057
     * @param secondNameString
1058
     * @return
1059
     */
1060
    private String extendSecondHybridPart(INonViralName givenName, String secondNameString) {
1061
        //first letter of genus given
1062
        if (secondNameString.matches("^" + abbrevHybridGenus + ".*")){
1063
            if (StringUtils.isNotBlank(givenName.getGenusOrUninomial())){
1064
                if (secondNameString.substring(0,1).equals(givenName.getGenusOrUninomial().substring(0, 1))){
1065
                    secondNameString = secondNameString.replaceAll("^" + abbrevHybridGenus, givenName.getGenusOrUninomial() + " ");
1066
                }
1067
            }
1068
        }else if (secondNameString.matches(abbrevHybridSecondPartOnlyInfraSpecies)){
1069
            secondNameString = CdmUtils.concat(" " , givenName.getGenusOrUninomial(), givenName.getSpecificEpithet(), secondNameString);
1070
        }else if (true){  //there will be further alternatives in future maybe
1071
            secondNameString = CdmUtils.concat(" " , givenName.getGenusOrUninomial(), secondNameString);
1072
        }
1073
        return secondNameString;
1074
    }
1075

    
1076
    /**
1077
     * Checks if a hybrid relation exists in the Set of existing relations
1078
     * and <BR>
1079
     *  if it does not adds it to relations not to be deleted <BR>
1080
     *  if it does adds the existing relations to the relations not to be deleted
1081
     *
1082
     * @param firstRel
1083
     * @param existingRelations
1084
     * @param notToBeDeleted
1085
     */
1086
    private void checkRelationExist(
1087
            HybridRelationship newRelation,
1088
            Set<HybridRelationship> existingRelations,
1089
            Set<HybridRelationship> notToBeDeleted) {
1090
        HybridRelationship relToKeep = newRelation;
1091
        for (HybridRelationship existingRelation : existingRelations){
1092
            if (existingRelation.equals(newRelation)){
1093
                relToKeep = existingRelation;
1094
                break;
1095
            }
1096
        }
1097
        notToBeDeleted.add(relToKeep);
1098
    }
1099

    
1100
    private void handleHybridBits(INonViralName nameToBeFilled) {
1101
		//uninomial
1102
		String uninomial = CdmUtils.Nz(nameToBeFilled.getGenusOrUninomial());
1103
		boolean isUninomialHybrid = uninomial.startsWith(hybridSign);
1104
		if (isUninomialHybrid){
1105
			nameToBeFilled.setMonomHybrid(true);
1106
			nameToBeFilled.setGenusOrUninomial(uninomial.replace(hybridSign, ""));
1107
		}
1108
		//infrageneric
1109
		String infrageneric = CdmUtils.Nz(nameToBeFilled.getInfraGenericEpithet());
1110
		boolean isInfraGenericHybrid = infrageneric.startsWith(hybridSign);
1111
		if (isInfraGenericHybrid){
1112
			nameToBeFilled.setBinomHybrid(true);
1113
			nameToBeFilled.setInfraGenericEpithet(infrageneric.replace(hybridSign, ""));
1114
		}
1115
		//species Epi
1116
		String speciesEpi = CdmUtils.Nz(nameToBeFilled.getSpecificEpithet());
1117
		boolean isSpeciesHybrid = speciesEpi.startsWith(hybridSign);
1118
		if (isSpeciesHybrid){
1119
			if (StringUtils.isBlank(infrageneric)){
1120
				nameToBeFilled.setBinomHybrid(true);
1121
			}else{
1122
				nameToBeFilled.setTrinomHybrid(true);
1123
			}
1124
			nameToBeFilled.setSpecificEpithet(speciesEpi.replace(hybridSign, ""));
1125
		}
1126
		//infra species
1127
		String infraSpeciesEpi = CdmUtils.Nz(nameToBeFilled.getInfraSpecificEpithet());
1128
		boolean isInfraSpeciesHybrid = infraSpeciesEpi.startsWith(hybridSign);
1129
		if (isInfraSpeciesHybrid){
1130
			nameToBeFilled.setTrinomHybrid(true);
1131
			nameToBeFilled.setInfraSpecificEpithet(infraSpeciesEpi.replace(hybridSign, ""));
1132
		}
1133

    
1134
	}
1135

    
1136
	private String removeHybridBlanks(String fullNameString) {
1137
		String result = fullNameString
1138
		        .replaceAll(oWs + "[xX]" + oWs + "(?=[A-Z])", " " + hybridSign + " ")
1139
		        .replaceAll(hybridFull, " " + hybridSign).trim();
1140
		if (result.contains(hybridSign + " ") &&
1141
		        result.matches("^" + capitalEpiWord + oWs + hybridSign + oWs + nonCapitalEpiWord + ".*")){
1142
		    result = result.replaceFirst(hybridSign + oWs, hybridSign);
1143
		}
1144
		return result;
1145
	}
1146

    
1147

    
1148
    private String removeSpNovBlanks(String fullNameString) {
1149
        Matcher spNovMatcher = spNovPattern.matcher(fullNameString);
1150
        if (spNovMatcher.find()){
1151
            String spNov = spNovMatcher.group(0);
1152
            String spNovShort = spNov.replaceAll("\\s", "");
1153
            if (spNov.length() != spNovShort.length()){
1154
                fullNameString = fullNameString.replace(spNov, spNovShort);
1155
            }
1156
        }
1157
        return fullNameString;
1158
    }
1159

    
1160

    
1161
	/**
1162
	 * Author parser for external use
1163
	 * @param nonViralName
1164
	 * @param authorString
1165
	 * @throws StringNotParsableException
1166
	 */
1167
	@Override
1168
	public void parseAuthors(INonViralName nonViralNameOrig, String authorString) throws StringNotParsableException{
1169
	    INonViralName nonViralName = CdmBase.deproxy(nonViralNameOrig);
1170
	    TeamOrPersonBase<?>[] authors = new TeamOrPersonBase[4];
1171
		Integer[] years = new Integer[4];
1172
		NomenclaturalCode code = nonViralName.getNameType();
1173
		fullAuthors(authorString, authors, years, code);
1174
		nonViralName.setCombinationAuthorship(authors[0]);
1175
		nonViralName.setExCombinationAuthorship(authors[1]);
1176
		nonViralName.setBasionymAuthorship(authors[2]);
1177
		nonViralName.setExBasionymAuthorship(authors[3]);
1178
		if (nonViralName.isZoological()){
1179
			IZoologicalName zooName = (IZoologicalName)nonViralName;
1180
			zooName.setPublicationYear(years[0]);
1181
			zooName.setOriginalPublicationYear(years[2]);
1182
		}
1183
	}
1184

    
1185
	/**
1186
	 * @param nameToBeFilled
1187
	 * @param fullNameString
1188
	 * @param authorString
1189
	 */
1190
	public void handleAuthors(INonViralName nameToBeFilled, String fullNameString, String authorString) {
1191
	    TeamOrPersonBase<?>[] authors = new TeamOrPersonBase[4];
1192
		Integer[] years = new Integer[4];
1193
		try {
1194
			NomenclaturalCode code = nameToBeFilled.getNameType();
1195
			fullAuthors(authorString, authors, years, code);
1196
		} catch (StringNotParsableException e) {
1197
			nameToBeFilled.addParsingProblem(ParserProblem.UnparsableAuthorPart);
1198
			nameToBeFilled.setTitleCache(fullNameString, true);
1199
			// FIXME Quick fix, otherwise search would not deliver results for unparsable names
1200
			nameToBeFilled.setNameCache(fullNameString, true);
1201
			// END
1202
			logger.info("no applicable parsing rule could be found for \"" + fullNameString + "\"");
1203
		}
1204
		nameToBeFilled.setCombinationAuthorship(authors[0]);
1205
		nameToBeFilled.setExCombinationAuthorship(authors[1]);
1206
		nameToBeFilled.setBasionymAuthorship(authors[2]);
1207
		nameToBeFilled.setExBasionymAuthorship(authors[3]);
1208
		if (nameToBeFilled.isZoological()){
1209
			IZoologicalName zooName = (IZoologicalName)nameToBeFilled;
1210
			zooName.setPublicationYear(years[0]);
1211
			zooName.setOriginalPublicationYear(years[2]);
1212
		}
1213
	}
1214

    
1215
	/**
1216
	 * Guesses the rank of uninomial depending on the typical endings for ranks
1217
	 * @param nameToBeFilled
1218
	 * @param string
1219
	 */
1220
	private Rank guessUninomialRank(INonViralName nameToBeFilled, String uninomial) {
1221
		Rank result = Rank.GENUS();
1222
		if (nameToBeFilled.isBotanical()){
1223
			if (false){
1224
				//
1225
			}else if (uninomial.endsWith("phyta") || uninomial.endsWith("mycota") ){  //plants, fungi
1226
				result = Rank.SECTION_BOTANY();
1227
			}else if (uninomial.endsWith("bionta")){
1228
				result = Rank.SUBKINGDOM();  //TODO
1229
			}else if (uninomial.endsWith("phytina")|| uninomial.endsWith("mycotina")  ){  //plants, fungi
1230
				result = Rank.SUBSECTION_BOTANY();
1231
			}else if (uninomial.endsWith("opsida") || uninomial.endsWith("phyceae") || uninomial.endsWith("mycetes")){  //plants, algae, fungi
1232
				result = Rank.CLASS();
1233
			}else if (uninomial.endsWith("idae") || uninomial.endsWith("phycidae") || uninomial.endsWith("mycetidae")){ //plants, algae, fungi
1234
				result = Rank.SUBCLASS();
1235
			}else if (uninomial.endsWith("ales")){
1236
				result = Rank.ORDER();
1237
			}else if (uninomial.endsWith("ineae")){
1238
				result = Rank.SUBORDER();
1239
			}else if (uninomial.endsWith("aceae")){
1240
					result = Rank.FAMILY();
1241
			}else if (uninomial.endsWith("oideae")){
1242
				result = Rank.SUBFAMILY();
1243
			}else if (uninomial.endsWith("eae")){
1244
				result = Rank.TRIBE();
1245
			}else if (uninomial.endsWith("inae")){
1246
				result = Rank.SUBTRIBE();
1247
			}else if (uninomial.endsWith("ota")){
1248
				result = Rank.KINGDOM();  //TODO
1249
			}
1250
		}else if (nameToBeFilled.isZoological()){
1251
			if (false){
1252
				//
1253
			}else if (uninomial.endsWith("oideae")){
1254
				result = Rank.SUPERFAMILY();
1255
			}else if (uninomial.endsWith("idae")){
1256
					result = Rank.FAMILY();
1257
			}else if (uninomial.endsWith("inae")){
1258
				result = Rank.SUBFAMILY();
1259
			}else if (uninomial.endsWith("inae")){
1260
				result = Rank.SUBFAMILY();
1261
			}else if (uninomial.endsWith("ini")){
1262
				result = Rank.TRIBE();
1263
			}else if (uninomial.endsWith("ina")){
1264
				result = Rank.SUBTRIBE();
1265
			}
1266
		}else{
1267
			//
1268
		}
1269
		return result;
1270
	}
1271

    
1272
	/**
1273
	 * Parses the fullAuthorString
1274
	 * @param fullAuthorString
1275
	 * @return array of Teams containing the Team[0],
1276
	 * ExTeam[1], BasionymTeam[2], ExBasionymTeam[3]
1277
	 */
1278
	protected void fullAuthors (String fullAuthorStringOrig, TeamOrPersonBase<?>[] authors,
1279
	        Integer[] years, NomenclaturalCode code)
1280
			throws StringNotParsableException{
1281
		if (fullAuthorStringOrig == null || code == null){
1282
			return;
1283
		}
1284
		String fullAuthorString = fullAuthorStringOrig.trim();
1285

    
1286
		//Botanic
1287
		if ( code.isBotanical() ){
1288
			if (! fullBotanicAuthorStringPattern.matcher(fullAuthorString).matches() ){
1289
				throw new StringNotParsableException("fullAuthorString (" +fullAuthorString+") not parsable: ");
1290
			}
1291
		}
1292
		//Zoo
1293
		else if ( code.isZoological() ){
1294
			if (! fullZooAuthorStringPattern.matcher(fullAuthorString).matches() ){
1295
				throw new StringNotParsableException("fullAuthorString (" +fullAuthorString+") not parsable: ");
1296
			}
1297
		}else {
1298
			//TODO
1299
			logger.warn ("Full author String parsable only for defined BotanicalNames or ZoologicalNames but this is " + code.getMessage());
1300
			throw new StringNotParsableException("fullAuthorString (" +fullAuthorString+") not parsable: ");
1301
		}
1302
		fullAuthorsChecked(fullAuthorString, authors, years);
1303
	}
1304

    
1305
	/*
1306
	 * like fullTeams but without trim and match check
1307
	 */
1308
	protected void fullAuthorsChecked (String fullAuthorString, TeamOrPersonBase<?>[] authors, Integer[] years){
1309
		int authorShipStart = 0;
1310
		Matcher basionymMatcher = basionymPattern.matcher(fullAuthorString);
1311

    
1312
		if (basionymMatcher.find(0)){
1313

    
1314
			String basString = basionymMatcher.group();
1315
			basString = basString.replaceFirst(basStart, "");
1316
			basString = basString.replaceAll(basEnd, "").trim();
1317
			authorShipStart = basionymMatcher.end(1);
1318

    
1319
			TeamOrPersonBase<?>[] basAuthors = new TeamOrPersonBase[2];
1320
			Integer[] basYears = new Integer[2];
1321
			authorsAndEx(basString, basAuthors, basYears);
1322
			authors[2]= basAuthors[0];
1323
			years[2] = basYears[0];
1324
			authors[3]= basAuthors[1];
1325
			years[3] = basYears[1];
1326
		}
1327
		if (fullAuthorString.length() >= authorShipStart){
1328
			TeamOrPersonBase<?>[] combinationAuthors = new TeamOrPersonBase[2];
1329
			Integer[] combinationYears = new Integer[2];
1330
			authorsAndEx(fullAuthorString.substring(authorShipStart), combinationAuthors, combinationYears);
1331
			authors[0]= combinationAuthors[0] ;
1332
			years[0] = combinationYears[0];
1333
			authors[1]= combinationAuthors[1];
1334
			years[1] = combinationYears[1];
1335
		}
1336
	}
1337

    
1338

    
1339
	/**
1340
	 * Parses the author and ex-author String
1341
	 * @param authorShipStringOrig String representing the author and the ex-author team
1342
	 * @return array of Teams containing the Team[0] and the ExTeam[1]
1343
	 */
1344
	protected void authorsAndEx (String authorShipStringOrig, TeamOrPersonBase<?>[] authors, Integer[] years){
1345
		//TODO noch allgemeiner am Anfang durch Replace etc.
1346
		String authorShipString = authorShipStringOrig.trim();
1347
		authorShipString = authorShipString.replaceFirst(oWs + "ex" + oWs, " ex. " );
1348

    
1349
		//int authorEnd = authorTeamString.length();
1350
		int authorBegin = 0;
1351

    
1352
		Matcher exAuthorMatcher = exAuthorPattern.matcher(authorShipString);
1353
		if (exAuthorMatcher.find(0)){
1354
			authorBegin = exAuthorMatcher.end(0);
1355
			int exAuthorEnd = exAuthorMatcher.start(0);
1356
			String exString = authorShipString.substring(0, exAuthorEnd).trim();
1357
			authors [1] = author(exString);
1358
		}
1359
		zooOrBotanicAuthor(authorShipString.substring(authorBegin), authors, years );
1360
	}
1361

    
1362
	/**
1363
	 * Parses the authorString and if it matches an botanical or zoological authorTeam it fills
1364
	 * the computes the AuthorTeam and fills it into the first field of the team array. Same applies
1365
	 * to the year in case of an zoological name.
1366
	 * @param authorString
1367
	 * @param team
1368
	 * @param year
1369
	 */
1370
	protected void zooOrBotanicAuthor(String authorString, TeamOrPersonBase<?>[] team, Integer[] year){
1371
		if (authorString == null){
1372
			return;
1373
		}else if ((authorString = authorString.trim()).length() == 0){
1374
			return;
1375
		}
1376
		Matcher zooAuthorAddidtionMatcher = zooAuthorAddidtionPattern.matcher(authorString);
1377
		if (zooAuthorAddidtionMatcher.find()){
1378
			int index = zooAuthorAddidtionMatcher.start(0);
1379
			String strYear = authorString.substring(index);
1380
			strYear = strYear.replaceAll(zooAuthorYearSeperator, "").trim();
1381
			year[0] = Integer.valueOf(strYear);
1382
			authorString = authorString.substring(0, index).trim();
1383
		}
1384
		team[0] = author(authorString);
1385
	}
1386

    
1387

    
1388
	/**
1389
	 * Parses an author (person or team) string and returns the Person or Team.
1390
	 * @param authorString String representing the author
1391
	 * @return a person or team
1392
	 */
1393
	public TeamOrPersonBase<?> author (String authorString){
1394
		if (authorString == null){
1395
			return null;
1396
		}else if ((authorString = authorString.trim()).length() == 0){
1397
			return null;
1398
		}else if (! finalTeamSplitterPattern.matcher(authorString).find() && ! authorIsAlwaysTeam){
1399
			//1 Person
1400
			Person result = Person.NewInstance();
1401
			authorString = normalizeNomenclaturalPersonString(authorString);
1402
			result.setNomenclaturalTitle(authorString);
1403
			return result;
1404
		}else{
1405
			return parsedTeam(authorString);
1406
		}
1407

    
1408
	}
1409

    
1410
	/**
1411
	 * Parses an authorString (representing a team into the single authors and add
1412
	 * them to the return Team.
1413
	 * @param authorString
1414
	 * @return Team
1415
	 */
1416
	protected Team parsedTeam(String authorString){
1417
		Team result = Team.NewInstance();
1418
		String[] authors = authorString.split(notFinalTeamSplitter);
1419
		for (int i = 0; i < authors.length; i++){
1420
		    String author = authors[i];
1421
		    if ("al.".equals(author.trim()) && i == authors.length - 1){  //final al. is handled as hasMoreMembers
1422
			    result.setHasMoreMembers(true);
1423
			}else{
1424
			    Person person = Person.NewInstance();
1425
			    author = normalizeNomenclaturalPersonString(author);
1426
	            person.setNomenclaturalTitle(author);
1427
			    result.addTeamMember(person);
1428
			}
1429
		}
1430
		return result;
1431
	}
1432

    
1433

    
1434
/**
1435
     * @param author
1436
     * @return
1437
     */
1438
    private String normalizeNomenclaturalPersonString(String author) {
1439
        if (removeSpaceAfterDot){
1440
            author = author.replaceAll("\\.\\s", ".");
1441
        }
1442
        return author;
1443
    }
1444

    
1445
    //	// Parsing of the given full name that has been identified as a cultivar already somwhere else.
1446
//	// The ... cv. ... syntax is not covered here as it is not according the rules for naming cultivars.
1447
	public IBotanicalName parseCultivar(String fullName) throws StringNotParsableException{
1448
		ICultivarPlantName result = null;
1449
		    String[] words = oWsPattern.split(fullName);
1450

    
1451
		    /* ---------------------------------------------------------------------------------
1452
		     * cultivar
1453
		     * ---------------------------------------------------------------------------------*/
1454
			if (fullName.indexOf(" '") != 0){
1455
				//TODO location of 'xx' is probably not arbitrary
1456
				Matcher cultivarMatcher = cultivarPattern.matcher(fullName);
1457
				if (cultivarMatcher.find()){
1458
					String namePart = fullName.replaceFirst(cultivar, "");
1459

    
1460
					String cultivarPart = cultivarMatcher.group(0).replace("'","").trim();
1461
					//OLD: String cultivarPart = cultivarRE.getParen(0).replace("'","").trim();
1462

    
1463
					result = (ICultivarPlantName)parseFullName(namePart);
1464
					result.setCultivarName(cultivarPart);
1465
				}
1466
			}else if (fullName.indexOf(" cv.") != 0){
1467
				// cv. is old form (not official)
1468
				throw new StringNotParsableException("Cultivars with only cv. not yet implemented in name parser!");
1469
			}
1470

    
1471
		    /* ---------------------------------------------------------------------------------
1472
		     * cultivar group
1473
		     * ---------------------------------------------------------------------------------
1474
		     */
1475
			// TODO in work
1476
			//Ann. this is not the official way of noting cultivar groups
1477
		    String group = oWs + "Group" + oWs + capitalEpiWord + end;
1478
			Pattern groupRE = Pattern.compile(group);
1479
			Matcher groupMatcher = groupRE.matcher(fullName);
1480
			if (groupMatcher.find()){
1481
		    	if (! words[words.length - 2].equals("group")){
1482
		            throw new StringNotParsableException ("fct ParseHybrid --> term before cultivar group name in " + fullName + " should be 'group'");
1483
		        }else{
1484

    
1485
		        	String namePart = fullName.substring(0, groupMatcher.start(0) - 0);
1486
		        	//OLD: String namePart = fullName.substring(0, groupRE.getParenStart(0) - 0);
1487

    
1488
		        	String cultivarPart = words[words.length -1];
1489
		        	result = (ICultivarPlantName)parseFullName(namePart);
1490
		        	if (result != null){
1491
		        		result.setCultivarName(cultivarPart);
1492

    
1493
		        		//OLD: result.setCultivarGroupName(cultivarPart);
1494
		        	}
1495
		        }
1496

    
1497
		    }
1498
//		    // ---------------------------------------------------------------------------------
1499
//		    if ( result = "" ){
1500
//		        return "I: fct ParseCultivar: --> could not parse cultivar " + fullName;
1501
//		    }else{
1502
//		        return result;
1503
	//	    }
1504
			return result; //TODO
1505
	}
1506

    
1507

    
1508
	private void makeEmpty(INonViralName name){
1509
	    TaxonName nameToBeFilled = TaxonName.castAndDeproxy(name);
1510
		nameToBeFilled.setRank(null);
1511
		nameToBeFilled.setTitleCache(null, false);
1512
		nameToBeFilled.setFullTitleCache(null, false);
1513
		nameToBeFilled.setNameCache(null, false);
1514

    
1515
		nameToBeFilled.setAppendedPhrase(null);
1516
		nameToBeFilled.setBasionymAuthorship(null);
1517
		nameToBeFilled.setCombinationAuthorship(null);
1518
		nameToBeFilled.setExBasionymAuthorship(null);
1519
		nameToBeFilled.setExCombinationAuthorship(null);
1520
		nameToBeFilled.setAuthorshipCache(null, false);
1521

    
1522

    
1523
		//delete problems except check rank
1524
		makeProblemEmpty(nameToBeFilled);
1525

    
1526
		// TODO ?
1527
		//nameToBeFilled.setHomotypicalGroup(newHomotypicalGroup);
1528

    
1529

    
1530
		nameToBeFilled.setGenusOrUninomial(null);
1531
		nameToBeFilled.setInfraGenericEpithet(null);
1532
		nameToBeFilled.setSpecificEpithet(null);
1533
		nameToBeFilled.setInfraSpecificEpithet(null);
1534

    
1535
		nameToBeFilled.setNomenclaturalMicroReference(null);
1536
		nameToBeFilled.setNomenclaturalReference(null);
1537

    
1538
		nameToBeFilled.setHybridFormula(false);
1539
		nameToBeFilled.setMonomHybrid(false);
1540
		nameToBeFilled.setBinomHybrid(false);
1541
		nameToBeFilled.setTrinomHybrid(false);
1542

    
1543
		nameToBeFilled.setAnamorphic(false);
1544

    
1545
		nameToBeFilled.setBreed(null);
1546
		nameToBeFilled.setOriginalPublicationYear(null);
1547

    
1548
		//nom status handled in nom status parser, otherwise we loose additional information like reference etc.
1549
		//hybrid relationships handled in hybrid formula and at end of fullNameParser
1550
	}
1551

    
1552

    
1553
    /**
1554
     * If <code>true</code> author names are parsed such that spaces after the abbreviated
1555
     * givenname are removed (IPNI style). see #7094
1556
     */
1557
    public boolean isRemoveSpaceAfterDot() {
1558
        return removeSpaceAfterDot;
1559
    }
1560
    /**
1561
     * @see #isRemoveSpaceAfterDot()
1562
     */
1563
    public void setRemoveSpaceAfterDot(boolean removeSpaceAfterDot) {
1564
        this.removeSpaceAfterDot = removeSpaceAfterDot;
1565
    }
1566
}
(4-4/8)