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