2 * Copyright (C) 2009 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
10 package eu
.etaxonomy
.cdm
.io
.markup
;
12 import java
.net
.MalformedURLException
;
14 import java
.util
.HashSet
;
17 import java
.util
.UUID
;
19 import javax
.xml
.stream
.Location
;
20 import javax
.xml
.stream
.XMLEventReader
;
21 import javax
.xml
.stream
.XMLStreamException
;
22 import javax
.xml
.stream
.events
.Attribute
;
23 import javax
.xml
.stream
.events
.StartElement
;
24 import javax
.xml
.stream
.events
.XMLEvent
;
26 import org
.apache
.commons
.lang
.StringUtils
;
27 import org
.apache
.log4j
.Logger
;
29 import eu
.etaxonomy
.cdm
.io
.common
.mapping
.UndefinedTransformerMethodException
;
30 import eu
.etaxonomy
.cdm
.model
.agent
.TeamOrPersonBase
;
31 import eu
.etaxonomy
.cdm
.model
.common
.ExtensionType
;
32 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
33 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
34 import eu
.etaxonomy
.cdm
.model
.description
.PolytomousKey
;
35 import eu
.etaxonomy
.cdm
.model
.description
.PolytomousKeyNode
;
36 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
37 import eu
.etaxonomy
.cdm
.model
.description
.TextData
;
38 import eu
.etaxonomy
.cdm
.model
.name
.INonViralName
;
39 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
40 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameFactory
;
41 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
42 import eu
.etaxonomy
.cdm
.model
.reference
.ReferenceFactory
;
43 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
44 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
45 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
52 public class MarkupDocumentImportNoComponent
extends MarkupImportBase
{
53 @SuppressWarnings("unused")
54 private static final Logger logger
= Logger
.getLogger(MarkupDocumentImportNoComponent
.class);
56 private final MarkupKeyImport keyImport
;
58 private final MarkupModsImport modsImport
;
59 private final MarkupSpecimenImport specimenImport
;
60 private final MarkupNomenclatureImport nomenclatureImport
;
62 public MarkupDocumentImportNoComponent(MarkupDocumentImport docImport
) {
64 this.keyImport
= new MarkupKeyImport(docImport
);
65 this.specimenImport
= new MarkupSpecimenImport(docImport
);
66 this.nomenclatureImport
= new MarkupNomenclatureImport(docImport
, specimenImport
);
67 this.modsImport
= new MarkupModsImport(docImport
);
68 this.featureImport
= new MarkupFeatureImport(docImport
, specimenImport
, nomenclatureImport
, keyImport
);
71 public void doInvoke(MarkupImportState state
) throws XMLStreamException
{
72 XMLEventReader reader
= state
.getReader();
74 // publication (= root element)
75 String elName
= PUBLICATION
;
76 boolean hasPublication
= false;
78 while (reader
.hasNext()) {
79 XMLEvent nextEvent
= reader
.nextEvent();
80 if (isStartingElement(nextEvent
, elName
)) {
81 handlePublication(state
, reader
, nextEvent
, elName
);
82 hasPublication
= true;
83 } else if (nextEvent
.isEndDocument()) {
84 if (!hasPublication
) {
85 String message
= "No publication root element found";
86 fireWarningEvent(message
, nextEvent
, 8);
90 fireSchemaConflictEventExpectedStartTag(elName
, reader
);
99 private void handlePublication(MarkupImportState state
, XMLEventReader reader
, XMLEvent currentEvent
, String elName
) throws XMLStreamException
{
102 StartElement element
= currentEvent
.asStartElement();
103 Map
<String
, Attribute
> attributes
= getAttributes(element
);
104 String lang
= getAndRemoveAttributeValue(attributes
, "lang");
106 Language language
= getTermService().getLanguageByIso(lang
);
107 state
.setDefaultLanguage(language
);
110 handleUnexpectedAttributes(element
.getLocation(), attributes
, "noNamespaceSchemaLocation");
112 while (reader
.hasNext()) {
113 XMLEvent event
= readNoWhitespace(reader
);
114 // TODO cardinality of alternative
115 if (isEndingElement(event
, elName
)) {
117 } else if (event
.isStartElement()) {
118 if (isStartingElement(event
, META_DATA
)) {
119 handleMetaData(state
, reader
, event
);
120 } else if (isStartingElement(event
, TREATMENT
)) {
121 handleTreatment(state
, reader
, event
);
122 } else if (isStartingElement(event
, BIOGRAPHIES
)) {
123 handleNotYetImplementedElement(event
);
124 } else if (isStartingElement(event
, REFERENCES
)) {
125 handleNotYetImplementedElement(event
);
126 } else if (isStartingElement(event
, TEXT_SECTION
)) {
127 handleNotYetImplementedElement(event
);
128 } else if (isStartingElement(event
, ADDENDA
)) {
129 handleNotYetImplementedElement(event
);
131 handleUnexpectedStartElement(event
);
134 handleUnexpectedElement(event
);
137 throw new IllegalStateException("Publication has no ending element");
140 private void handleMetaData(MarkupImportState state
, XMLEventReader reader
, XMLEvent parentEvent
) throws XMLStreamException
{
141 checkNoAttributes(parentEvent
);
143 while (reader
.hasNext()) {
144 XMLEvent next
= readNoWhitespace(reader
);
145 if (isMyEndingElement(next
, parentEvent
)) {
147 } else if (isStartingElement(next
, DEFAULT_MEDIA_URL
)) {
148 String baseUrl
= getCData(state
, reader
, next
);
151 state
.setBaseMediaUrl(baseUrl
);
152 } catch (MalformedURLException e
) {
153 String message
= "defaultMediaUrl '%s' is not a valid URL";
154 message
= String
.format(message
, baseUrl
);
155 fireWarningEvent(message
, next
, 8);
157 } else if (isStartingElement(next
, MODS
)){
158 modsImport
.handleMods(state
, reader
, next
);
160 handleUnexpectedElement(next
);
163 throw new IllegalStateException("MetaData has no ending element");
167 private void handleTreatment(MarkupImportState state
, XMLEventReader reader
, XMLEvent parentEvent
) throws XMLStreamException
{
168 checkNoAttributes(parentEvent
);
169 Taxon lastTaxon
= null;
170 while (reader
.hasNext()) {
171 XMLEvent next
= readNoWhitespace(reader
);
172 if (isMyEndingElement(next
, parentEvent
)) {
173 Set
<PolytomousKeyNode
> keyNodesToSave
= state
.getPolytomousKeyNodesToSave();
174 //better save the key then the nodes
175 Set
<PolytomousKey
> keySet
= new HashSet
<PolytomousKey
>();
176 for (PolytomousKeyNode node
: keyNodesToSave
){
177 PolytomousKey key
= node
.getKey();
181 //unmatched key leads
182 UnmatchedLeads unmatched
= state
.getUnmatchedLeads();
183 if (unmatched
.size() > 0){
184 String message
= "The following %d key leads are unmatched: %s";
185 message
= String
.format(message
, unmatched
.size(), state
.getUnmatchedLeads().toString());
186 fireWarningEvent(message
, next
, 6);
188 // save(keyNodesToSave, state);
191 } else if (isStartingElement(next
, TAXON
)) {
192 state
.setCurrentTaxonExcluded(false);
193 Taxon thisTaxon
= handleTaxon(state
, reader
, next
.asStartElement());
194 doTaxonRelation(state
, thisTaxon
, lastTaxon
, parentEvent
.getLocation());
195 if (state
.isTaxonInClassification() == true){
196 lastTaxon
= thisTaxon
;
197 // TODO for imports spanning multiple documents ?? Still needed?
198 state
.getConfig().setLastTaxonUuid(lastTaxon
.getUuid());
200 } else if (isStartingElement(next
, ADDENDA
)) {
201 handleNotYetImplementedElement(next
);
203 handleUnexpectedElement(next
);
213 private TaxonNode
doTaxonRelation(MarkupImportState state
, Taxon taxon
, Taxon lastTaxon
, Location dataLocation
) {
215 if (state
.isTaxonInClassification() == false){
219 boolean excluded
= state
.isCurrentTaxonExcluded();
221 Classification tree
= makeTree(state
, dataLocation
);
222 if (lastTaxon
== null) {
223 node
= tree
.addChildTaxon(taxon
, null, null);
224 node
.setExcluded(excluded
);
227 Rank thisRank
= taxon
.getName().getRank();
228 Rank lastRank
= lastTaxon
.getName().getRank();
229 if (lastRank
== null){
230 String message
= "Last rank was null. Can't create tree correctly";
231 fireWarningEvent(message
, makeLocationStr(dataLocation
), 12);
233 if (!lastTaxon
.getTaxonNodes().isEmpty()) {
234 TaxonNode lastNode
= lastTaxon
.getTaxonNodes().iterator().next();
235 if (thisRank
== null){
236 String message
= "Rank is undefined for taxon '%s'. Can't create classification without rank.";
237 message
= String
.format(message
, taxon
.getName().getTitleCache());
238 fireWarningEvent(message
, makeLocationStr(dataLocation
), 6);
240 }else if (thisRank
.isLower(lastRank
)) {
242 node
= lastNode
.addChildTaxon(taxon
, null, null);
243 fillMissingEpithetsForTaxa(lastTaxon
, taxon
);
244 } else if (thisRank
.equals(lastRank
)) {
245 TaxonNode parent
= lastNode
.getParent();
246 if (parent
!= null && parent
.getTaxon() != null) {
247 node
= parent
.addChildTaxon(taxon
, null, null);
248 fillMissingEpithetsForTaxa(parent
.getTaxon(), taxon
);
250 node
= tree
.addChildTaxon(taxon
, null, null);
252 } else if (thisRank
.isHigher(lastRank
)) {
253 TaxonNode parent
= lastNode
.getParent();
255 node
= doTaxonRelation(state
, taxon
, parent
.getTaxon(), dataLocation
);
257 String warning
= "No parent available for lastNode. Classification can not be build correctly. Maybe the rank was missing for the lastNode";
258 fireWarningEvent(warning
, makeLocationStr(dataLocation
), 16);
259 //TODO what to do in this case (haven't spend time to think about yet
263 // TaxonNode parentNode = handleTaxonRelation(state, taxon,
264 // lastNode.getParent().getTaxon());
265 // parentNode.addChildTaxon(taxon, null, null, null);
267 fireWarningEvent("Unhandled case", makeLocationStr(dataLocation
), 8);
271 String message
= "Last taxon has no node";
272 fireWarningEvent(message
, makeLocationStr(dataLocation
), 6);
277 node
.setExcluded(excluded
);
279 fireWarningEvent("Taxon is excluded but no taxon node can be created", makeLocationStr(dataLocation
), 4);
289 * @param dataLocation
292 private Classification
makeTree(MarkupImportState state
, Location dataLocation
) {
293 Classification result
= state
.getTree(null);
294 if (result
== null) {
295 UUID uuid
= state
.getConfig().getClassificationUuid();
297 String message
= "No classification uuid is defined";
298 fireWarningEvent(message
, makeLocationStr(dataLocation
), 6);
299 result
= createNewClassification(state
);
301 result
= getClassificationService().find(uuid
);
302 if (result
== null) {
303 result
= createNewClassification(state
);
304 result
.setUuid(uuid
);
307 state
.putTree(null, result
);
313 private Classification
createNewClassification(MarkupImportState state
) {
314 Classification result
= Classification
.NewInstance(state
.getConfig().getClassificationName(), getDefaultLanguage(state
));
315 state
.putTree(null, result
);
319 private Taxon
handleTaxon(MarkupImportState state
, XMLEventReader reader
, StartElement parentEvent
) throws XMLStreamException
{
320 // TODO progress monitoring
321 Map
<String
, Attribute
> attributes
= getAttributes(parentEvent
);
322 Taxon taxon
= createTaxonAndName(state
, attributes
, parentEvent
);
323 state
.setCurrentTaxon(taxon
);
324 state
.addNewFeatureSorterLists(taxon
.getUuid().toString());
326 boolean hasTitle
= false;
327 boolean hasNomenclature
= false;
328 String taxonTitle
= null;
330 Reference sourceReference
= state
.getConfig().getSourceReference();
331 while (reader
.hasNext()) {
332 XMLEvent next
= readNoWhitespace(reader
);
333 if (isMyEndingElement(next
, parentEvent
)) {
334 // checkMandatoryElement(hasTitle, parentEvent, TAXONTITLE);
335 checkMandatoryElement(hasNomenclature
, parentEvent
, NOMENCLATURE
);
336 boolean inClassification
= getAndRemoveBooleanAttributeValue(next
, attributes
, "inClassification", true);
337 state
.setTaxonInClassification(inClassification
);
338 handleUnexpectedAttributes(parentEvent
.getLocation(),attributes
);
339 if (taxon
.getName().getRank() == null){
340 String warning
= "No rank exists for taxon " + taxon
.getTitleCache();
341 fireWarningEvent(warning
, next
, 12);
342 taxon
.getName().setRank(Rank
.UNKNOWN_RANK());
345 if (state
.isTaxonIsHybrid() && !taxon
.getName().isHybrid()){
346 fireWarningEvent("Taxon is hybrid but name is not a hybrid name", next
, 4);
348 state
.setTaxonIsHybrid(false);
350 keyImport
.makeKeyNodes(state
, parentEvent
, taxonTitle
);
351 state
.setCurrentTaxon(null);
352 state
.setCurrentTaxonNum(null);
353 if (taxon
.getName().getRank().isHigher(Rank
.GENUS())){
354 state
.setLatestGenusEpithet(null);
356 state
.setLatestGenusEpithet(taxon
.getName().getGenusOrUninomial());
360 } else if (next
.isStartElement()) {
361 if (isStartingElement(next
, HEADING
)) {
362 handleNotYetImplementedElement(next
);
363 } else if (isStartingElement(next
, TAXONTITLE
)) {
364 taxonTitle
= handleTaxonTitle(state
, reader
, next
);
366 } else if (isStartingElement(next
, WRITER
)) {
367 makeKeyWriter(state
, reader
, taxon
, taxonTitle
, next
);
368 } else if (isStartingElement(next
, TEXT_SECTION
)) {
369 handleNotYetImplementedElement(next
);
370 } else if (isStartingElement(next
, KEY
)) {
371 keyImport
.handleKey(state
, reader
, next
);
372 } else if (isStartingElement(next
, NOMENCLATURE
)) {
373 nomenclatureImport
.handleNomenclature(state
, reader
, next
);
374 hasNomenclature
= true;
375 } else if (isStartingElement(next
, FEATURE
)) {
376 featureImport
.handleFeature(state
, reader
, next
);
377 } else if (isStartingElement(next
, NOTES
)) {
378 // TODO is this the correct way to handle notes?
379 String note
= handleNotes(state
, reader
, next
);
383 notesUuid
= state
.getTransformer().getFeatureUuid("notes");
384 Feature feature
= getFeature(state
, notesUuid
, "Notes", "Notes", "note", null);
385 TextData textData
= TextData
.NewInstance(feature
);
386 textData
.addPrimaryTaxonomicSource(sourceReference
);
387 textData
.putText(getDefaultLanguage(state
), note
);
388 TaxonDescription description
= getDefaultTaxonDescription(taxon
, false, true, sourceReference
);
389 description
.addElement(textData
);
390 } catch (UndefinedTransformerMethodException e
) {
391 String message
= "getFeatureUuid method not yet implemented";
392 fireWarningEvent(message
, next
, 8);
394 } else if (isStartingElement(next
, REFERENCES
)) {
395 handleNotYetImplementedElement(next
);
396 } else if (isStartingElement(next
, FIGURE_REF
)) {
397 TaxonDescription desc
= getTaxonDescription(taxon
, sourceReference
, IMAGE_GALLERY
, CREATE_NEW
);
399 if (desc
.getElements().isEmpty()){
400 textData
= TextData
.NewInstance(Feature
.IMAGE());
401 textData
.addPrimaryTaxonomicSource(sourceReference
);
402 desc
.addElement(textData
);
404 textData
= (TextData
)desc
.getElements().iterator().next();
405 featureImport
.makeFeatureFigureRef(state
, reader
, desc
, false, textData
, sourceReference
, next
);
406 } else if (isStartingElement(next
, FIGURE
)) {
407 handleFigure(state
, reader
, next
, specimenImport
, nomenclatureImport
);
408 } else if (isStartingElement(next
, FOOTNOTE
)) {
409 FootnoteDataHolder footnote
= handleFootnote(state
, reader
, next
, specimenImport
, nomenclatureImport
);
410 if (footnote
.isRef()) {
411 String message
= "Ref footnote not implemented here";
412 fireWarningEvent(message
, next
, 4);
414 registerGivenFootnote(state
, footnote
);
417 handleUnexpectedStartElement(next
);
420 handleUnexpectedElement(next
);
423 throw new IllegalStateException("<Taxon> has no closing tag");
432 * @throws XMLStreamException
434 private void makeKeyWriter(MarkupImportState state
, XMLEventReader reader
, Taxon taxon
, String taxonTitle
, XMLEvent next
) throws XMLStreamException
{
435 WriterDataHolder writer
= handleWriter(state
, reader
, next
);
436 if (state
.getConfig().isHandleWriterManually()){
437 fireWarningEvent("<Writer> is expected to be handled manually", next
, 1);
439 taxon
.addExtension(writer
.extension
);
440 // TODO what if taxonTitle comes later
441 taxonTitle
= taxonTitle
!= null ? taxonTitle
: taxon
.getName() == null ?
null : taxon
.getName().getNameCache();
442 if (writer
.extension
!= null) {
443 if (StringUtils
.isBlank(taxonTitle
)){
444 fireWarningEvent("No taxon title defined for writer. Please add sec.title manually.", next
, 6);
447 Reference sec
= ReferenceFactory
.newBookSection();
448 sec
.setTitle(taxonTitle
);
449 TeamOrPersonBase
<?
> author
= createAuthor(state
, writer
.writer
);
450 sec
.setAuthorship(author
);
451 sec
.setInReference(state
.getConfig().getSourceReference());
453 registerFootnotes(state
, sec
, writer
.footnotes
);
455 String message
= "There is no writer extension defined";
456 fireWarningEvent(message
, next
, 6);
461 private String
handleNotes(MarkupImportState state
, XMLEventReader reader
,
462 XMLEvent parentEvent
) throws XMLStreamException
{
463 checkNoAttributes(parentEvent
);
466 while (reader
.hasNext()) {
467 XMLEvent next
= readNoWhitespace(reader
);
468 if (isMyEndingElement(next
, parentEvent
)) {
470 } else if (next
.isStartElement()) {
471 if (isStartingElement(next
, HEADING
)) {
472 handleNotYetImplementedElement(next
);
473 } else if (isStartingElement(next
, SUB_HEADING
)) {
474 String subheading
= getCData(state
, reader
, next
).trim();
475 if (! isNoteHeading(subheading
)) {
476 fireNotYetImplementedElement(next
.getLocation(), next
.asStartElement().getName(), 0);
478 } else if (isStartingElement(next
, WRITER
)) {
479 handleNotYetImplementedElement(next
);
480 } else if (isStartingElement(next
, NUM
)) {
481 handleNotYetImplementedElement(next
);
482 } else if (isStartingElement(next
, STRING
)) {
483 // TODO why multiple strings in schema?
484 text
= makeNotesString(state
, reader
, text
, next
);
486 handleUnexpectedStartElement(next
.asStartElement());
489 handleUnexpectedElement(next
);
492 throw new IllegalStateException("<Notes> has no closing tag");
501 * @throws XMLStreamException
503 private String
makeNotesString(MarkupImportState state
, XMLEventReader reader
, String text
, XMLEvent next
) throws XMLStreamException
{
504 Map
<String
, SubheadingResult
> stringMap
= handleString(state
, reader
, next
, null);
505 if (stringMap
.size() == 0){
506 String message
= "No text available in <notes>";
507 fireWarningEvent(message
, next
, 4);
508 }else if (stringMap
.size() > 1){
509 String message
= "Subheadings not yet supported in <notes>";
510 fireWarningEvent(message
, next
, 4);
512 String firstSubheading
= stringMap
.keySet().iterator().next();
513 if ( firstSubheading
!= null && ! isNoteHeading (firstSubheading
) ) {
514 String message
= "Subheadings not yet supported in <notes>";
515 fireWarningEvent(message
, next
, 4);
518 for (String subheading
: stringMap
.keySet()){
520 text
+= stringMap
.get(subheading
);
525 private boolean isNoteHeading(String heading
) {
526 String excludePattern
= "(i?)(Notes?):?";
527 return heading
.matches(excludePattern
);
535 private Taxon
createTaxonAndName(MarkupImportState state
,
536 Map
<String
, Attribute
> attributes
, StartElement event
) {
538 Rank rank
= null; //Rank.SPECIES(); // default
539 boolean isCultivar
= checkAndRemoveAttributeValue(attributes
, CLASS
, "cultivated");
542 name
= TaxonNameFactory
.NewCultivarInstance(rank
);
544 name
= createNameByCode(state
, rank
);
546 Taxon taxon
= Taxon
.NewInstance(name
, state
.getConfig().getSourceReference());
547 if (checkAndRemoveAttributeValue(attributes
, CLASS
, "dubious")) {
548 taxon
.setDoubtful(true);
549 } else if (checkAndRemoveAttributeValue(attributes
, CLASS
, "excluded")) {
550 state
.setCurrentTaxonExcluded(true);
552 state
.setTaxonIsHybrid(checkAndRemoveAttributeValue(attributes
, CLASS
, "hybrid"));
554 // TODO insufficient, new, expected
555 handleNotYetImplementedAttribute(attributes
, CLASS
, event
);
557 // MarkerType markerType = getMarkerType(state, attrValue);
558 // if (markerType == null){
559 // logger.warn("Class attribute value for taxon not yet supported: " +
562 // taxon.addMarker(Marker.NewInstance(markerType, true));
565 // save(name, state);
566 // save(taxon, state);
570 private String
handleTaxonTitle(MarkupImportState state
, XMLEventReader reader
, XMLEvent parentEvent
) throws XMLStreamException
{
573 Map
<String
, Attribute
> attributes
= getAttributes(parentEvent
);
574 String rankAttr
= getAndRemoveAttributeValue(attributes
, RANK
);
575 Rank rank
= makeRank(state
, rankAttr
, false);
576 String num
= getAndRemoveAttributeValue(attributes
, NUM
);
577 state
.setCurrentTaxonNum(num
);
578 checkNoAttributes(attributes
, parentEvent
);
580 // TODO handle attributes
581 while (reader
.hasNext()) {
582 XMLEvent next
= readNoWhitespace(reader
);
583 if (next
.isEndElement()) {
584 if (isMyEndingElement(next
, parentEvent
)) {
585 Taxon taxon
= state
.getCurrentTaxon();
586 String titleText
= null;
587 if (state
.getConfig().isDoExtensionForTaxonTitle() && checkMandatoryText(text
, parentEvent
)) {
588 titleText
= normalize(text
);
589 UUID uuidTitle
= MarkupTransformer
.uuidTaxonTitle
;
590 ExtensionType titleExtension
= this.getExtensionType(state
, uuidTitle
, "Taxon Title ","taxon title", "title");
591 taxon
.addExtension(titleText
, titleExtension
);
593 taxon
.getName().setRank(rank
);
594 // TODO check title exists
597 if (isEndingElement(next
, FOOTNOTE
)) {
598 // NOT YET IMPLEMENTED
599 popUnimplemented(next
.asEndElement());
601 handleUnexpectedEndElement(next
.asEndElement());
602 state
.setUnsuccessfull();
605 } else if (next
.isStartElement()) {
606 if (isStartingElement(next
, FOOTNOTE
)) {
607 handleNotYetImplementedElement(next
);
608 }else if (isStartingElement(next
, FOOTNOTE_REF
)) {
609 handleNotYetImplementedElement(next
);
611 handleUnexpectedStartElement(next
);
612 state
.setUnsuccessfull();
614 } else if (next
.isCharacters()) {
615 text
+= next
.asCharacters().getData();
618 handleUnexpectedElement(next
);
619 state
.setUnsuccessfull();