001/*
002 *  Copyright 2019 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.odf.lheo;
017
018import java.time.LocalDate;
019import java.time.ZonedDateTime;
020import java.time.format.DateTimeFormatter;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Objects;
025import java.util.Optional;
026import java.util.Set;
027import java.util.stream.Collectors;
028import java.util.stream.Stream;
029
030import org.apache.avalon.framework.component.Component;
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.avalon.framework.service.Serviceable;
034import org.apache.cocoon.xml.AttributesImpl;
035import org.apache.cocoon.xml.XMLUtils;
036import org.apache.commons.lang3.StringUtils;
037import org.xml.sax.ContentHandler;
038import org.xml.sax.SAXException;
039
040import org.ametys.cms.data.RichText;
041import org.ametys.cms.data.RichTextHelper;
042import org.ametys.cms.repository.Content;
043import org.ametys.core.util.DateUtils;
044import org.ametys.odf.orgunit.OrgUnit;
045import org.ametys.odf.person.Person;
046import org.ametys.odf.program.AbstractProgram;
047import org.ametys.plugins.repository.AmetysObjectResolver;
048import org.ametys.runtime.plugin.component.AbstractLogEnabled;
049
050/**
051 * Manager to generate programs to LHEO XML
052 */
053public class ExportToLHEOManager extends AbstractLogEnabled implements Serviceable, Component
054{
055    /** The Avalon role */
056    public static final String ROLE = ExportToLHEOManager.class.getName();
057    
058    /** The right to export a program to LHEO */
059    public static final String RIGHT_EXPORT_LHEO = "ODF_Rights_ExportLHEO";
060    
061    /** The ametys object resolver */
062    protected AmetysObjectResolver _resolver;
063    
064    /** The rich text helper */
065    protected RichTextHelper _richTextHelper;
066    
067    /** The LHEO utils */
068    protected LHEOUtils _lheoUtils;
069    
070    @Override
071    public void service(ServiceManager manager) throws ServiceException
072    {
073        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
074        _richTextHelper = (RichTextHelper) manager.lookup(RichTextHelper.ROLE);
075        _lheoUtils = (LHEOUtils) manager.lookup(LHEOUtils.ROLE);
076    }
077    
078    /**
079     * Sax the LHEO xml for the list of programs
080     * @param contentHandler the content handler
081     * @param programs the list of program to sax
082     * @throws SAXException if a saxing exception occurred
083     */
084    public void saxLHEO(ContentHandler contentHandler, List<AbstractProgram> programs) throws SAXException
085    {
086        saxLHEO(contentHandler, programs, new HashMap<>());
087    }
088    
089    /**
090     * Sax the LHEO xml for the list of programs
091     * @param contentHandler the content handler
092     * @param programs the list of program to sax
093     * @param additionalParameters the additional parameters
094     * @throws SAXException if a saxing exception occurred
095     */
096    public void saxLHEO(ContentHandler contentHandler, List<AbstractProgram> programs, Map<String, Object> additionalParameters) throws SAXException
097    {
098        contentHandler.startDocument();
099        contentHandler.startPrefixMapping("", "http://www.lheo.org/2.2");
100            
101        XMLUtils.startElement(contentHandler, "lheo");
102        _saxOffers(contentHandler, programs, additionalParameters);
103        XMLUtils.endElement(contentHandler, "lheo");
104        
105        contentHandler.endDocument();
106    }
107
108    /**
109     * Sax the XML tag &lt;offres&gt;
110     * <br>Can contain the following XML tags:
111     * <br>[1,N] &lt;formation&gt;
112     * <br>[0,N] &lt;extras&gt;
113     * @param contentHandler the content handler
114     * @param programs the list of program to sax
115     * @param additionalParameters the additional parameters
116     * @throws SAXException if a saxing exception occurred
117     */
118    protected void _saxOffers(ContentHandler contentHandler, List<AbstractProgram> programs, Map<String, Object> additionalParameters) throws SAXException
119    {
120        XMLUtils.startElement(contentHandler, "offres");
121        _saxPrograms(contentHandler, programs, additionalParameters);
122        _saxOffersExtras(contentHandler, programs, additionalParameters);
123        XMLUtils.endElement(contentHandler, "offres");
124    }
125
126    /**
127     * Sax for each program a XML tag &lt;formation&gt;
128     * @param contentHandler the content handler
129     * @param programs the list of program to sax
130     * @param additionalParameters the additional parameters
131     * @throws SAXException if a saxing exception occurred
132     */
133    protected void _saxPrograms(ContentHandler contentHandler, List<AbstractProgram> programs, Map<String, Object> additionalParameters) throws SAXException
134    {
135        for (AbstractProgram program : programs)
136        {
137            _saxProgram(contentHandler, program, additionalParameters);
138        }
139    }
140    
141    /**
142     * Sax a XML tag &lt;formation&gt;
143     * <br>Can contain the following XML tags:
144     * <br>[1,1] &lt;domaine-formation&gt;
145     * <br>[1,1] &lt;intitule-formation&gt;
146     * <br>[1,1] &lt;objectif-formation&gt;
147     * <br>[1,1] &lt;resultats-attendus&gt;
148     * <br>[1,1] &lt;contenu-formation&gt;
149     * <br>[1,1] &lt;certifiante&gt;
150     * <br>[1,1] &lt;contact-formation&gt;
151     * <br>[1,1] &lt;parcours-de-formation&gt;
152     * <br>[1,1] &lt;code-niveau-entree&gt;
153     * <br>[0,1] &lt;objectif-general-formation&gt;
154     * <br>[0,5] &lt;certification&gt;
155     * <br>[0,1] &lt;code-niveau-sortie&gt;
156     * <br>[0,1] &lt;url-formation&gt;
157     * <br>[1,N] &lt;action&gt;
158     * <br>[1,1] &lt;organisme-formation-responsable&gt;
159     * <br>[0,1] &lt;identifiant-module&gt;
160     * <br>[0,1] &lt;positionnement&gt;
161     * <br>[0,1] &lt;sous-modules&gt;
162     * <br>[0,1] &lt;modules-prerequis&gt;
163     * <br>[0,N] &lt;extras&gt;
164     * @param contentHandler the content handler
165     * @param program the program to sax
166     * @param additionalParameters the additional parameters
167     * @throws SAXException if a saxing exception occurred
168     */
169    protected void _saxProgram(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
170    {
171        XMLUtils.startElement(contentHandler, "formation", _getProgramAttributes(program, additionalParameters));
172        
173        // <domaine-formation>
174        _saxProgramDomain(contentHandler, program, additionalParameters);
175        
176        // <intitule-formation>
177        _saxProgramTitle(contentHandler, program, additionalParameters);
178        
179        // <objectif-formation>
180        _saxProgramObjectives(contentHandler, program, additionalParameters);
181        
182        // <resultats-attendus>
183        _saxProgramExpectedResults(contentHandler, program, additionalParameters);
184        
185        // <contenu-formation>
186        _saxProgramPresentation(contentHandler, program, additionalParameters);
187        
188        // <certifiante>
189        _saxProgramCertifying(contentHandler, program, additionalParameters);
190        
191        // <contact-formation>
192        _saxProgramContact(contentHandler, program, additionalParameters);
193        
194        // <parcours-de-formation>
195        _saxProgramPath(contentHandler, program, additionalParameters);
196        
197        // <code-niveau-entree>
198        _saxProgramEducationEntryLevel(contentHandler, program, additionalParameters);
199        
200        // <certification>
201        _saxProgramCertification(contentHandler, program, additionalParameters);
202
203        // <action>
204        _saxProgramAction(contentHandler, program, additionalParameters);
205        
206        // <organisme-formation-responsable>
207        _saxProgramResponsibleOrgUnit(contentHandler, program, additionalParameters);
208        
209        // <extras>
210        _saxProgramsExtras(contentHandler, program, additionalParameters);
211        
212        XMLUtils.endElement(contentHandler, "formation");
213    }
214
215    /**
216     * Get attribute for XML tag &lt;formation&gt;
217     * @param program the program
218     * @param additionalParameters the additional parameters
219     * @return the attributes for XML tag &lt;formation&gt;
220     */
221    protected AttributesImpl _getProgramAttributes(AbstractProgram program, Map<String, Object> additionalParameters)
222    {
223        AttributesImpl attributesImpl = new AttributesImpl();
224        attributesImpl.addCDATAAttribute("numero", program.getCode());
225        
226        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
227        
228        ZonedDateTime creationDate = program.getCreationDate();
229        LocalDate creationDateToLocalDate = DateUtils.asLocalDate(creationDate.toInstant());
230        attributesImpl.addCDATAAttribute("datecrea", formatter.format(creationDateToLocalDate));
231        
232        ZonedDateTime lastModificationDate = program.getLastModified();
233        LocalDate lastModificationDateToLocalDate = DateUtils.asLocalDate(lastModificationDate.toInstant());
234        attributesImpl.addCDATAAttribute("datemaj", formatter.format(lastModificationDateToLocalDate));
235        
236        return attributesImpl;
237    }
238    
239    /**
240     * Sax the XML tag &lt;domaine-formation&gt;
241     * <br>Can contain the following XML tags:
242     * <br>[0,5] &lt;cpde-FORMACODE&gt;
243     * <br>[0,3] &lt;code-NSF&gt;
244     * <br>[0,5] &lt;code-ROME&gt;
245     * <br>[0,N] &lt;extras&gt;
246     * @param contentHandler the content handler
247     * @param program the program to sax
248     * @param additionalParameters the additional parameters
249     * @throws SAXException if a saxing exception occurred
250     */
251    protected void _saxProgramDomain(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
252    {
253        XMLUtils.startElement(contentHandler, "domaine-formation");
254        
255        // <code-FORMACODE>
256        _saxFORMACODE(contentHandler, program, additionalParameters);
257        
258        // <code-NSF>
259        _saxNSF(contentHandler, program, additionalParameters);
260        
261        // <code-ROME>
262        _saxROME(contentHandler, program, additionalParameters);
263        
264        // <extras>
265        _saxProgramDomainExtras(contentHandler, program, additionalParameters);
266        
267        XMLUtils.endElement(contentHandler, "domaine-formation");
268    }
269
270    /**
271     * Sax the XML tag &lt;code-FORMACODE&gt;
272     * <br>The code contains exactly 5 characters
273     * <br>The tag must contains the attribute "ref"
274     * @param contentHandler the content handler
275     * @param program the program to sax
276     * @param additionalParameters the additional parameters
277     * @throws SAXException if a saxing exception occurred
278     */
279    protected void _saxFORMACODE(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
280    {
281        AttributesImpl attrs = new AttributesImpl();
282        attrs.addCDATAAttribute("ref", "V12"); // The value is fixed for the default implementation
283        String[] formacodes = program.getFORMACODE();
284        if (formacodes.length > 5)
285        {
286            getLogger().warn("[" + program.getTitle() + " (" + program.getId() + ")] The LHEO format can have only 5 FORMACODE");
287        }
288        
289        for (String code : formacodes)
290        {
291            if (StringUtils.isNotBlank(code))
292            {
293                _lheoUtils.createLHEOElement(contentHandler, program, "code-FORMACODE", attrs, code, 5, 5);
294            }
295        }
296    }
297
298    /**
299     * Sax the XML tag &lt;code-NSF&gt;
300     * <br>The code contains exactly 3 characters
301     * @param contentHandler the content handler
302     * @param program the program to sax
303     * @param additionalParameters the additional parameters
304     * @throws SAXException if a saxing exception occurred
305     */
306    protected void _saxNSF(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
307    {
308        String nsfCodeId = program.getNSFCode();
309        if (StringUtils.isNotBlank(nsfCodeId))
310        {
311            Content nsfCodeContent = _resolver.resolveById(nsfCodeId);
312            String code = nsfCodeContent.getValue("code");
313            if (StringUtils.isNotBlank(code))
314            {
315                _lheoUtils.createLHEOElement(contentHandler, program, "code-NSF", code, 3, 3);
316            }
317        }
318    }
319
320    /**
321     * Sax the XML tag &lt;code-ROME&gt;
322     * <br>The code contains exactly 5 characters
323     * <br>The tag can have the attribute "ref". If not, the default value is "V3"
324     * @param contentHandler the content handler
325     * @param program the program to sax
326     * @param additionalParameters the additional parameters
327     * @throws SAXException if a saxing exception occurred
328     */
329    protected void _saxROME(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
330    {
331        String[] romeCodes = program.getRomeCode();
332        if (romeCodes.length > 5)
333        {
334            getLogger().warn("[" + program.getTitle() + " (" + program.getId() + ")] The LHEO format can have only 5 rome code");
335        }
336        
337        for (String romeCodeId : romeCodes)
338        {
339            Content romeCodeContent = _resolver.resolveById(romeCodeId);
340            String code = romeCodeContent.getValue("code");
341            if (StringUtils.isNotBlank(code))
342            {
343                _lheoUtils.createLHEOElement(contentHandler, program, "code-ROME", code, 5, 5);
344            }
345        }
346    }
347    
348    /**
349     * Sax for program domain the XML tag &lt;extras&gt;
350     * <br>Can contains all not LHEO normalized elements
351     * @param contentHandler the content handler
352     * @param program the program to sax
353     * @param additionalParameters the additional parameters
354     * @throws SAXException if a saxing exception occurred
355     */
356    protected void _saxProgramDomainExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
357    {
358        // No extras in default implementation
359        XMLUtils.createElement(contentHandler, "extras");
360    }
361    
362    /**
363     * Sax the XML tag &lt;intitule-formation&gt;
364     * <br>The value contains between 1 to 255 characters
365     * @param contentHandler the content handler
366     * @param program the program to sax
367     * @param additionalParameters the additional parameters
368     * @throws SAXException if a saxing exception occurred
369     */
370    protected void _saxProgramTitle(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
371    {
372        _lheoUtils.createMandatoryLHEOElement(contentHandler, program, "intitule-formation", program.getTitle(), 1, 255);
373    }
374    
375    /**
376     * Sax the XML tag &lt;objectif-formation&gt;
377     * <br>The value contains between 1 to 3000 characters
378     * @param contentHandler the content handler
379     * @param program the program to sax
380     * @param additionalParameters the additional parameters
381     * @throws SAXException if a saxing exception occurred
382     */
383    protected void _saxProgramObjectives(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
384    {
385        String richText2String = _richText2String(program.getObjectives());
386        _lheoUtils.createMandatoryLHEOElement(contentHandler, program, "objectif-formation", richText2String, 1, 3000);
387    }
388    
389    /**
390     * Sax the XML tag &lt;resultats-attendus&gt;
391     * <br>The value contains between 1 to 3000 characters
392     * @param contentHandler the content handler
393     * @param program the program to sax
394     * @param additionalParameters the additional parameters
395     * @throws SAXException if a saxing exception occurred
396     */
397    protected void _saxProgramExpectedResults(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
398    {
399        String richText2String = _richText2String(program.getExpectedResults());
400        _lheoUtils.createMandatoryLHEOElement(contentHandler, program, "resultats-attendus", richText2String, 1, 3000);
401    }
402    
403    /**
404     * Sax the XML tag &lt;contenu-formation&gt;
405     * <br>The value contains between 1 to 3000 characters
406     * @param contentHandler the content handler
407     * @param program the program to sax
408     * @param additionalParameters the additional parameters
409     * @throws SAXException if a saxing exception occurred
410     */
411    protected void _saxProgramPresentation(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
412    {
413        String richText2String = _richText2String(program.getPresentation());
414        _lheoUtils.createMandatoryLHEOElement(contentHandler, program, "contenu-formation", richText2String, 1, 3000);
415    }
416    
417    /**
418     * Sax the XML tag &lt;certifiante&gt;
419     * <br>0 for false and 1 for true
420     * @param contentHandler the content handler
421     * @param program the program to sax
422     * @param additionalParameters the additional parameters
423     * @throws SAXException if a saxing exception occurred
424     */
425    protected void _saxProgramCertifying(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
426    {
427        boolean certifying = program.isCertifying();
428        String certifyingAsString = certifying ? "1" : "0";
429        _lheoUtils.createLHEOElement(contentHandler, program, "certifiante", certifyingAsString);
430    }
431    
432    /**
433     * Sax the XML tag &lt;contact-formation&gt;
434     * <br>Can contain the following XML tags:
435     * <br>[1,1] &lt;coordonnees&gt;
436     * <br>[0,N] &lt;extras&gt;
437     * @param contentHandler the content handler
438     * @param program the program to sax
439     * @param additionalParameters the additional parameters
440     * @throws SAXException if a saxing exception occurred
441     */
442    protected void _saxProgramContact(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
443    {
444        XMLUtils.startElement(contentHandler, "contact-formation");
445        
446        // <coordonnees>
447        _saxProgramContactCoordonnees(contentHandler, program, additionalParameters);
448        
449        // <extras>
450        _saxProgramContactExtras(contentHandler, program, additionalParameters);
451        
452        XMLUtils.endElement(contentHandler, "contact-formation");
453    }
454
455    /**
456     * Sax for program contact the XML tag &lt;coordonnees&gt;
457     * <br>Can contain the following XML tags:
458     * <br>[0,1] &lt;civilite&gt;
459     * <br>[0,1] &lt;nom&gt;
460     * <br>[0,1] &lt;prenom&gt;
461     * <br>[0,3] &lt;ligne&gt;
462     * <br>[0,1] &lt;adresse&gt;
463     * <br>[0,1] &lt;telfixe&gt;
464     * <br>[0,1] &lt;portable&gt;
465     * <br>[0,1] &lt;fax&gt;
466     * <br>[0,1] &lt;courriel&gt;
467     * <br>[0,1] &lt;web&gt;
468     * <br>[0,N] &lt;extras&gt;
469     * @param contentHandler the content handler
470     * @param program the program to sax
471     * @param additionalParameters the additional parameters
472     * @throws SAXException if a saxing exception occurred
473     */
474    protected void _saxProgramContactCoordonnees(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
475    {
476        XMLUtils.startElement(contentHandler, "coordonnees");
477        
478        Optional<Person> personOpt = _getFirstContact(program);
479        if (personOpt.isPresent())
480        {
481            Person person = personOpt.get();
482            _saxPersonCoordinate(contentHandler, person, additionalParameters);
483        }
484        
485        XMLUtils.endElement(contentHandler, "coordonnees");
486    }
487    
488    /**
489     * Sax for program contact the XML tag &lt;extras&gt;
490     * <br>Can contains all not LHEO normalized elements
491     * @param contentHandler the content handler
492     * @param program the program to sax
493     * @param additionalParameters the additional parameters
494     * @throws SAXException if a saxing exception occurred
495     */
496    protected void _saxProgramContactExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
497    {
498        // No extras in default implementation
499        XMLUtils.createElement(contentHandler, "extras");
500    }
501    
502    /**
503     * Sax the XML tag &lt;parcours-de-formation&gt;
504     * @param contentHandler the content handler
505     * @param program the program to sax
506     * @param additionalParameters the additional parameters
507     * @throws SAXException if a saxing exception occurred
508     */
509    protected void _saxProgramPath(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
510    {
511        // In default implementation, the path value is always 1 ('En groupe')
512        _lheoUtils.createLHEOElement(contentHandler, program, "parcours-de-formation", "1"); 
513    }
514    
515    /**
516     * Sax the XML tag &lt;code-niveau-entree&gt;
517     * @param contentHandler the content handler
518     * @param program the program to sax
519     * @param additionalParameters the additional parameters
520     * @throws SAXException if a saxing exception occurred
521     */
522    protected void _saxProgramEducationEntryLevel(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
523    {
524        String[] educationLevelEntries = program.getEducationLevelEntry();
525        
526        String educationLevelEntryForLHEO = null;
527        if (educationLevelEntries.length > 0)
528        {
529            String educationLevelEntryId = educationLevelEntries[0]; // Take the first
530            Content educationLevelEntryContent = _resolver.resolveById(educationLevelEntryId);
531            
532            String code = educationLevelEntryContent.getValue("code");
533            educationLevelEntryForLHEO = _convertEducationEntryLevel2LHEO(code);
534        }
535        
536        _lheoUtils.createMandatoryLHEOElement(contentHandler, program, "code-niveau-entree", educationLevelEntryForLHEO); 
537    }
538    
539    /**
540     * Convert the Ametys education entry level code to LHEO
541     * @param code the ametys code
542     * @return the LHEO key value
543     */
544    protected String _convertEducationEntryLevel2LHEO(String code)
545    {
546        // No code or "Bac + 1" or "Inconnu" or "Inferieur ou égal au baccalauréat"
547        if (StringUtils.isBlank(code) || "1".equals(code) || "9".equals(code) || "0".equals(code))
548        {
549            // "information non communiquée"
550            return "0";
551        }
552        // "Bac + 2"
553        else if ("2".equals(code))
554        {
555            // "niveau III (BTS, DUT)"
556            return "6";
557        }
558        // "Bac + 3"
559        else if ("3".equals(code))
560        {
561            // "niveau II (licence ou maîtrise universitaire)"
562            return "7";
563        }
564        // "Bac + 4" or "Bac + 5" or "Bac + 6" or "Bac + 7" or "Bac + 8"
565        else
566        {
567            // "niveau I (supérieur à la maîtrise)"
568            return "8";
569        }
570    }
571    
572    /**
573     * Sax the XML tag &lt;certification&gt;
574     * <br>Can contain the following XML tags:
575     * <br>[0,1] &lt;code-RNCP&gt;
576     * <br>[0,1] &lt;code-CERTIFINFO&gt;
577     * <br>[0,N] &lt;extras&gt;
578     * @param contentHandler the content handler
579     * @param program the program to sax
580     * @param additionalParameters the additional parameters
581     * @throws SAXException if a saxing exception occurred
582     */
583    protected void _saxProgramCertification(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
584    {
585        XMLUtils.startElement(contentHandler, "certification");
586        
587        // <code-RNCP>
588        _saxProgramRNCPCode(contentHandler, program, additionalParameters);
589        
590        // <code-CERTIFINFO>
591        _saxProgramCERTIFINFOCode(contentHandler, program, additionalParameters);
592        
593        // <extras>
594        _saxCertificationExtras(contentHandler, program, additionalParameters);
595        
596        XMLUtils.endElement(contentHandler, "certification");
597    }
598    
599    /**
600     * Sax the XML tag &lt;action&gt;
601     * <br>Can contain the following XML tags:
602     * <br>[1,1] &lt;rythme-formation&gt;
603     * <br>[1,10] &lt;code-public-vise&gt;
604     * <br>[0,1] &lt;info-public-vise&gt;
605     * <br>[1,1] &lt;niveau-entree-obligatoire&gt;
606     * <br>[1,1] &lt;modalites-alternance&gt;
607     * <br>[1,1] &lt;modalites-enseignement&gt;
608     * <br>[1,1] &lt;conditions-specifiques&gt;
609     * <br>[1,1] &lt;prise-en-charge-frais-possible&gt;
610     * <br>[1,1] &lt;lieu-de-formation&gt;
611     * <br>[1,1] &lt;modalites-entrees-sorties&gt;
612     * <br>[0,1] &lt;url-action&gt;
613     * <br>[1,N] &lt;session&gt;
614     * <br>[0,1] &lt;adresse-information&gt;
615     * <br>[0,3] &lt;date-information&gt;
616     * <br>[0,1] &lt;restauration&gt;
617     * <br>[0,1] &lt;hebergement&gt;
618     * <br>[0,1] &lt;transport&gt;
619     * <br>[0,1] &lt;acces-handicapes&gt;
620     * <br>[0,1] &lt;langue-formation&gt;
621     * <br>[0,1] &lt;modalites-recrutement&gt;
622     * <br>[0,1] &lt;modalites-pedagogiques&gt;
623     * <br>[0,5] &lt;code-modalite-pedagogique&gt;
624     * <br>[0,1] &lt;frais-restants&gt;
625     * <br>[0,1] &lt;code-perimetre-recrutement&gt;
626     * <br>[0,1] &lt;infos-perimetre-recrutement&gt;
627     * <br>[0,1] &lt;prix-horaire-TTC&gt;
628     * <br>[0,1] &lt;prix-total-TTC&gt;
629     * <br>[0,1] &lt;duree-indicative&gt;
630     * <br>[0,1] &lt;nombre-heures-centre&gt;
631     * <br>[0,1] &lt;nombre-heures-entreprise&gt;
632     * <br>[0,1] &lt;nombre-heures-total&gt;
633     * <br>[0,1] &lt;detail-conditions-prise-en-charge&gt;
634     * <br>[0,1] &lt;conventionnement&gt;
635     * <br>[0,1] &lt;duree-conventionnee&gt;
636     * <br>[0,1] &lt;organisme-formateur&gt;
637     * <br>[0,8] &lt;organisme-financeur&gt;
638     * <br>[0,N] &lt;extras&gt;
639     * @param contentHandler the content handler
640     * @param program the program to sax
641     * @param additionalParameters the additional parameters
642     * @throws SAXException if a saxing exception occurred
643     */
644    protected void _saxProgramAction(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
645    {
646        XMLUtils.startElement(contentHandler, "action", _getActionAttributes(program, additionalParameters));
647        
648        // <rythme-formation>
649        _saxProgramTiming(contentHandler, program, additionalParameters);
650        
651        // <code-public-vise>
652        _saxProgramTargetAudience(contentHandler, program, additionalParameters);
653        
654        // <niveau-entree-obligatoire>
655        _saxProgramMandatoryEntryLevel(contentHandler, program, additionalParameters);
656        
657        // <modalites-alternance>
658        _saxProgramApprenticeshipModalities(contentHandler, program, additionalParameters);
659        
660        // <modalites-enseignement>
661        _saxProrgamDistanceLearning(contentHandler, program, additionalParameters);
662        
663        // <conditions-specifiques>
664        _saxProgramNeededPrerequisite(contentHandler, program, additionalParameters);
665        
666        // <prise-en-charge-frais-possible>
667        _saxProgramCostBearing(contentHandler, program, additionalParameters);
668        
669        // <lieu-de-formation>
670        _saxProgramPlaces(contentHandler, program, additionalParameters);
671        
672        // <modalites-entrees-sorties>
673        _saxProgramEntryExitModalities(contentHandler, program, additionalParameters);
674        
675        // <session>
676        _saxProgramSession(contentHandler, program, additionalParameters);
677        
678        // <langue-formation>
679        _saxProgramEducationLanguage(contentHandler, program, additionalParameters);
680
681        // <modalites-recrutement>
682        _saxProgramAccessCondition(contentHandler, program, additionalParameters);
683        
684        // <code-modalite-pedagogique>
685        _saxProgramEducationalModalities(contentHandler, program, additionalParameters);
686        
687        // <organisme-formateur>
688        _saxProgramOrgUnitFormer(contentHandler, program, additionalParameters);
689        
690        // <organisme-financeur>
691        _saxProgramOrgUnitFunder(contentHandler, program, additionalParameters);
692        
693        // <extras>
694        _saxActionExtras(contentHandler, program, additionalParameters);
695        
696        XMLUtils.endElement(contentHandler, "action");
697    }
698    
699    /**
700     * Get attribute for XML tag &lt;action&gt;
701     * @param program the program
702     * @param additionalParameters the additional parameters
703     * @return the attributes for XML tag &lt;action&gt;
704     */
705    protected AttributesImpl _getActionAttributes(AbstractProgram program, Map<String, Object> additionalParameters)
706    {
707        AttributesImpl attributesImpl = new AttributesImpl();
708        attributesImpl.addCDATAAttribute("numero", program.getCode());
709        
710        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
711        
712        ZonedDateTime creationDate = program.getCreationDate();
713        LocalDate creationDateToLocalDate = DateUtils.asLocalDate(creationDate.toInstant());
714        attributesImpl.addCDATAAttribute("datecrea", formatter.format(creationDateToLocalDate));
715        
716        ZonedDateTime lastModificationDate = program.getLastModified();
717        LocalDate lastModificationDateToLocalDate = DateUtils.asLocalDate(lastModificationDate.toInstant());
718        attributesImpl.addCDATAAttribute("datemaj", formatter.format(lastModificationDateToLocalDate));
719        
720        return attributesImpl;
721    }
722
723    /**
724     * Sax the XML tag &lt;rythme-formation&gt;
725     * <br>The value contains between 1 to 3000 characters
726     * @param contentHandler the content handler
727     * @param program the program to sax
728     * @param additionalParameters the additional parameters
729     * @throws SAXException if a saxing exception occurred
730     */
731    protected void _saxProgramTiming(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
732    {
733        // In default implementation, the program timing value is always "Temps plein"
734        _lheoUtils.createLHEOElement(contentHandler, program, "rythme-formation", "Temps plein");
735    }
736
737    /**
738     * Sax the XML tag &lt;code-public-vise&gt;
739     * <br>The code contains exactly 5 characters
740     * <br>The tag must contains the attribute "ref"
741     * @param contentHandler the content handler
742     * @param program the program to sax
743     * @param additionalParameters the additional parameters
744     * @throws SAXException if a saxing exception occurred
745     */
746    protected void _saxProgramTargetAudience(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
747    {
748        AttributesImpl attrs = new AttributesImpl();
749        attrs.addCDATAAttribute("ref", "V12"); // The value is fixed for the default implementation
750        String[] formacodes = program.getFORMACODE();
751        if (formacodes.length > 10)
752        {
753            getLogger().warn("[" + program.getTitle() + " (" + program.getId() + ")] The LHEO format can have only 10 FORMACODE for XML tag 'code-public-vise'");
754        }
755        if (formacodes.length == 0)
756        {
757            getLogger().warn("[" + program.getTitle() + " (" + program.getId() + ")] The LHEO format must have at least one FORMACODE for XML tag 'code-public-vise'");
758        }
759        
760        for (String code : formacodes)
761        {
762            if (StringUtils.isNotBlank(code))
763            {
764                _lheoUtils.createLHEOElement(contentHandler, program, "code-public-vise", attrs, code, 5, 5);
765            }
766        }
767    }
768
769    /**
770     * Sax the XML tag &lt;niveau-entree-obligatoire&gt;
771     * <br>0 for false and 1 for true
772     * @param contentHandler the content handler
773     * @param program the program to sax
774     * @param additionalParameters the additional parameters
775     * @throws SAXException if a saxing exception occurred
776     */
777    protected void _saxProgramMandatoryEntryLevel(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
778    {
779        boolean mandatoryEntryLevel = program.isMandatoryEntryLevel();
780        String mandatoryEntryLevelAsString = mandatoryEntryLevel ? "1" : "0";
781        _lheoUtils.createLHEOElement(contentHandler, program, "niveau-entree-obligatoire", mandatoryEntryLevelAsString);
782    }
783
784    /**
785     * Sax the XML tag &lt;modalites-alternance&gt;
786     * <br>The value contains between 1 to 3000 characters
787     * @param contentHandler the content handler
788     * @param program the program to sax
789     * @param additionalParameters the additional parameters
790     * @throws SAXException if a saxing exception occurred
791     */
792    protected void _saxProgramApprenticeshipModalities(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
793    {
794        String richText2String = _richText2String(program.getApprenticeshipModalities());
795        _lheoUtils.createMandatoryLHEOElement(contentHandler, program, "modalites-alternance", richText2String, 1, 3000);
796    }
797
798    /**
799     * Sax the XML tag &lt;modalites-enseignement&gt;
800     * @param contentHandler the content handler
801     * @param program the program to sax
802     * @param additionalParameters the additional parameters
803     * @throws SAXException if a saxing exception occurred
804     */
805    protected void _saxProrgamDistanceLearning(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
806    {
807        String distanceLearningId = program.getDistanceLearning();
808        String distanceLearningForLHEO = null;
809        if (StringUtils.isNotBlank(distanceLearningId))
810        {
811            Content distanceLearningContent = _resolver.resolveById(distanceLearningId);
812            
813            String code = distanceLearningContent.getValue("code");
814            distanceLearningForLHEO = _convertDistanceLearning2LHEO(code);
815        }
816        _lheoUtils.createMandatoryLHEOElement(contentHandler, program, "modalites-enseignement", distanceLearningForLHEO);
817    }
818
819    /**
820     * Convert the ametys code in LHEO code
821     * @param code the ametys code
822     * @return the LHEO code
823     */
824    protected String _convertDistanceLearning2LHEO(String code)
825    {
826        // "A distance"
827        if ("distanceLearningModalities_mandatory".equals(code))
828        {
829            // "formation entièrement à distance"
830            return "2";
831        }
832        // "Hybride"
833        else if ("distanceLearningModalities_possible".equals(code))
834        {
835            // "formation mixte"
836            return "1";
837        }
838        // "En présence"
839        else if ("distanceLearningModalities_no".equals(code))
840        {
841            // "formation entièrement présentielle"
842            return "0";
843        }
844        
845        return null;
846    }
847
848    /**
849     * Sax the XML tag &lt;conditions-specifiques&gt;
850     * <br>The value contains between 1 to 3000 characters
851     * @param contentHandler the content handler
852     * @param program the program to sax
853     * @param additionalParameters the additional parameters
854     * @throws SAXException if a saxing exception occurred
855     */
856    protected void _saxProgramNeededPrerequisite(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
857    {
858        String richText2String = _richText2String(program.getNeededPrerequisite());
859        _lheoUtils.createMandatoryLHEOElement(contentHandler, program, "conditions-specifiques", richText2String, 1, 3000);
860    }
861
862    /**
863     * Sax the XML tag &lt;prise-en-charge-frais-possible&gt;
864     * <br>0 for false and 1 for true
865     * @param contentHandler the content handler
866     * @param program the program to sax
867     * @param additionalParameters the additional parameters
868     * @throws SAXException if a saxing exception occurred
869     */
870    protected void _saxProgramCostBearing(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
871    {
872        // In default implementation, the program cost bearing value is always 0 (false)
873        _lheoUtils.createLHEOElement(contentHandler, program, "prise-en-charge-frais-possible", "0");
874    }
875
876    /**
877     * Sax the XML tag &lt;lieu-de-formation&gt;
878     * <br>Can contain the following XML tags:
879     * <br>[1,1] &lt;coordonnees&gt;
880     * <br>[0,N] &lt;extras&gt;
881     * @param contentHandler the content handler
882     * @param program the program to sax
883     * @param additionalParameters the additional parameters
884     * @throws SAXException if a saxing exception occurred
885     */
886    protected void _saxProgramPlaces(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
887    {
888        XMLUtils.startElement(contentHandler, "lieu-de-formation");
889        
890        // <coordonnees>
891        _saxPlacesCoordonnees(contentHandler, program, additionalParameters);
892        
893        // <extras>
894        _saxPlacesExtras(contentHandler, program, additionalParameters);
895        
896        XMLUtils.endElement(contentHandler, "lieu-de-formation");
897    }
898    
899    /**
900     * Sax for places the XML tag &lt;coordonnees&gt;
901     * <br>Can contain the following XML tags:
902     * <br>[0,1] &lt;civilite&gt;
903     * <br>[0,1] &lt;nom&gt;
904     * <br>[0,1] &lt;prenom&gt;
905     * <br>[0,3] &lt;ligne&gt;
906     * <br>[0,1] &lt;adresse&gt;
907     * <br>[0,1] &lt;telfixe&gt;
908     * <br>[0,1] &lt;portable&gt;
909     * <br>[0,1] &lt;fax&gt;
910     * <br>[0,1] &lt;courriel&gt;
911     * <br>[0,1] &lt;web&gt;
912     * <br>[0,N] &lt;extras&gt;
913     * @param contentHandler the content handler
914     * @param program the program to sax
915     * @param additionalParameters the additional parameters
916     * @throws SAXException if a saxing exception occurred
917     */
918    protected void _saxPlacesCoordonnees(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
919    {
920        XMLUtils.startElement(contentHandler, "coordonnees");
921        
922        List<String> places = Stream.of(program.getPlace())
923            .filter(StringUtils::isNotBlank)
924            .map(id -> _getRefContent(id))
925            .filter(Objects::nonNull)
926            .map(c -> c.getTitle())
927            .collect(Collectors.toList());
928        
929        _lheoUtils.createCoordinateLHEOElementsPart1(
930                contentHandler, 
931                program,
932                null, // civilite 
933                null, // nom
934                null, // prenom
935                places // ligne
936        );
937        
938        XMLUtils.endElement(contentHandler, "coordonnees");
939    }
940    
941    /**
942     * Sax for places the XML tag &lt;extras&gt;
943     * <br>Can contains all not LHEO normalized elements
944     * @param contentHandler the content handler
945     * @param program the program to sax
946     * @param additionalParameters the additional parameters
947     * @throws SAXException if a saxing exception occurred
948     */
949    protected void _saxPlacesExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
950    {
951        // No extras in default implementation
952        XMLUtils.createElement(contentHandler, "extras");
953    }
954
955    /**
956     * Sax the XML tag &lt;modalites-entrees-sorties&gt;
957     * @param contentHandler the content handler
958     * @param program the program to sax
959     * @param additionalParameters the additional parameters
960     * @throws SAXException if a saxing exception occurred
961     */
962    protected void _saxProgramEntryExitModalities(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
963    {
964        // In default implementation, the program entry exit modalities value is always 0 ('entrées/sorties à dates fixes')
965        _lheoUtils.createLHEOElement(contentHandler, program, "modalites-entrees-sorties", "1");
966    }
967
968    /**
969     * Sax the XML tag &lt;session&gt;
970     * <br>Can contain the following XML tags:
971     * <br>[1,1] &lt;periode&gt;
972     * <br>[1,1] &lt;adresse-inscription&gt;
973     * <br>[0,1] &lt;modalites-inscription&gt;
974     * <br>[0,1] &lt;periode-inscription&gt;
975     * <br>[0,1] &lt;etat-recrutement&gt;
976     * <br>[0,N] &lt;extras&gt;
977     * @param contentHandler the content handler
978     * @param program the program to sax
979     * @param additionalParameters the additional parameters
980     * @throws SAXException if a saxing exception occurred
981     */
982    protected void _saxProgramSession(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
983    {
984        XMLUtils.startElement(contentHandler, "session", _getSessionAttributes(program, additionalParameters));
985        
986        // <periode>
987        _saxProgramPeriod(contentHandler, program, additionalParameters);
988        
989        // <adresse-inscription>
990        _saxProgramRegistrationAddress(contentHandler, program, additionalParameters);
991        
992        // <periode-inscription>
993        _saxProgramInscriptionPeriod(contentHandler, program, additionalParameters);
994        
995        // <extras>
996        _saxSessionExtras(contentHandler, program, additionalParameters);
997        
998        XMLUtils.endElement(contentHandler, "session");
999    }
1000
1001    /**
1002     * Get attribute for XML tag &lt;session&gt;
1003     * @param program the program
1004     * @param additionalParameters the additional parameters
1005     * @return the attributes for XML tag &lt;session&gt;
1006     */
1007    protected AttributesImpl _getSessionAttributes(AbstractProgram program, Map<String, Object> additionalParameters)
1008    {
1009        AttributesImpl attributesImpl = new AttributesImpl();
1010        attributesImpl.addCDATAAttribute("numero", program.getCode());
1011        
1012        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
1013        
1014        ZonedDateTime creationDate = program.getCreationDate();
1015        LocalDate creationDateToLocalDate = DateUtils.asLocalDate(creationDate.toInstant());
1016        attributesImpl.addCDATAAttribute("datecrea", formatter.format(creationDateToLocalDate));
1017        
1018        ZonedDateTime lastModificationDate = program.getLastModified();
1019        LocalDate lastModificationDateToLocalDate = DateUtils.asLocalDate(lastModificationDate.toInstant());
1020        attributesImpl.addCDATAAttribute("datemaj", formatter.format(lastModificationDateToLocalDate));
1021        
1022        return attributesImpl;
1023    }
1024    
1025    /**
1026     * Sax the XML tag &lt;periode&gt;
1027     * <br>Can contain the following XML tags:
1028     * <br>[1,1] &lt;debut&gt;
1029     * <br>[1,1] &lt;fin&gt;
1030     * <br>[0,N] &lt;extras&gt;
1031     * @param contentHandler the content handler
1032     * @param program the program to sax
1033     * @param additionalParameters the additional parameters
1034     * @throws SAXException if a saxing exception occurred
1035     */
1036    protected void _saxProgramPeriod(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1037    {
1038        XMLUtils.startElement(contentHandler, "periode");
1039        
1040        _lheoUtils.createPeriodLHEOElments(contentHandler, program, program.getTeachingStart(), program.getTeachingEnd());
1041        
1042        // <extras>
1043        _saxPeriodExtras(contentHandler, program, additionalParameters);
1044        
1045        XMLUtils.endElement(contentHandler, "periode");
1046    }
1047
1048    /**
1049     * Sax the XML tag &lt;periode-inscription&gt;
1050     * <br>Can contain the following XML tags:
1051     * <br>[1,1] &lt;periode&gt;
1052     * @param contentHandler the content handler
1053     * @param program the program to sax
1054     * @param additionalParameters the additional parameters
1055     * @throws SAXException if a saxing exception occurred
1056     */
1057    protected void _saxProgramInscriptionPeriod(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1058    {
1059        XMLUtils.startElement(contentHandler, "periode-inscription");
1060        
1061        XMLUtils.startElement(contentHandler, "periode");
1062        
1063        _lheoUtils.createPeriodLHEOElments(contentHandler, program, program.getRegistrationStart(), program.getRegistrationDeadline());
1064        
1065        _saxProgramInscriptionPeriodExtras(contentHandler, program, additionalParameters);
1066        
1067        XMLUtils.endElement(contentHandler, "periode");
1068        
1069        XMLUtils.endElement(contentHandler, "periode-inscription");
1070    }
1071    
1072    /**
1073     * Sax for period the XML tag &lt;extras&gt;
1074     * <br>Can contains all not LHEO normalized elements
1075     * @param contentHandler the content handler
1076     * @param program the program to sax
1077     * @param additionalParameters the additional parameters
1078     * @throws SAXException if a saxing exception occurred
1079     */
1080    protected void _saxPeriodExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1081    {
1082        // No extras in default implementation
1083        XMLUtils.createElement(contentHandler, "extras");
1084    }
1085    
1086    /**
1087     * Sax for inscription period the XML tag &lt;extras&gt;
1088     * <br>Can contains all not LHEO normalized elements
1089     * @param contentHandler the content handler
1090     * @param program the program to sax
1091     * @param additionalParameters the additional parameters
1092     * @throws SAXException if a saxing exception occurred
1093     */
1094    protected void _saxProgramInscriptionPeriodExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1095    {
1096        // No extras in default implementation
1097        XMLUtils.createElement(contentHandler, "extras");
1098    }
1099
1100    /**
1101     * Sax the XML tag &lt;adresse-inscription&gt;
1102     * <br>Can contain the following XML tags:
1103     * <br>[1,1] &lt;adresse&gt;
1104     * <br>[0,N] &lt;extras&gt;
1105     * @param contentHandler the content handler
1106     * @param program the program to sax
1107     * @param additionalParameters the additional parameters
1108     * @throws SAXException if a saxing exception occurred
1109     */
1110    protected void _saxProgramRegistrationAddress(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1111    {
1112        XMLUtils.startElement(contentHandler, "adresse-inscription");
1113        
1114        // <adresse>
1115        _saxRegistrationAddress(contentHandler, program, additionalParameters);
1116        
1117        // <extras>
1118        _saxRegistrationAddressExtras(contentHandler, program, additionalParameters);
1119        
1120        XMLUtils.endElement(contentHandler, "adresse-inscription");
1121    }
1122
1123    /**
1124     * Sax the XML tag &lt;adresse&gt;
1125     * <br>Can contain the following XML tags:
1126     * <br>[1,4] &lt;ligne&gt;
1127     * <br>[1,1] &lt;codepostal&gt;
1128     * <br>[1,1] &lt;ville&gt;
1129     * <br>[0,1] &lt;departement&gt;
1130     * <br>[0,1] &lt;code-INSEE-commune&gt;
1131     * <br>[0,1] &lt;code-INSEE-canton&gt;
1132     * <br>[0,1] &lt;region&gt;
1133     * <br>[0,1] &lt;pays&gt;
1134     * <br>[0,1] &lt;geolocalisation&gt;
1135     * <br>[0,N] &lt;extras&gt;
1136     * @param contentHandler the content handler
1137     * @param program the program to sax
1138     * @param additionalParameters the additional parameters
1139     * @throws SAXException if a saxing exception occurred
1140     */
1141    protected void _saxRegistrationAddress(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1142    {
1143        Optional<Person> contactOpt = _getFirstContact(program);
1144        XMLUtils.startElement(contentHandler, "adresse");
1145        
1146        Content contentSaxed = contactOpt.isPresent() ? contactOpt.get() : program;
1147        String address = contactOpt.isPresent() ? contactOpt.get().getValue("address") : null;
1148        String zipCode = contactOpt.isPresent() ? contactOpt.get().getValue("zipCode") : null;
1149        String town = contactOpt.isPresent() ? contactOpt.get().getValue("town") : null;
1150        
1151        _lheoUtils.createAddressLHEOElements(
1152                contentHandler,
1153                contentSaxed,
1154                address, // ligne
1155                zipCode, // codepostal
1156                town, // ville
1157                null, // departement
1158                null, // code-INSEE-commune
1159                null, // code-INSEE-canton
1160                null, // region
1161                null, // pays
1162                null, // geolocalisation/latitude
1163                null // geolocalisation/longitude
1164        );
1165        XMLUtils.endElement(contentHandler, "adresse");
1166    }
1167
1168    /**
1169     * Sax for registration address the XML tag &lt;extras&gt;
1170     * <br>Can contains all not LHEO normalized elements
1171     * @param contentHandler the content handler
1172     * @param program the program to sax
1173     * @param additionalParameters the additional parameters
1174     * @throws SAXException if a saxing exception occurred
1175     */
1176    protected void _saxRegistrationAddressExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1177    {
1178        // No extras in default implementation
1179        XMLUtils.createElement(contentHandler, "extras");
1180    }
1181    
1182    /**
1183     * Sax for action the XML tag &lt;extras&gt;
1184     * <br>Can contains all not LHEO normalized elements
1185     * @param contentHandler the content handler
1186     * @param program the program to sax
1187     * @param additionalParameters the additional parameters
1188     * @throws SAXException if a saxing exception occurred
1189     */
1190    protected void _saxSessionExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1191    {
1192        // No extras in default implementation
1193        XMLUtils.createElement(contentHandler, "extras");
1194    }
1195    
1196    /**
1197     * Sax the XML tag &lt;langue-formation&gt;
1198     * <br>The value contains exactly 2 characters
1199     * @param contentHandler the content handler
1200     * @param program the program to sax
1201     * @param additionalParameters the additional parameters
1202     * @throws SAXException if a saxing exception occurred
1203     */
1204    protected void _saxProgramEducationLanguage(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1205    {
1206        Optional<String> languageOpt = Stream.of(program.getEducationLanguage())
1207                .filter(StringUtils::isNotBlank)
1208                .map(id -> _getRefContent(id))
1209                .filter(Objects::nonNull)
1210                .map(c -> (String) c.getValue("code"))
1211                .filter(StringUtils::isNotBlank)
1212                .findFirst();
1213                
1214        _lheoUtils.createLHEOElement(contentHandler, program, "langue-formation", languageOpt.isPresent() ? languageOpt.get() : null, 2, 2);
1215    }
1216    
1217    /**
1218     * Sax the XML tag &lt;modalites-recrutement&gt;
1219     * <br>The value contains between 1 to 3000 characters
1220     * @param contentHandler the content handler
1221     * @param program the program to sax
1222     * @param additionalParameters the additional parameters
1223     * @throws SAXException if a saxing exception occurred
1224     */
1225    protected void _saxProgramAccessCondition(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1226    {
1227        String richText2String = _richText2String(program.getAccessCondition());
1228        _lheoUtils.createLHEOElement(contentHandler, program, "modalites-recrutement", richText2String, 0, 3000);
1229    }
1230    
1231    /**
1232     * Sax the XML tag &lt;code-modalite-pedagogique&gt;
1233     * <br>The code contains exactly 5 characters
1234     * <br>The tag must contains the attribute "ref"
1235     * @param contentHandler the content handler
1236     * @param program the program to sax
1237     * @param additionalParameters the additional parameters
1238     * @throws SAXException if a saxing exception occurred
1239     */
1240    protected void _saxProgramEducationalModalities(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1241    {
1242        // Nothing in default implementation
1243    }
1244    
1245    /**
1246     * Sax the XML tag &lt;organisme-formateur&gt;
1247     * <br>Can contain the following XML tags:
1248     * <br>[1,4] &lt;SIRET-formateur&gt;
1249     * <br>[1,1] &lt;raison-sociale-formateur&gt;
1250     * <br>[1,1] &lt;contact-formateur&gt;
1251     * <br>[0,1] &lt;potentiel&gt;
1252     * <br>[0,1] &lt;code-UAI-formateur&gt;
1253     * <br>[0,1] &lt;reference-certification&gt;
1254     * <br>[0,N] &lt;extras&gt;
1255     * @param contentHandler the content handler
1256     * @param program the program to sax
1257     * @param additionalParameters the additional parameters
1258     * @throws SAXException if a saxing exception occurred
1259     */
1260    protected void _saxProgramOrgUnitFormer(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1261    {
1262        Optional<OrgUnit> orgunit = _getFirstOrgUnit(program);
1263        if (orgunit.isPresent())
1264        {
1265            XMLUtils.startElement(contentHandler, "organisme-formateur");
1266            
1267            // <SIRET-formateur>
1268            _saxProgramOrgUnitFormerSIRETInformation(contentHandler, program, additionalParameters);
1269            
1270            // <raison-sociale-formateur>
1271            _saxProgramOrgUnitFormerSocialReason(contentHandler, program, additionalParameters);
1272            
1273            // <contact-formateur>
1274            _saxProgramOrgUnitFormerContact(contentHandler, program, additionalParameters);
1275            
1276            // <extras>
1277            _saxProgramOrgUnitFormerExtras(contentHandler, program, additionalParameters);
1278            
1279            XMLUtils.endElement(contentHandler, "organisme-formateur");
1280        }
1281    }
1282    
1283    /**
1284     * Sax the XML tag &lt;SIRET-formateur&gt;
1285     * <br>Can contain the following XML tags:
1286     * <br>[1,1] &lt;SIRET&gt;
1287     * <br>[0,N] &lt;extras&gt;
1288     * @param contentHandler the content handler
1289     * @param program the program to sax
1290     * @param additionalParameters the additional parameters
1291     * @throws SAXException if a saxing exception occurred
1292     */
1293    protected void _saxProgramOrgUnitFormerSIRETInformation(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1294    {
1295        XMLUtils.startElement(contentHandler, "SIRET-formateur");
1296        
1297        // <SIRET>
1298        _saxProgramOrgUnitFormerSIRET(contentHandler, program, additionalParameters);
1299        
1300        // <extras>
1301        _saxProgramOrgUnitFormerSIRETExtras(contentHandler, program, additionalParameters);
1302        
1303        XMLUtils.endElement(contentHandler, "SIRET-formateur");
1304    }
1305    
1306    /**
1307     * Sax the XML tag &lt;SIRET&gt;
1308     * <br>The value contains exactly 14 characters
1309     * @param contentHandler the content handler
1310     * @param program the program to sax
1311     * @param additionalParameters the additional parameters
1312     * @throws SAXException if a saxing exception occurred
1313     */
1314    protected void _saxProgramOrgUnitFormerSIRET(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1315    {
1316        Optional<OrgUnit> orgUnit = _getFirstOrgUnit(program);
1317        _lheoUtils.createMandatoryLHEOElement(contentHandler, orgUnit.isPresent() ? orgUnit.get() : program, "SIRET", orgUnit.isPresent() ? orgUnit.get().getSIRET() : null, 14, 14); 
1318    }
1319
1320    /**
1321     * Sax for SIRET information the XML tag &lt;extras&gt;
1322     * <br>Can contains all not LHEO normalized elements
1323     * @param contentHandler the content handler
1324     * @param program the program to sax
1325     * @param additionalParameters the additional parameters
1326     * @throws SAXException if a saxing exception occurred
1327     */
1328    protected void _saxProgramOrgUnitFormerSIRETExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1329    {
1330        // No extras in default implementation
1331        XMLUtils.createElement(contentHandler, "extras");
1332    }
1333    
1334    /**
1335     * Sax the XML tag &lt;raison-sociale-formateur&gt;
1336     * <br>The value contains between 1 to 255 characters
1337     * @param contentHandler the content handler
1338     * @param program the program to sax
1339     * @param additionalParameters the additional parameters
1340     * @throws SAXException if a saxing exception occurred
1341     */
1342    protected void _saxProgramOrgUnitFormerSocialReason(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1343    {
1344        Optional<OrgUnit> orgUnit = _getFirstOrgUnit(program);
1345        _lheoUtils.createMandatoryLHEOElement(contentHandler, orgUnit.isPresent() ? orgUnit.get() : program, "raison-sociale-formateur", orgUnit.isPresent() ? orgUnit.get().getTitle() : null, 1, 255);
1346    }
1347    
1348    /**
1349     * Sax the XML tag &lt;contact-formateur&gt;
1350     * <br>Can contain the following XML tags:
1351     * <br>[1,1] &lt;coordonnees&gt;
1352     * <br>[0,N] &lt;extras&gt;
1353     * @param contentHandler the content handler
1354     * @param program the program to sax
1355     * @param additionalParameters the additional parameters
1356     * @throws SAXException if a saxing exception occurred
1357     */
1358    protected void _saxProgramOrgUnitFormerContact(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1359    {
1360        XMLUtils.startElement(contentHandler, "contact-formateur");
1361        
1362        // <coordonnees>
1363        _saxProgramOrgUnitFormerContactCoordinates(contentHandler, program, additionalParameters);
1364        
1365        // <extras>
1366        _saxProgramOrgUnitFormerContactExtras(contentHandler, program, additionalParameters);
1367        
1368        XMLUtils.endElement(contentHandler, "contact-formateur");
1369    }
1370    
1371    /**
1372     * Sax for contact former orgUnit the XML tag &lt;coordonnees&gt;
1373     * <br>Can contain the following XML tags:
1374     * <br>[0,1] &lt;civilite&gt;
1375     * <br>[0,1] &lt;nom&gt;
1376     * <br>[0,1] &lt;prenom&gt;
1377     * <br>[0,3] &lt;ligne&gt;
1378     * <br>[0,1] &lt;adresse&gt;
1379     * <br>[0,1] &lt;telfixe&gt;
1380     * <br>[0,1] &lt;portable&gt;
1381     * <br>[0,1] &lt;fax&gt;
1382     * <br>[0,1] &lt;courriel&gt;
1383     * <br>[0,1] &lt;web&gt;
1384     * <br>[0,N] &lt;extras&gt;
1385     * @param contentHandler the content handler
1386     * @param program the program to sax
1387     * @param additionalParameters the additional parameters
1388     * @throws SAXException if a saxing exception occurred
1389     */
1390    protected void _saxProgramOrgUnitFormerContactCoordinates(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1391    {
1392        XMLUtils.startElement(contentHandler, "coordonnees");
1393        
1394        Optional<Person> personOpt = _getFirstOrgUnit(program).stream()
1395            .map(OrgUnit::getContacts)
1396            .flatMap(List::stream)
1397            .filter(StringUtils::isNotBlank)
1398            .map(this::_getPerson)
1399            .filter(Objects::nonNull)
1400            .findFirst();
1401            
1402        if (personOpt.isPresent())
1403        {
1404            Person person = personOpt.get();
1405            _saxPersonCoordinate(contentHandler, person, additionalParameters);
1406        }
1407        
1408        XMLUtils.endElement(contentHandler, "coordonnees");
1409    }
1410    
1411    /**
1412     * Sax for contact former orgUnit the XML tag &lt;extras&gt;
1413     * <br>Can contains all not LHEO normalized elements
1414     * @param contentHandler the content handler
1415     * @param program the program to sax
1416     * @param additionalParameters the additional parameters
1417     * @throws SAXException if a saxing exception occurred
1418     */
1419    protected void _saxProgramOrgUnitFormerContactExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1420    {
1421        // No extras in default implementation
1422        XMLUtils.createElement(contentHandler, "extras");
1423    }
1424    
1425    /**
1426     * Sax for former orgUnit the XML tag &lt;extras&gt;
1427     * <br>Can contains all not LHEO normalized elements
1428     * @param contentHandler the content handler
1429     * @param program the program to sax
1430     * @param additionalParameters the additional parameters
1431     * @throws SAXException if a saxing exception occurred
1432     */
1433    protected void _saxProgramOrgUnitFormerExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1434    {
1435        // No extras in default implementation
1436        XMLUtils.createElement(contentHandler, "extras");
1437    }
1438    
1439    /**
1440     * Sax the XML tag &lt;organisme-financeur&gt;
1441     * <br>Can contain the following XML tags:
1442     * <br>[1,1] &lt;code-financeur&gt;
1443     * <br>[0,1] &lt;nb-places-financees&gt;
1444     * <br>[0,N] &lt;extras&gt;
1445     * @param contentHandler the content handler
1446     * @param program the program to sax
1447     * @param additionalParameters the additional parameters
1448     * @throws SAXException if a saxing exception occurred
1449     */
1450    protected void _saxProgramOrgUnitFunder(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1451    {
1452        // Nothing in default implementation
1453    }
1454    
1455    /**
1456     * Sax for action the XML tag &lt;extras&gt;
1457     * <br>Can contains all not LHEO normalized elements
1458     * @param contentHandler the content handler
1459     * @param program the program to sax
1460     * @param additionalParameters the additional parameters
1461     * @throws SAXException if a saxing exception occurred
1462     */
1463    protected void _saxActionExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1464    {
1465        // No extras in default implementation
1466        XMLUtils.createElement(contentHandler, "extras");
1467    }
1468    
1469    /**
1470     * Sax the XML tag &lt;organisme-formation-responsable&gt;
1471     * <br>Can contain the following XML tags:
1472     * <br>[1,1] &lt;numero-activite&gt;
1473     * <br>[1,1] &lt;SIRET-organisme-formation&gt;
1474     * <br>[1,1] &lt;nom-organisme&gt;
1475     * <br>[1,1] &lt;raison-sociale&gt;
1476     * <br>[1,1] &lt;coordonnees-organisme&gt;
1477     * <br>[1,1] &lt;contact-organisme&gt;
1478     * <br>[0,1] &lt;renseignements-specifiques&gt;
1479     * <br>[0,1] &lt;potentiel&gt;
1480     * <br>[0,N] &lt;extras&gt;
1481     * @param contentHandler the content handler
1482     * @param program the program to sax
1483     * @param additionalParameters the additional parameters
1484     * @throws SAXException if a saxing exception occurred
1485     */
1486    protected void _saxProgramResponsibleOrgUnit(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1487    {
1488        XMLUtils.startElement(contentHandler, "organisme-formation-responsable", _getResponsibleOrgUnitAttribut(program, additionalParameters));
1489        
1490        // <numero-activite>
1491        _saxProgramActivityNumber(contentHandler, program, additionalParameters);
1492        
1493        // <SIRET-organisme-formation>
1494        _saxProgramSIRETInformation(contentHandler, program, additionalParameters);
1495        
1496        // <nom-organisme>
1497        _saxProgramOrgUnitName(contentHandler, program, additionalParameters);
1498        
1499        // <raison-sociale>
1500        _saxProgramCorporateName(contentHandler, program, additionalParameters);
1501        
1502        // <coordonnees-organisme>
1503        _saxProgramOrgUnitDetails(contentHandler, program, additionalParameters);
1504        
1505        // <contact-organisme>
1506        _saxProgramContactOrgUnit(contentHandler, program, additionalParameters);
1507        
1508        // <extras>
1509        _saxResponsibleOrgUnitExtras(contentHandler, program, additionalParameters);
1510        
1511        XMLUtils.endElement(contentHandler, "organisme-formation-responsable");
1512    }
1513    
1514    /**
1515     * Get attribute for XML tag &lt;organisme-formation-responsable&gt;
1516     * @param program the program
1517     * @param additionalParameters the additional parameters
1518     * @return the attributes for XML tag &lt;session&gt;
1519     */
1520    protected AttributesImpl _getResponsibleOrgUnitAttribut(AbstractProgram program, Map<String, Object> additionalParameters)
1521    {
1522        return new AttributesImpl();
1523    }
1524
1525    /**
1526     * Sax the XML tag &lt;numero-activite&gt;
1527     * <br>The value contains exactly 11 characters
1528     * @param contentHandler the content handler
1529     * @param program the program to sax
1530     * @param additionalParameters the additional parameters
1531     * @throws SAXException if a saxing exception occurred
1532     */
1533    protected void _saxProgramActivityNumber(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1534    {
1535        Optional<OrgUnit> orgUnit = _getFirstOrgUnit(program);
1536        
1537        // In default implementation, the program activity number value is always '00000000000'
1538        String activityNumber = orgUnit
1539            .map(c -> c.<String>getValue(OrgUnit.ACTIVITY_NUMBER))
1540            .orElseGet(() -> "00000000000");
1541        
1542        _lheoUtils.createMandatoryLHEOElement(contentHandler, orgUnit.isPresent() ? orgUnit.get() : program, "numero-activite", activityNumber, 11, 11);
1543    }
1544
1545    /**
1546     * Sax the XML tag &lt;SIRET-organisme-formation&gt;
1547     * <br>Can contain the following XML tags:
1548     * <br>[1,1] &lt;SIRET&gt;
1549     * <br>[0,N] &lt;extras&gt;
1550     * @param contentHandler the content handler
1551     * @param program the program to sax
1552     * @param additionalParameters the additional parameters
1553     * @throws SAXException if a saxing exception occurred
1554     */
1555    protected void _saxProgramSIRETInformation(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1556    {
1557        XMLUtils.startElement(contentHandler, "SIRET-organisme-formation");
1558        
1559        // <SIRET>
1560        _saxProgramSIRET(contentHandler, program, additionalParameters);
1561        
1562        // <extras>
1563        _saxSIRETInformationExtras(contentHandler, program, additionalParameters);
1564        
1565        XMLUtils.endElement(contentHandler, "SIRET-organisme-formation");
1566    }
1567
1568    /**
1569     * Sax the XML tag &lt;SIRET&gt;
1570     * <br>The value contains exactly 14 characters
1571     * @param contentHandler the content handler
1572     * @param program the program to sax
1573     * @param additionalParameters the additional parameters
1574     * @throws SAXException if a saxing exception occurred
1575     */
1576    protected void _saxProgramSIRET(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1577    {
1578        Optional<OrgUnit> orgUnit = _getFirstOrgUnit(program);
1579        
1580        _lheoUtils.createMandatoryLHEOElement(contentHandler, orgUnit.isPresent() ? orgUnit.get() : program, "SIRET", orgUnit.isPresent() ? orgUnit.get().getSIRET() : null, 14, 14); 
1581    }
1582
1583    /**
1584     * Sax for SIRET information the XML tag &lt;extras&gt;
1585     * <br>Can contains all not LHEO normalized elements
1586     * @param contentHandler the content handler
1587     * @param program the program to sax
1588     * @param additionalParameters the additional parameters
1589     * @throws SAXException if a saxing exception occurred
1590     */
1591    protected void _saxSIRETInformationExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1592    {
1593        // No extras in default implementation
1594        XMLUtils.createElement(contentHandler, "extras");
1595    }
1596    
1597    /**
1598     * Sax the XML tag &lt;nom-organisme&gt;
1599     * <br>The value contains between 1 to 255 characters
1600     * @param contentHandler the content handler
1601     * @param program the program to sax
1602     * @param additionalParameters the additional parameters
1603     * @throws SAXException if a saxing exception occurred
1604     */
1605    protected void _saxProgramOrgUnitName(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1606    {
1607        Optional<OrgUnit> orgUnit = _getFirstOrgUnit(program);
1608        
1609        _lheoUtils.createMandatoryLHEOElement(contentHandler, orgUnit.isPresent() ? orgUnit.get() : program, "nom-organisme", orgUnit.isPresent() ? orgUnit.get().getTitle() : null, 1, 255);
1610    }
1611
1612    /**
1613     * Sax the XML tag &lt;raison-sociale&gt;
1614     * <br>The value contains between 1 to 255 characters
1615     * @param contentHandler the content handler
1616     * @param program the program to sax
1617     * @param additionalParameters the additional parameters
1618     * @throws SAXException if a saxing exception occurred
1619     */
1620    protected void _saxProgramCorporateName(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1621    {
1622        Optional<OrgUnit> orgUnit = _getFirstOrgUnit(program);
1623        _lheoUtils.createMandatoryLHEOElement(contentHandler, orgUnit.isPresent() ? orgUnit.get() : program, "raison-sociale", orgUnit.isPresent() ? orgUnit.get().getTitle() : null, 1, 255);
1624    }
1625
1626    /**
1627     * Sax the XML tag &lt;coordonnees-organisme&gt;
1628     * <br>Can contain the following XML tags:
1629     * <br>[1,1] &lt;coordonnees&gt;
1630     * <br>[0,N] &lt;extras&gt;
1631     * @param contentHandler the content handler
1632     * @param program the program to sax
1633     * @param additionalParameters the additional parameters
1634     * @throws SAXException if a saxing exception occurred
1635     */
1636    protected void _saxProgramOrgUnitDetails(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1637    {
1638        XMLUtils.startElement(contentHandler, "coordonnees-organisme");
1639        
1640        // <coordonnees>
1641        _saxOrgUnitDetailsCoordinates(contentHandler, program, additionalParameters);
1642        
1643        // <extras>
1644        _saxOrgUnitDetailsExtras(contentHandler, program, additionalParameters);
1645        
1646        XMLUtils.endElement(contentHandler, "coordonnees-organisme");
1647    }
1648
1649    /**
1650     * Sax for orgUnit details the XML tag &lt;coordonnees&gt;
1651     * <br>Can contain the following XML tags:
1652     * <br>[0,1] &lt;civilite&gt;
1653     * <br>[0,1] &lt;nom&gt;
1654     * <br>[0,1] &lt;prenom&gt;
1655     * <br>[0,3] &lt;ligne&gt;
1656     * <br>[0,1] &lt;adresse&gt;
1657     * <br>[0,1] &lt;telfixe&gt;
1658     * <br>[0,1] &lt;portable&gt;
1659     * <br>[0,1] &lt;fax&gt;
1660     * <br>[0,1] &lt;courriel&gt;
1661     * <br>[0,1] &lt;web&gt;
1662     * <br>[0,N] &lt;extras&gt;
1663     * @param contentHandler the content handler
1664     * @param program the program to sax
1665     * @param additionalParameters the additional parameters
1666     * @throws SAXException if a saxing exception occurred
1667     */
1668    protected void _saxOrgUnitDetailsCoordinates(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1669    {
1670        XMLUtils.startElement(contentHandler, "coordonnees");
1671        
1672        Optional<Person> personOpt = _getFirstContactFromOrgUnits(program);
1673        if (personOpt.isPresent())
1674        {
1675            Person person = personOpt.get();
1676            _saxPersonCoordinate(contentHandler, person, additionalParameters);
1677        }
1678        
1679        XMLUtils.endElement(contentHandler, "coordonnees");
1680    }
1681    
1682    /**
1683     * Sax for orgUnit details the XML tag &lt;extras&gt;
1684     * <br>Can contains all not LHEO normalized elements
1685     * @param contentHandler the content handler
1686     * @param program the program to sax
1687     * @param additionalParameters the additional parameters
1688     * @throws SAXException if a saxing exception occurred
1689     */
1690    protected void _saxOrgUnitDetailsExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1691    {
1692        // No extras in default implementation
1693        XMLUtils.createElement(contentHandler, "extras");
1694    }
1695    
1696    /**
1697     * Sax the XML tag &lt;contact-organisme&gt;
1698     * <br>Can contain the following XML tags:
1699     * <br>[1,1] &lt;coordonnees&gt;
1700     * <br>[0,N] &lt;extras&gt;
1701     * @param contentHandler the content handler
1702     * @param program the program to sax
1703     * @param additionalParameters the additional parameters
1704     * @throws SAXException if a saxing exception occurred
1705     */
1706    protected void _saxProgramContactOrgUnit(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1707    {
1708        XMLUtils.startElement(contentHandler, "contact-organisme");
1709        
1710        // <coordonnees>
1711        _saxContactOrgUnitCoordinates(contentHandler, program, additionalParameters);
1712        
1713        // <extras>
1714        _saxContactOrgUnitExtras(contentHandler, program, additionalParameters);
1715        
1716        XMLUtils.endElement(contentHandler, "contact-organisme");
1717    }
1718    
1719    /**
1720     * Sax for contact orgUnit the XML tag &lt;coordonnees&gt;
1721     * <br>Can contain the following XML tags:
1722     * <br>[0,1] &lt;civilite&gt;
1723     * <br>[0,1] &lt;nom&gt;
1724     * <br>[0,1] &lt;prenom&gt;
1725     * <br>[0,3] &lt;ligne&gt;
1726     * <br>[0,1] &lt;adresse&gt;
1727     * <br>[0,1] &lt;telfixe&gt;
1728     * <br>[0,1] &lt;portable&gt;
1729     * <br>[0,1] &lt;fax&gt;
1730     * <br>[0,1] &lt;courriel&gt;
1731     * <br>[0,1] &lt;web&gt;
1732     * <br>[0,N] &lt;extras&gt;
1733     * @param contentHandler the content handler
1734     * @param program the program to sax
1735     * @param additionalParameters the additional parameters
1736     * @throws SAXException if a saxing exception occurred
1737     */
1738    protected void _saxContactOrgUnitCoordinates(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1739    {
1740        XMLUtils.startElement(contentHandler, "coordonnees");
1741        
1742        Optional<Person> personOpt = _getFirstContact(program);
1743        if (personOpt.isPresent())
1744        {
1745            Person person = personOpt.get();
1746            _saxPersonCoordinate(contentHandler, person, additionalParameters);
1747        }
1748        
1749        XMLUtils.endElement(contentHandler, "coordonnees");
1750    }
1751    
1752    /**
1753     * Sax for contact orgUnit the XML tag &lt;extras&gt;
1754     * <br>Can contains all not LHEO normalized elements
1755     * @param contentHandler the content handler
1756     * @param program the program to sax
1757     * @param additionalParameters the additional parameters
1758     * @throws SAXException if a saxing exception occurred
1759     */
1760    protected void _saxContactOrgUnitExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1761    {
1762        // No extras in default implementation
1763        XMLUtils.createElement(contentHandler, "extras");
1764    }
1765    
1766    /**
1767     * Sax for responsible orgUnit the XML tag &lt;extras&gt;
1768     * <br>Can contains all not LHEO normalized elements
1769     * @param contentHandler the content handler
1770     * @param program the program to sax
1771     * @param additionalParameters the additional parameters
1772     * @throws SAXException if a saxing exception occurred
1773     */
1774    protected void _saxResponsibleOrgUnitExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1775    {
1776        // No extras in default implementation
1777        XMLUtils.createElement(contentHandler, "extras");
1778    }
1779    
1780    /**
1781     * Sax the XML tag &lt;code-RNCP&gt;
1782     * <br>The value contains between 1 and 6 characters
1783     * @param contentHandler the content handler
1784     * @param program the program to sax
1785     * @param additionalParameters the additional parameters
1786     * @throws SAXException if a saxing exception occurred
1787     */
1788    protected void _saxProgramRNCPCode(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1789    {
1790        Optional<String> rncpCode = Stream.of(program.getRncpCode())
1791                .filter(StringUtils::isNotBlank)
1792                .findFirst();
1793        
1794        _lheoUtils.createLHEOElement(contentHandler, program, "code-RNCP", rncpCode.isPresent() ? rncpCode.get() : null, 1, 6);
1795    }
1796    
1797    /**
1798     * Sax the XML tag &lt;code-CERTIFINFO&gt;
1799     * <br>The value contains between 1 and 6 characters
1800     * @param contentHandler the content handler
1801     * @param program the program to sax
1802     * @param additionalParameters the additional parameters
1803     * @throws SAXException if a saxing exception occurred
1804     */
1805    protected void _saxProgramCERTIFINFOCode(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1806    {
1807        // No CERTIFINFO code in default implementation
1808    }
1809    
1810    /**
1811     * Sax for certification the XML tag &lt;extras&gt;
1812     * <br>Can contains all not LHEO normalized elements
1813     * @param contentHandler the content handler
1814     * @param program the program to sax
1815     * @param additionalParameters the additional parameters
1816     * @throws SAXException if a saxing exception occurred
1817     */
1818    protected void _saxCertificationExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1819    {
1820        // No extras in default implementation
1821        XMLUtils.createElement(contentHandler, "extras");
1822    }
1823    
1824    /**
1825     * Sax for programs the XML tag &lt;extras&gt;
1826     * <br>Can contains all not LHEO normalized elements
1827     * @param contentHandler the content handler
1828     * @param program the program to sax
1829     * @param additionalParameters the additional parameters
1830     * @throws SAXException if a saxing exception occurred
1831     */
1832    protected void _saxProgramsExtras(ContentHandler contentHandler, AbstractProgram program, Map<String, Object> additionalParameters) throws SAXException
1833    {
1834        // No extras in default implementation
1835        XMLUtils.createElement(contentHandler, "extras");
1836    }
1837    
1838    /**
1839     * Sax for offers the XML tag &lt;extras&gt;
1840     * <br>Can contains all not LHEO normalized elements
1841     * @param contentHandler the content handler
1842     * @param programs the list of program to sax
1843     * @param additionalParameters the additional parameters
1844     * @throws SAXException if a saxing exception occurred
1845     */
1846    protected void _saxOffersExtras(ContentHandler contentHandler, List<AbstractProgram> programs, Map<String, Object> additionalParameters) throws SAXException
1847    {
1848        // No extras in default implementation
1849        XMLUtils.createElement(contentHandler, "extras");
1850    }
1851    
1852    /**
1853     * Sax LHEO elements of a ametys person for the XML tag &gt;coordonnees&lt;
1854     * @param contentHandler the content handler
1855     * @param contact the person contact
1856     * @param additionalParameters the additional parameters
1857     * @throws SAXException if a saxing exception occurred
1858     */
1859    protected void _saxPersonCoordinate(ContentHandler contentHandler, Person contact, Map<String, Object> additionalParameters) throws SAXException
1860    {
1861        _lheoUtils.createCoordinateLHEOElementsPart1(
1862                contentHandler,
1863                contact,
1864                contact.getValue("personTitle"), // civilite 
1865                contact.getValue("lastName"), // nom
1866                contact.getValue("givenName"), // prenom
1867                (String) contact.getValue("address") // ligne
1868        );
1869        
1870        XMLUtils.startElement(contentHandler, "adresse");
1871        _lheoUtils.createAddressLHEOElements(
1872                contentHandler,
1873                contact,
1874                (String) contact.getValue("address"), // ligne
1875                contact.getValue("zipCode"), // codepostal
1876                contact.getValue("town"), // ville
1877                null, // departement
1878                null, // code-INSEE-commune
1879                null, // code-INSEE-canton
1880                null, // region
1881                null, // pays
1882                null, // geolocalisation/latitude
1883                null // geolocalisation/longitude
1884        );
1885        XMLUtils.endElement(contentHandler, "adresse");
1886        
1887        _lheoUtils.createCoordinateLHEOElementsPart2(
1888                contentHandler,
1889                contact,
1890                contact.getValue("phone"), // telfix
1891                null, // portable
1892                contact.getValue("fax"), //fax
1893                contact.getValue("mail"), // courriel
1894                contact.getValue("webLinkUrl") // web
1895        );
1896    }
1897    
1898    /**
1899     * Get ref content from id
1900     * @param id the ref content id
1901     * @return the ref content. Null if no exist
1902     */
1903    protected Content _getRefContent(String id)
1904    {
1905        try
1906        {
1907            return _resolver.resolveById(id);
1908        }
1909        catch (Exception e) 
1910        {
1911            getLogger().warn("Can't find person with id " + id, e);
1912            return null;
1913        }
1914    }
1915    
1916    /**
1917     * Get first contact from abstract program
1918     * @param program the abstract program
1919     * @return the first contact if exist
1920     */
1921    protected Optional<Person> _getFirstContact(AbstractProgram program)
1922    {
1923        Set<String> contacts = program.getContacts();
1924        return contacts
1925                .stream()
1926                .filter(StringUtils::isNotBlank)
1927                .map(id -> _getPerson(id))
1928                .filter(Objects::nonNull)
1929                .findFirst();
1930    }
1931    
1932    /**
1933     * Get first contact from orgunits of the abstract program
1934     * @param program the abstract program
1935     * @return the first contact if exist
1936     */
1937    protected Optional<Person> _getFirstContactFromOrgUnits(AbstractProgram program)
1938    {
1939        List<String> orgUnits = program.getOrgUnits();
1940        return orgUnits
1941            .stream()
1942            .filter(StringUtils::isNotBlank)
1943            .map(id -> _getOrgUnit(id))
1944            .filter(Objects::nonNull)
1945            .map(OrgUnit::getContacts)
1946            .flatMap(List::stream)
1947            .filter(StringUtils::isNotBlank)
1948            .map(id -> _getPerson(id))
1949            .filter(Objects::nonNull)
1950            .findFirst();
1951    }
1952    
1953    /**
1954     * Get first orgunit of the abstract program
1955     * @param program the abstract program
1956     * @return the first orgunit
1957     */
1958    protected Optional<OrgUnit> _getFirstOrgUnit(AbstractProgram program)
1959    {
1960        List<String> orgUnits = program.getOrgUnits();
1961        return orgUnits
1962                .stream()
1963                .filter(StringUtils::isNotBlank)
1964                .map(id -> _getOrgUnit(id))
1965                .filter(Objects::nonNull)
1966                .findFirst();
1967    }
1968    
1969    /**
1970     * Get person from id
1971     * @param id the person id
1972     * @return the person content. Null if no exist
1973     */
1974    protected Person _getPerson(String id)
1975    {
1976        try
1977        {
1978            return _resolver.resolveById(id);
1979        }
1980        catch (Exception e) 
1981        {
1982            getLogger().warn("Can't find person with id " + id, e);
1983            return null;
1984        }
1985    }
1986    
1987    /**
1988     * Get orgUnit from id
1989     * @param id the orgUnit id
1990     * @return the orgUnit content. Null if no exist
1991     */
1992    protected OrgUnit _getOrgUnit(String id)
1993    {
1994        try
1995        {
1996            return _resolver.resolveById(id);
1997        }
1998        catch (Exception e) 
1999        {
2000            getLogger().warn("Can't find orgUnit with id " + id, e);
2001            return null;
2002        }
2003    }
2004    
2005    /**
2006     * Convert rich-text to string for LHEO
2007     * @param richText the rich-text
2008     * @return the transformed rich-text
2009     */
2010    protected String _richText2String(RichText richText)
2011    {
2012        return richText != null ? _richTextHelper.richTextToString(richText) : null;
2013    }
2014}