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