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.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Objects;
024import java.util.Optional;
025import java.util.stream.Collectors;
026import java.util.stream.Stream;
027
028import org.apache.avalon.framework.component.Component;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.avalon.framework.service.Serviceable;
032import org.apache.cocoon.xml.AttributesImpl;
033import org.apache.cocoon.xml.XMLUtils;
034import org.apache.commons.lang3.StringUtils;
035import org.xml.sax.ContentHandler;
036import org.xml.sax.SAXException;
037
038import org.ametys.cms.data.RichText;
039import org.ametys.cms.data.RichTextHelper;
040import org.ametys.cms.repository.Content;
041import org.ametys.odf.orgunit.OrgUnit;
042import org.ametys.odf.person.Person;
043import org.ametys.odf.program.Program;
044import org.ametys.plugins.repository.AmetysObjectResolver;
045import org.ametys.runtime.plugin.component.AbstractLogEnabled;
046
047/**
048 * Manager to generate programs to LHEO XML
049 */
050public class ExportToLHEOManager extends AbstractLogEnabled implements Serviceable, Component
051{
052    /** The Avalon role */
053    public static final String ROLE = ExportToLHEOManager.class.getName();
054    
055    /** The ametys object resolver */
056    protected AmetysObjectResolver _resolver;
057    
058    /** The rich text helper */
059    protected RichTextHelper _richTextHelper;
060    
061    @Override
062    public void service(ServiceManager manager) throws ServiceException
063    {
064        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
065        _richTextHelper = (RichTextHelper) manager.lookup(RichTextHelper.ROLE);
066    }
067    
068    /**
069     * Sax the LHEO xml for the list of programs
070     * @param contentHandler the content handler
071     * @param programs the list of program to sax
072     * @throws SAXException if a saxing exception occurred
073     */
074    public void saxLHEO(ContentHandler contentHandler, List<Program> programs) throws SAXException
075    {
076        saxLHEO(contentHandler, programs, new HashMap<>());
077    }
078    
079    /**
080     * Sax the LHEO xml for the list of programs
081     * @param contentHandler the content handler
082     * @param programs the list of program to sax
083     * @param additionalParameters the additional parameters
084     * @throws SAXException if a saxing exception occurred
085     */
086    public void saxLHEO(ContentHandler contentHandler, List<Program> programs, Map<String, Object> additionalParameters) throws SAXException
087    {
088        contentHandler.startDocument();
089        contentHandler.startPrefixMapping("", "http://www.lheo.org/2.2");
090            
091        XMLUtils.startElement(contentHandler, "lheo");
092        _saxOffers(contentHandler, programs, additionalParameters);
093        XMLUtils.endElement(contentHandler, "lheo");
094        
095        contentHandler.endDocument();
096    }
097
098    /**
099     * Sax the XML tag &lt;offres&gt;
100     * <br>Can contain the following XML tags:
101     * <br>[1,N] &lt;formation&gt;
102     * <br>[0,N] &lt;extras&gt;
103     * @param contentHandler the content handler
104     * @param programs the list of program to sax
105     * @param additionalParameters the additional parameters
106     * @throws SAXException if a saxing exception occurred
107     */
108    protected void _saxOffers(ContentHandler contentHandler, List<Program> programs, Map<String, Object> additionalParameters) throws SAXException
109    {
110        XMLUtils.startElement(contentHandler, "offres");
111        _saxPrograms(contentHandler, programs, additionalParameters);
112        _saxOffersExtras(contentHandler, programs, additionalParameters);
113        XMLUtils.endElement(contentHandler, "offres");
114    }
115
116    /**
117     * Sax for each program a XML tag &lt;formation&gt;
118     * @param contentHandler the content handler
119     * @param programs the list of program to sax
120     * @param additionalParameters the additional parameters
121     * @throws SAXException if a saxing exception occurred
122     */
123    protected void _saxPrograms(ContentHandler contentHandler, List<Program> programs, Map<String, Object> additionalParameters) throws SAXException
124    {
125        for (Program program : programs)
126        {
127            _saxProgram(contentHandler, program, additionalParameters);
128        }
129    }
130    
131    /**
132     * Sax a XML tag &lt;formation&gt;
133     * <br>Can contain the following XML tags:
134     * <br>[1,1] &lt;domaine-formation&gt;
135     * <br>[1,1] &lt;intitule-formation&gt;
136     * <br>[1,1] &lt;objectif-formation&gt;
137     * <br>[1,1] &lt;resultats-attendus&gt;
138     * <br>[1,1] &lt;contenu-formation&gt;
139     * <br>[1,1] &lt;certifiante&gt;
140     * <br>[1,1] &lt;contact-formation&gt;
141     * <br>[1,1] &lt;parcours-de-formation&gt;
142     * <br>[1,1] &lt;code-niveau-entree&gt;
143     * <br>[0,1] &lt;objectif-general-formation&gt;
144     * <br>[0,5] &lt;certification&gt;
145     * <br>[0,1] &lt;code-niveau-sortie&gt;
146     * <br>[0,1] &lt;url-formation&gt;
147     * <br>[1,N] &lt;action&gt;
148     * <br>[1,1] &lt;organisme-formation-responsable&gt;
149     * <br>[0,1] &lt;identifiant-module&gt;
150     * <br>[0,1] &lt;positionnement&gt;
151     * <br>[0,1] &lt;sous-modules&gt;
152     * <br>[0,1] &lt;modules-prerequis&gt;
153     * <br>[0,N] &lt;extras&gt;
154     * @param contentHandler the content handler
155     * @param program the program to sax
156     * @param additionalParameters the additional parameters
157     * @throws SAXException if a saxing exception occurred
158     */
159    protected void _saxProgram(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
160    {
161        XMLUtils.startElement(contentHandler, "formation");
162        
163        // <domaine-formation>
164        _saxProgramDomain(contentHandler, program, additionalParameters);
165        
166        // <intitule-formation>
167        _saxProgramTitle(contentHandler, program, additionalParameters);
168        
169        // <objectif-formation>
170        _saxProgramObjectives(contentHandler, program, additionalParameters);
171        
172        // <resultats-attendus>
173        _saxProgramExpectedResults(contentHandler, program, additionalParameters);
174        
175        // <contenu-formation>
176        _saxProgramPresentation(contentHandler, program, additionalParameters);
177        
178        // <certifiante>
179        _saxProgramCertifying(contentHandler, program, additionalParameters);
180        
181        // <contact-formation>
182        _saxProgramContact(contentHandler, program, additionalParameters);
183        
184        // <parcours-de-formation>
185        _saxProgramPath(contentHandler, program, additionalParameters);
186        
187        // <code-niveau-entree>
188        _saxProgramEducationEntryLevel(contentHandler, program, additionalParameters);
189        
190        // <certification>
191        _saxProgramCertification(contentHandler, program, additionalParameters);
192
193        // <action>
194        _saxProgramAction(contentHandler, program, additionalParameters);
195        
196        // <organisme-formation-responsable>
197        _saxProgramResponsibleOrgUnit(contentHandler, program, additionalParameters);
198        
199        // <extras>
200        _saxProgramsExtras(contentHandler, program, additionalParameters);
201        
202        XMLUtils.endElement(contentHandler, "formation");
203    }
204
205    /**
206     * Sax the XML tag &lt;domaine-formation&gt;
207     * <br>Can contain the following XML tags:
208     * <br>[0,5] &lt;cpde-FORMACODE&gt;
209     * <br>[0,3] &lt;code-NSF&gt;
210     * <br>[0,5] &lt;code-ROME&gt;
211     * <br>[0,N] &lt;extras&gt;
212     * @param contentHandler the content handler
213     * @param program the program to sax
214     * @param additionalParameters the additional parameters
215     * @throws SAXException if a saxing exception occurred
216     */
217    protected void _saxProgramDomain(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
218    {
219        XMLUtils.startElement(contentHandler, "domaine-formation");
220        
221        // <code-FORMACODE>
222        _saxFORMACODE(contentHandler, program, additionalParameters);
223        
224        // <code-NSF>
225        _saxNSF(contentHandler, program, additionalParameters);
226        
227        // <code-ROME>
228        _saxROME(contentHandler, program, additionalParameters);
229        
230        // <extras>
231        _saxProgramDomainExtras(contentHandler, program, additionalParameters);
232        
233        XMLUtils.endElement(contentHandler, "domaine-formation");
234    }
235
236    /**
237     * Sax the XML tag &lt;code-FORMACODE&gt;
238     * <br>The code contains exactly 5 characters
239     * <br>The tag must contains the attribute "ref"
240     * @param contentHandler the content handler
241     * @param program the program to sax
242     * @param additionalParameters the additional parameters
243     * @throws SAXException if a saxing exception occurred
244     */
245    protected void _saxFORMACODE(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
246    {
247        AttributesImpl attrs = new AttributesImpl();
248        attrs.addCDATAAttribute("ref", "V12"); // The value is fixed for the default implementation
249        String[] formacodes = program.getFORMACODE();
250        if (formacodes.length > 5)
251        {
252            getLogger().warn("[" + program.getTitle() + " (" + program.getId() + ")] The LHEO format can have only 5 FORMACODE");
253        }
254        
255        for (String code : formacodes)
256        {
257            if (StringUtils.isNotBlank(code))
258            {
259                LHEOUtils.createLHEOElement(contentHandler, program, "code-FORMACODE", attrs, code, 5, 5);
260            }
261        }
262    }
263
264    /**
265     * Sax the XML tag &lt;code-NSF&gt;
266     * <br>The code contains exactly 3 characters
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 _saxNSF(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
273    {
274        String nsfCodeId = program.getNSFCode();
275        if (StringUtils.isNotBlank(nsfCodeId))
276        {
277            Content nsfCodeContent = _resolver.resolveById(nsfCodeId);
278            String code = nsfCodeContent.getValue("code");
279            if (StringUtils.isNotBlank(code))
280            {
281                LHEOUtils.createLHEOElement(contentHandler, program, "code-NSF", code, 3, 3);
282            }
283        }
284    }
285
286    /**
287     * Sax the XML tag &lt;code-ROME&gt;
288     * <br>The code contains exactly 5 characters
289     * <br>The tag can have the attribute "ref". If not, the default value is "V3"
290     * @param contentHandler the content handler
291     * @param program the program to sax
292     * @param additionalParameters the additional parameters
293     * @throws SAXException if a saxing exception occurred
294     */
295    protected void _saxROME(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
296    {
297        String[] romeCodes = program.getRomeCode();
298        if (romeCodes.length > 5)
299        {
300            getLogger().warn("[" + program.getTitle() + " (" + program.getId() + ")] The LHEO format can have only 5 rome code");
301        }
302        
303        for (String romeCodeId : romeCodes)
304        {
305            Content romeCodeContent = _resolver.resolveById(romeCodeId);
306            String code = romeCodeContent.getValue("code");
307            if (StringUtils.isNotBlank(code))
308            {
309                LHEOUtils.createLHEOElement(contentHandler, program, "code-ROME", code, 5, 5);
310            }
311        }
312    }
313    
314    /**
315     * Sax for program domain the XML tag &lt;extras&gt;
316     * <br>Can contains all not LHEO normalized elements
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 _saxProgramDomainExtras(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
323    {
324        // No extras in default implementation
325        XMLUtils.createElement(contentHandler, "extras");
326    }
327    
328    /**
329     * Sax the XML tag &lt;intitule-formation&gt;
330     * <br>The value contains between 1 to 255 characters
331     * @param contentHandler the content handler
332     * @param program the program to sax
333     * @param additionalParameters the additional parameters
334     * @throws SAXException if a saxing exception occurred
335     */
336    protected void _saxProgramTitle(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
337    {
338        LHEOUtils.createMandatoryLHEOElement(contentHandler, program, "intitule-formation", program.getTitle(), 1, 255);
339    }
340    
341    /**
342     * Sax the XML tag &lt;objectif-formation&gt;
343     * <br>The value contains between 1 to 3000 characters
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 _saxProgramObjectives(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
350    {
351        String richText2String = _richText2String(program.getObjectives());
352        LHEOUtils.createMandatoryLHEOElement(contentHandler, program, "objectif-formation", richText2String, 1, 3000);
353    }
354    
355    /**
356     * Sax the XML tag &lt;resultats-attendus&gt;
357     * <br>The value contains between 1 to 3000 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 _saxProgramExpectedResults(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
364    {
365        String richText2String = _richText2String(program.getExpectedResults());
366        LHEOUtils.createMandatoryLHEOElement(contentHandler, program, "resultats-attendus", richText2String, 1, 3000);
367    }
368    
369    /**
370     * Sax the XML tag &lt;contenu-formation&gt;
371     * <br>The value contains between 1 to 3000 characters
372     * @param contentHandler the content handler
373     * @param program the program to sax
374     * @param additionalParameters the additional parameters
375     * @throws SAXException if a saxing exception occurred
376     */
377    protected void _saxProgramPresentation(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
378    {
379        String richText2String = _richText2String(program.getPresentation());
380        LHEOUtils.createMandatoryLHEOElement(contentHandler, program, "contenu-formation", richText2String, 1, 3000);
381    }
382    
383    /**
384     * Sax the XML tag &lt;certifiante&gt;
385     * <br>0 for false and 1 for true
386     * @param contentHandler the content handler
387     * @param program the program to sax
388     * @param additionalParameters the additional parameters
389     * @throws SAXException if a saxing exception occurred
390     */
391    protected void _saxProgramCertifying(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
392    {
393        boolean certifying = program.isCertifying();
394        String certifyingAsString = certifying ? "1" : "0";
395        LHEOUtils.createLHEOElement(contentHandler, program, "certifiante", certifyingAsString);
396    }
397    
398    /**
399     * Sax the XML tag &lt;contact-formation&gt;
400     * <br>Can contain the following XML tags:
401     * <br>[1,1] &lt;coordonnees&gt;
402     * <br>[0,N] &lt;extras&gt;
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 _saxProgramContact(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
409    {
410        XMLUtils.startElement(contentHandler, "contact-formation");
411        
412        // <coordonnees>
413        _saxProgramContactCoordonnees(contentHandler, program, additionalParameters);
414        
415        // <extras>
416        _saxProgramContactExtras(contentHandler, program, additionalParameters);
417        
418        XMLUtils.endElement(contentHandler, "contact-formation");
419    }
420
421    /**
422     * Sax for program contact the XML tag &lt;coordonnees&gt;
423     * <br>Can contain the following XML tags:
424     * <br>[0,1] &lt;civilite&gt;
425     * <br>[0,1] &lt;nom&gt;
426     * <br>[0,1] &lt;prenom&gt;
427     * <br>[0,3] &lt;ligne&gt;
428     * <br>[0,1] &lt;adresse&gt;
429     * <br>[0,1] &lt;telfixe&gt;
430     * <br>[0,1] &lt;portable&gt;
431     * <br>[0,1] &lt;fax&gt;
432     * <br>[0,1] &lt;courriel&gt;
433     * <br>[0,1] &lt;web&gt;
434     * <br>[0,N] &lt;extras&gt;
435     * @param contentHandler the content handler
436     * @param program the program to sax
437     * @param additionalParameters the additional parameters
438     * @throws SAXException if a saxing exception occurred
439     */
440    protected void _saxProgramContactCoordonnees(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
441    {
442        XMLUtils.startElement(contentHandler, "coordonnees");
443        
444        Optional<Person> personOpt = program.getContacts()
445                .stream()
446                .filter(StringUtils::isNotBlank)
447                .map(id -> _getPerson(id))
448                .filter(Objects::nonNull)
449                .findFirst();
450        
451        if (personOpt.isPresent())
452        {
453            Person person = personOpt.get();
454            _saxPersonCoordinate(contentHandler, person, additionalParameters);
455        }
456        
457        XMLUtils.endElement(contentHandler, "coordonnees");
458    }
459    
460    /**
461     * Sax for program contact the XML tag &lt;extras&gt;
462     * <br>Can contains all not LHEO normalized elements
463     * @param contentHandler the content handler
464     * @param program the program to sax
465     * @param additionalParameters the additional parameters
466     * @throws SAXException if a saxing exception occurred
467     */
468    protected void _saxProgramContactExtras(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
469    {
470        // No extras in default implementation
471        XMLUtils.createElement(contentHandler, "extras");
472    }
473    
474    /**
475     * Sax the XML tag &lt;parcours-de-formation&gt;
476     * @param contentHandler the content handler
477     * @param program the program to sax
478     * @param additionalParameters the additional parameters
479     * @throws SAXException if a saxing exception occurred
480     */
481    protected void _saxProgramPath(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
482    {
483        // In default implementation, the path value is always 1 ('En groupe')
484        LHEOUtils.createLHEOElement(contentHandler, program, "parcours-de-formation", "1"); 
485    }
486    
487    /**
488     * Sax the XML tag &lt;code-niveau-entree&gt;
489     * @param contentHandler the content handler
490     * @param program the program to sax
491     * @param additionalParameters the additional parameters
492     * @throws SAXException if a saxing exception occurred
493     */
494    protected void _saxProgramEducationEntryLevel(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
495    {
496        String[] educationLevelEntries = program.getEducationLevelEntry();
497        
498        String educationLevelEntryForLHEO = null;
499        if (educationLevelEntries.length > 0)
500        {
501            String educationLevelEntryId = educationLevelEntries[0]; // Take the first
502            Content educationLevelEntryContent = _resolver.resolveById(educationLevelEntryId);
503            
504            String code = educationLevelEntryContent.getValue("code");
505            educationLevelEntryForLHEO = _convertEducationEntryLevel2LHEO(code);
506        }
507        
508        LHEOUtils.createMandatoryLHEOElement(contentHandler, program, "code-niveau-entree", educationLevelEntryForLHEO); 
509    }
510    
511    /**
512     * Convert the Ametys education entry level code to LHEO
513     * @param code the ametys code
514     * @return the LHEO key value
515     */
516    protected String _convertEducationEntryLevel2LHEO(String code)
517    {
518        // No code or "Bac + 1" or "Inconnu" or "Inferieur ou égal au baccalauréat"
519        if (StringUtils.isBlank(code) || "1".equals(code) || "9".equals(code) || "0".equals(code))
520        {
521            // "information non communiquée"
522            return "0";
523        }
524        // "Bac + 2"
525        else if ("2".equals(code))
526        {
527            // "niveau III (BTS, DUT)"
528            return "6";
529        }
530        // "Bac + 3"
531        else if ("3".equals(code))
532        {
533            // "niveau II (licence ou maîtrise universitaire)"
534            return "7";
535        }
536        // "Bac + 4" or "Bac + 5" or "Bac + 6" or "Bac + 7" or "Bac + 8"
537        else
538        {
539            // "niveau I (supérieur à la maîtrise)"
540            return "8";
541        }
542    }
543    
544    /**
545     * Sax the XML tag &lt;certification&gt;
546     * <br>Can contain the following XML tags:
547     * <br>[0,1] &lt;code-RNCP&gt;
548     * <br>[0,1] &lt;code-CERTIFINFO&gt;
549     * <br>[0,N] &lt;extras&gt;
550     * @param contentHandler the content handler
551     * @param program the program to sax
552     * @param additionalParameters the additional parameters
553     * @throws SAXException if a saxing exception occurred
554     */
555    protected void _saxProgramCertification(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
556    {
557        XMLUtils.startElement(contentHandler, "certification");
558        
559        // <code-RNCP>
560        _saxProgramRNCPCode(contentHandler, program, additionalParameters);
561        
562        // <code-CERTIFINFO>
563        _saxProgramCERTIFINFOCode(contentHandler, program, additionalParameters);
564        
565        // <extras>
566        _saxCertificationExtras(contentHandler, program, additionalParameters);
567        
568        XMLUtils.endElement(contentHandler, "certification");
569    }
570    
571    /**
572     * Sax the XML tag &lt;action&gt;
573     * <br>Can contain the following XML tags:
574     * <br>[1,1] &lt;rythme-formation&gt;
575     * <br>[1,10] &lt;code-public-vise&gt;
576     * <br>[0,1] &lt;info-public-vise&gt;
577     * <br>[1,1] &lt;niveau-entree-obligatoire&gt;
578     * <br>[1,1] &lt;modalites-alternance&gt;
579     * <br>[1,1] &lt;modalites-enseignement&gt;
580     * <br>[1,1] &lt;conditions-specifiques&gt;
581     * <br>[1,1] &lt;prise-en-charge-frais-possible&gt;
582     * <br>[1,1] &lt;lieu-de-formation&gt;
583     * <br>[1,1] &lt;modalites-entrees-sorties&gt;
584     * <br>[0,1] &lt;url-action&gt;
585     * <br>[1,N] &lt;session&gt;
586     * <br>[0,1] &lt;adresse-information&gt;
587     * <br>[0,3] &lt;date-information&gt;
588     * <br>[0,1] &lt;restauration&gt;
589     * <br>[0,1] &lt;hebergement&gt;
590     * <br>[0,1] &lt;transport&gt;
591     * <br>[0,1] &lt;acces-handicapes&gt;
592     * <br>[0,1] &lt;langue-formation&gt;
593     * <br>[0,1] &lt;modalites-recrutement&gt;
594     * <br>[0,1] &lt;modalites-pedagogiques&gt;
595     * <br>[0,5] &lt;code-modalite-pedagogique&gt;
596     * <br>[0,1] &lt;frais-restants&gt;
597     * <br>[0,1] &lt;code-perimetre-recrutement&gt;
598     * <br>[0,1] &lt;infos-perimetre-recrutement&gt;
599     * <br>[0,1] &lt;prix-horaire-TTC&gt;
600     * <br>[0,1] &lt;prix-total-TTC&gt;
601     * <br>[0,1] &lt;duree-indicative&gt;
602     * <br>[0,1] &lt;nombre-heures-centre&gt;
603     * <br>[0,1] &lt;nombre-heures-entreprise&gt;
604     * <br>[0,1] &lt;nombre-heures-total&gt;
605     * <br>[0,1] &lt;detail-conditions-prise-en-charge&gt;
606     * <br>[0,1] &lt;conventionnement&gt;
607     * <br>[0,1] &lt;duree-conventionnee&gt;
608     * <br>[0,1] &lt;organisme-formateur&gt;
609     * <br>[0,8] &lt;organisme-financeur&gt;
610     * <br>[0,N] &lt;extras&gt;
611     * @param contentHandler the content handler
612     * @param program the program to sax
613     * @param additionalParameters the additional parameters
614     * @throws SAXException if a saxing exception occurred
615     */
616    protected void _saxProgramAction(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
617    {
618        XMLUtils.startElement(contentHandler, "action");
619        
620        // <rythme-formation>
621        _saxProgramTiming(contentHandler, program, additionalParameters);
622        
623        // <code-public-vise>
624        _saxProgramTargetAudience(contentHandler, program, additionalParameters);
625        
626        // <niveau-entree-obligatoire>
627        _saxProgramMandatoryEntryLevel(contentHandler, program, additionalParameters);
628        
629        // <modalites-alternance>
630        _saxProgramAlternationModality(contentHandler, program, additionalParameters);
631        
632        // <modalites-enseignement>
633        _saxProrgamDistanceLearning(contentHandler, program, additionalParameters);
634        
635        // <conditions-specifiques>
636        _saxProgramNeededPrerequisite(contentHandler, program, additionalParameters);
637        
638        // <prise-en-charge-frais-possible>
639        _saxProgramCostBearing(contentHandler, program, additionalParameters);
640        
641        // <lieu-de-formation>
642        _saxProgramPlaces(contentHandler, program, additionalParameters);
643        
644        // <modalites-entrees-sorties>
645        _saxProgramEntryExitModalities(contentHandler, program, additionalParameters);
646        
647        // <session>
648        _saxProgramSession(contentHandler, program, additionalParameters);
649        
650        // <langue-formation>
651        _saxProgramEducationLanguage(contentHandler, program, additionalParameters);
652
653        // <modalites-recrutement>
654        _saxProgramAccessCondition(contentHandler, program, additionalParameters);
655        
656        // <extras>
657        _saxActionExtras(contentHandler, program, additionalParameters);
658        
659        XMLUtils.endElement(contentHandler, "action");
660    }
661
662    /**
663     * Sax the XML tag &lt;rythme-formation&gt;
664     * <br>The value contains between 1 to 3000 characters
665     * @param contentHandler the content handler
666     * @param program the program to sax
667     * @param additionalParameters the additional parameters
668     * @throws SAXException if a saxing exception occurred
669     */
670    protected void _saxProgramTiming(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
671    {
672        // In default implementation, the program timing value is always "Temps plein"
673        LHEOUtils.createLHEOElement(contentHandler, program, "rythme-formation", "Temps plein");
674    }
675
676    /**
677     * Sax the XML tag &lt;code-public-vise&gt;
678     * <br>The code contains exactly 5 characters
679     * <br>The tag must contains the attribute "ref"
680     * @param contentHandler the content handler
681     * @param program the program to sax
682     * @param additionalParameters the additional parameters
683     * @throws SAXException if a saxing exception occurred
684     */
685    protected void _saxProgramTargetAudience(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
686    {
687        AttributesImpl attrs = new AttributesImpl();
688        attrs.addCDATAAttribute("ref", "V12"); // The value is fixed for the default implementation
689        String[] formacodes = program.getFORMACODE();
690        if (formacodes.length > 10)
691        {
692            getLogger().warn("[" + program.getTitle() + " (" + program.getId() + ")] The LHEO format can have only 10 FORMACODE for XML tag 'code-public-vise'");
693        }
694        if (formacodes.length == 0)
695        {
696            getLogger().warn("[" + program.getTitle() + " (" + program.getId() + ")] The LHEO format must have at least one FORMACODE for XML tag 'code-public-vise'");
697        }
698        
699        for (String code : formacodes)
700        {
701            if (StringUtils.isNotBlank(code))
702            {
703                LHEOUtils.createLHEOElement(contentHandler, program, "code-public-vise", attrs, code, 5, 5);
704            }
705        }
706    }
707
708    /**
709     * Sax the XML tag &lt;niveau-entree-obligatoire&gt;
710     * <br>0 for false and 1 for true
711     * @param contentHandler the content handler
712     * @param program the program to sax
713     * @param additionalParameters the additional parameters
714     * @throws SAXException if a saxing exception occurred
715     */
716    protected void _saxProgramMandatoryEntryLevel(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
717    {
718        boolean mandatoryEntryLevel = program.isMandatoryEntryLevel();
719        String mandatoryEntryLevelAsString = mandatoryEntryLevel ? "1" : "0";
720        LHEOUtils.createLHEOElement(contentHandler, program, "niveau-entree-obligatoire", mandatoryEntryLevelAsString);
721    }
722
723    /**
724     * Sax the XML tag &lt;modalites-alternance&gt;
725     * <br>The value contains between 1 to 3000 characters
726     * @param contentHandler the content handler
727     * @param program the program to sax
728     * @param additionalParameters the additional parameters
729     * @throws SAXException if a saxing exception occurred
730     */
731    protected void _saxProgramAlternationModality(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
732    {
733        String richText2String = _richText2String(program.getAlternationModality());
734        LHEOUtils.createMandatoryLHEOElement(contentHandler, program, "modalites-alternance", richText2String, 1, 3000);
735    }
736
737    /**
738     * Sax the XML tag &lt;modalites-enseignement&gt;
739     * @param contentHandler the content handler
740     * @param program the program to sax
741     * @param additionalParameters the additional parameters
742     * @throws SAXException if a saxing exception occurred
743     */
744    protected void _saxProrgamDistanceLearning(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
745    {
746        String distanceLearningId = program.getDistanceLearning();
747        String distanceLearningForLHEO = null;
748        if (StringUtils.isNotBlank(distanceLearningId))
749        {
750            Content distanceLearningContent = _resolver.resolveById(distanceLearningId);
751            
752            String code = distanceLearningContent.getValue("code");
753            distanceLearningForLHEO = _convertDistanceLearning2LHEO(code);
754        }
755        LHEOUtils.createMandatoryLHEOElement(contentHandler, program, "modalites-enseignement", distanceLearningForLHEO);
756    }
757
758    /**
759     * Convert the ametys code in LHEO code
760     * @param code the ametys code
761     * @return the LHEO code
762     */
763    protected String _convertDistanceLearning2LHEO(String code)
764    {
765        // "A distance"
766        if ("distanceLearningModalities_mandatory".equals(code))
767        {
768            // "formation entièrement à distance"
769            return "2";
770        }
771        // "Hybride"
772        else if ("distanceLearningModalities_possible".equals(code))
773        {
774            // "formation mixte"
775            return "1";
776        }
777        // "En présence"
778        else if ("distanceLearningModalities_no".equals(code))
779        {
780            // "formation entièrement présentielle"
781            return "0";
782        }
783        
784        return null;
785    }
786
787    /**
788     * Sax the XML tag &lt;conditions-specifiques&gt;
789     * <br>The value contains between 1 to 3000 characters
790     * @param contentHandler the content handler
791     * @param program the program to sax
792     * @param additionalParameters the additional parameters
793     * @throws SAXException if a saxing exception occurred
794     */
795    protected void _saxProgramNeededPrerequisite(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
796    {
797        String richText2String = _richText2String(program.getNeededPrerequisite());
798        LHEOUtils.createMandatoryLHEOElement(contentHandler, program, "conditions-specifiques", richText2String, 1, 3000);
799    }
800
801    /**
802     * Sax the XML tag &lt;prise-en-charge-frais-possible&gt;
803     * <br>0 for false and 1 for true
804     * @param contentHandler the content handler
805     * @param program the program to sax
806     * @param additionalParameters the additional parameters
807     * @throws SAXException if a saxing exception occurred
808     */
809    protected void _saxProgramCostBearing(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
810    {
811        // In default implementation, the program cost bearing value is always 0 (false)
812        LHEOUtils.createLHEOElement(contentHandler, program, "prise-en-charge-frais-possible", "0");
813    }
814
815    /**
816     * Sax the XML tag &lt;lieu-de-formation&gt;
817     * <br>Can contain the following XML tags:
818     * <br>[1,1] &lt;coordonnees&gt;
819     * <br>[0,N] &lt;extras&gt;
820     * @param contentHandler the content handler
821     * @param program the program to sax
822     * @param additionalParameters the additional parameters
823     * @throws SAXException if a saxing exception occurred
824     */
825    protected void _saxProgramPlaces(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
826    {
827        XMLUtils.startElement(contentHandler, "lieu-de-formation");
828        
829        // <coordonnees>
830        _saxPlacesCoordonnees(contentHandler, program, additionalParameters);
831        
832        // <extras>
833        _saxPlacesExtras(contentHandler, program, additionalParameters);
834        
835        XMLUtils.endElement(contentHandler, "lieu-de-formation");
836    }
837    
838    /**
839     * Sax for places the XML tag &lt;coordonnees&gt;
840     * <br>Can contain the following XML tags:
841     * <br>[0,1] &lt;civilite&gt;
842     * <br>[0,1] &lt;nom&gt;
843     * <br>[0,1] &lt;prenom&gt;
844     * <br>[0,3] &lt;ligne&gt;
845     * <br>[0,1] &lt;adresse&gt;
846     * <br>[0,1] &lt;telfixe&gt;
847     * <br>[0,1] &lt;portable&gt;
848     * <br>[0,1] &lt;fax&gt;
849     * <br>[0,1] &lt;courriel&gt;
850     * <br>[0,1] &lt;web&gt;
851     * <br>[0,N] &lt;extras&gt;
852     * @param contentHandler the content handler
853     * @param program the program to sax
854     * @param additionalParameters the additional parameters
855     * @throws SAXException if a saxing exception occurred
856     */
857    protected void _saxPlacesCoordonnees(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
858    {
859        XMLUtils.startElement(contentHandler, "coordonnees");
860        
861        List<String> places = Stream.of(program.getPlace())
862            .filter(StringUtils::isNotBlank)
863            .map(id -> _getRefContent(id))
864            .filter(Objects::nonNull)
865            .map(c -> c.getTitle())
866            .collect(Collectors.toList());
867        
868        LHEOUtils.createCoordinateLHEOElementsPart1(
869                contentHandler, 
870                program,
871                null, // civilite 
872                null, // nom
873                null, // prenom
874                places // ligne
875        );
876        
877        XMLUtils.endElement(contentHandler, "coordonnees");
878    }
879    
880    /**
881     * Sax for places the XML tag &lt;extras&gt;
882     * <br>Can contains all not LHEO normalized elements
883     * @param contentHandler the content handler
884     * @param program the program to sax
885     * @param additionalParameters the additional parameters
886     * @throws SAXException if a saxing exception occurred
887     */
888    protected void _saxPlacesExtras(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
889    {
890        // No extras in default implementation
891        XMLUtils.createElement(contentHandler, "extras");
892    }
893
894    /**
895     * Sax the XML tag &lt;modalites-entrees-sorties&gt;
896     * @param contentHandler the content handler
897     * @param program the program to sax
898     * @param additionalParameters the additional parameters
899     * @throws SAXException if a saxing exception occurred
900     */
901    protected void _saxProgramEntryExitModalities(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
902    {
903        // In default implementation, the program entry exit modalities value is always 0 ('entrées/sorties à dates fixes')
904        LHEOUtils.createLHEOElement(contentHandler, program, "modalites-entrees-sorties", "1");
905    }
906
907    /**
908     * Sax the XML tag &lt;session&gt;
909     * <br>Can contain the following XML tags:
910     * <br>[1,1] &lt;periode&gt;
911     * <br>[1,1] &lt;adresse-inscription&gt;
912     * <br>[0,1] &lt;modalites-inscription&gt;
913     * <br>[0,1] &lt;periode-inscription&gt;
914     * <br>[0,1] &lt;etat-recrutement&gt;
915     * <br>[0,N] &lt;extras&gt;
916     * @param contentHandler the content handler
917     * @param program the program to sax
918     * @param additionalParameters the additional parameters
919     * @throws SAXException if a saxing exception occurred
920     */
921    protected void _saxProgramSession(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
922    {
923        XMLUtils.startElement(contentHandler, "session");
924        
925        // <periode>
926        _saxProgramPeriod(contentHandler, program, additionalParameters);
927        
928        // <adresse-inscription>
929        _saxProgramRegistrationAddress(contentHandler, program, additionalParameters);
930        
931        // <extras>
932        _saxSessionExtras(contentHandler, program, additionalParameters);
933        
934        XMLUtils.endElement(contentHandler, "session");
935    }
936
937    /**
938     * Sax the XML tag &lt;periode&gt;
939     * <br>Can contain the following XML tags:
940     * <br>[1,1] &lt;debut&gt;
941     * <br>[1,1] &lt;fin&gt;
942     * <br>[0,N] &lt;extras&gt;
943     * @param contentHandler the content handler
944     * @param program the program to sax
945     * @param additionalParameters the additional parameters
946     * @throws SAXException if a saxing exception occurred
947     */
948    protected void _saxProgramPeriod(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
949    {
950        XMLUtils.startElement(contentHandler, "periode");
951        
952        // <debut>
953        _saxProgramPeriodStartDate(contentHandler, program, additionalParameters);
954        
955        // <fin>
956        _saxProgramPeriodEndDate(contentHandler, program, additionalParameters);
957        
958        // <extras>
959        _saxPeriodExtras(contentHandler, program, additionalParameters);
960        
961        XMLUtils.endElement(contentHandler, "periode");
962    }
963
964    /**
965     * Sax the XML tag &lt;debut&gt;
966     * <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
967     * @param contentHandler the content handler
968     * @param program the program to sax
969     * @param additionalParameters the additional parameters
970     * @throws SAXException if a saxing exception occurred
971     */
972    protected void _saxProgramPeriodStartDate(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
973    {
974        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
975        LocalDate teachingStart = program.getTeachingStart();
976        LHEOUtils.createMandatoryLHEOElement(contentHandler, program, "debut", teachingStart != null ? formatter.format(teachingStart) : null, 8, 8);
977    }
978    
979    /**
980     * Sax the XML tag &lt;fin&gt;
981     * <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
982     * @param contentHandler the content handler
983     * @param program the program to sax
984     * @param additionalParameters the additional parameters
985     * @throws SAXException if a saxing exception occurred
986     */
987    protected void _saxProgramPeriodEndDate(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
988    {
989        // No end date in default implementation
990        LHEOUtils.createLHEOElement(contentHandler, program, "fin", "99999999");
991    }
992    
993    /**
994     * Sax for period the XML tag &lt;extras&gt;
995     * <br>Can contains all not LHEO normalized elements
996     * @param contentHandler the content handler
997     * @param program the program to sax
998     * @param additionalParameters the additional parameters
999     * @throws SAXException if a saxing exception occurred
1000     */
1001    protected void _saxPeriodExtras(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1002    {
1003        // No extras in default implementation
1004        XMLUtils.createElement(contentHandler, "extras");
1005    }
1006
1007    /**
1008     * Sax the XML tag &lt;adresse-inscription&gt;
1009     * <br>Can contain the following XML tags:
1010     * <br>[1,1] &lt;adresse&gt;
1011     * <br>[0,N] &lt;extras&gt;
1012     * @param contentHandler the content handler
1013     * @param program the program to sax
1014     * @param additionalParameters the additional parameters
1015     * @throws SAXException if a saxing exception occurred
1016     */
1017    protected void _saxProgramRegistrationAddress(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1018    {
1019        XMLUtils.startElement(contentHandler, "adresse-inscription");
1020        
1021        // <adresse>
1022        _saxRegistrationAddress(contentHandler, program, additionalParameters);
1023        
1024        // <extras>
1025        _saxRegistrationAddressExtras(contentHandler, program, additionalParameters);
1026        
1027        XMLUtils.endElement(contentHandler, "adresse-inscription");
1028    }
1029
1030    /**
1031     * Sax the XML tag &lt;adresse&gt;
1032     * <br>Can contain the following XML tags:
1033     * <br>[1,4] &lt;ligne&gt;
1034     * <br>[1,1] &lt;codepostal&gt;
1035     * <br>[1,1] &lt;ville&gt;
1036     * <br>[0,1] &lt;departement&gt;
1037     * <br>[0,1] &lt;code-INSEE-commune&gt;
1038     * <br>[0,1] &lt;code-INSEE-canton&gt;
1039     * <br>[0,1] &lt;region&gt;
1040     * <br>[0,1] &lt;pays&gt;
1041     * <br>[0,1] &lt;geolocalisation&gt;
1042     * <br>[0,N] &lt;extras&gt;
1043     * @param contentHandler the content handler
1044     * @param program the program to sax
1045     * @param additionalParameters the additional parameters
1046     * @throws SAXException if a saxing exception occurred
1047     */
1048    protected void _saxRegistrationAddress(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1049    {
1050        Optional<Person> contactOpt = program.getContacts()
1051                .stream()
1052                .filter(StringUtils::isNotBlank)
1053                .map(id -> _getPerson(id))
1054                .filter(Objects::nonNull)
1055                .findFirst();
1056        
1057        XMLUtils.startElement(contentHandler, "adresse");
1058        
1059        Content contentSaxed = contactOpt.isPresent() ? contactOpt.get() : program;
1060        String address = contactOpt.isPresent() ? contactOpt.get().getValue("address") : null;
1061        String zipCode = contactOpt.isPresent() ? contactOpt.get().getValue("zipCode") : null;
1062        String town = contactOpt.isPresent() ? contactOpt.get().getValue("town") : null;
1063        
1064        LHEOUtils.createAddressLHEOElements(
1065                contentHandler,
1066                contentSaxed,
1067                address, // ligne
1068                zipCode, // codepostal
1069                town, // ville
1070                null, // departement
1071                null, // code-INSEE-commune
1072                null, // code-INSEE-canton
1073                null, // region
1074                null, // pays
1075                null, // geolocalisation/latitude
1076                null // geolocalisation/longitude
1077        );
1078        XMLUtils.endElement(contentHandler, "adresse");
1079    }
1080
1081    /**
1082     * Sax for registration address the XML tag &lt;extras&gt;
1083     * <br>Can contains all not LHEO normalized elements
1084     * @param contentHandler the content handler
1085     * @param program the program to sax
1086     * @param additionalParameters the additional parameters
1087     * @throws SAXException if a saxing exception occurred
1088     */
1089    protected void _saxRegistrationAddressExtras(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1090    {
1091        // No extras in default implementation
1092        XMLUtils.createElement(contentHandler, "extras");
1093    }
1094    
1095    /**
1096     * Sax for action the XML tag &lt;extras&gt;
1097     * <br>Can contains all not LHEO normalized elements
1098     * @param contentHandler the content handler
1099     * @param program the program to sax
1100     * @param additionalParameters the additional parameters
1101     * @throws SAXException if a saxing exception occurred
1102     */
1103    protected void _saxSessionExtras(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1104    {
1105        // No extras in default implementation
1106        XMLUtils.createElement(contentHandler, "extras");
1107    }
1108    
1109    /**
1110     * Sax the XML tag &lt;langue-formation&gt;
1111     * <br>The value contains exactly 2 characters
1112     * @param contentHandler the content handler
1113     * @param program the program to sax
1114     * @param additionalParameters the additional parameters
1115     * @throws SAXException if a saxing exception occurred
1116     */
1117    protected void _saxProgramEducationLanguage(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1118    {
1119        Optional<String> languageOpt = Stream.of(program.getEducationLanguage())
1120                .filter(StringUtils::isNotBlank)
1121                .map(id -> _getRefContent(id))
1122                .filter(Objects::nonNull)
1123                .map(c -> (String) c.getValue("code"))
1124                .filter(StringUtils::isNotBlank)
1125                .findFirst();
1126                
1127        LHEOUtils.createLHEOElement(contentHandler, program, "langue-formation", languageOpt.isPresent() ? languageOpt.get() : null, 2, 2);
1128    }
1129    
1130    /**
1131     * Sax the XML tag &lt;modalites-recrutement&gt;
1132     * <br>The value contains between 1 to 3000 characters
1133     * @param contentHandler the content handler
1134     * @param program the program to sax
1135     * @param additionalParameters the additional parameters
1136     * @throws SAXException if a saxing exception occurred
1137     */
1138    protected void _saxProgramAccessCondition(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1139    {
1140        String richText2String = _richText2String(program.getAccessCondition());
1141        LHEOUtils.createLHEOElement(contentHandler, program, "modalites-recrutement", richText2String, 0, 3000);
1142    }
1143    
1144    /**
1145     * Sax for action the XML tag &lt;extras&gt;
1146     * <br>Can contains all not LHEO normalized elements
1147     * @param contentHandler the content handler
1148     * @param program the program to sax
1149     * @param additionalParameters the additional parameters
1150     * @throws SAXException if a saxing exception occurred
1151     */
1152    protected void _saxActionExtras(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1153    {
1154        // No extras in default implementation
1155        XMLUtils.createElement(contentHandler, "extras");
1156    }
1157    
1158    /**
1159     * Sax the XML tag &lt;organisme-formation-responsable&gt;
1160     * <br>Can contain the following XML tags:
1161     * <br>[1,1] &lt;numero-activite&gt;
1162     * <br>[1,1] &lt;SIRET-organisme-formation&gt;
1163     * <br>[1,1] &lt;nom-organisme&gt;
1164     * <br>[1,1] &lt;raison-sociale&gt;
1165     * <br>[1,1] &lt;coordonnees-organisme&gt;
1166     * <br>[1,1] &lt;contact-organisme&gt;
1167     * <br>[0,1] &lt;renseignements-specifiques&gt;
1168     * <br>[0,1] &lt;potentiel&gt;
1169     * <br>[0,N] &lt;extras&gt;
1170     * @param contentHandler the content handler
1171     * @param program the program to sax
1172     * @param additionalParameters the additional parameters
1173     * @throws SAXException if a saxing exception occurred
1174     */
1175    protected void _saxProgramResponsibleOrgUnit(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1176    {
1177        XMLUtils.startElement(contentHandler, "organisme-formation-responsable");
1178        
1179        // <numero-activite>
1180        _saxProgramActivityNumber(contentHandler, program, additionalParameters);
1181        
1182        // <SIRET-organisme-formation>
1183        _saxProgramSIRETInformation(contentHandler, program, additionalParameters);
1184        
1185        // <nom-organisme>
1186        _saxProgramOrgUnitName(contentHandler, program, additionalParameters);
1187        
1188        // <raison-sociale>
1189        _saxProgramCorporateName(contentHandler, program, additionalParameters);
1190        
1191        // <coordonnees-organisme>
1192        _saxProgramOrgUnitDetails(contentHandler, program, additionalParameters);
1193        
1194        // <contact-organisme>
1195        _saxProgramContactOrgUnit(contentHandler, program, additionalParameters);
1196        
1197        // <extras>
1198        _saxResponsibleOrgUnitExtras(contentHandler, program, additionalParameters);
1199        
1200        XMLUtils.endElement(contentHandler, "organisme-formation-responsable");
1201    }
1202
1203    /**
1204     * Sax the XML tag &lt;numero-activite&gt;
1205     * <br>The value contains exactly 11 characters
1206     * @param contentHandler the content handler
1207     * @param program the program to sax
1208     * @param additionalParameters the additional parameters
1209     * @throws SAXException if a saxing exception occurred
1210     */
1211    protected void _saxProgramActivityNumber(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1212    {
1213        // In default implementation, the program activity number value is always '00000000000'
1214        LHEOUtils.createLHEOElement(contentHandler, program, "numero-activite", "00000000000");
1215    }
1216
1217    /**
1218     * Sax the XML tag &lt;SIRET-organisme-formation&gt;
1219     * <br>Can contain the following XML tags:
1220     * <br>[1,1] &lt;SIRET&gt;
1221     * <br>[0,N] &lt;extras&gt;
1222     * @param contentHandler the content handler
1223     * @param program the program to sax
1224     * @param additionalParameters the additional parameters
1225     * @throws SAXException if a saxing exception occurred
1226     */
1227    protected void _saxProgramSIRETInformation(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1228    {
1229        XMLUtils.startElement(contentHandler, "SIRET-organisme-formation");
1230        
1231        // <SIRET>
1232        _saxProgramSIRET(contentHandler, program, additionalParameters);
1233        
1234        // <extras>
1235        _saxSIRETInformationExtras(contentHandler, program, additionalParameters);
1236        
1237        XMLUtils.endElement(contentHandler, "SIRET-organisme-formation");
1238    }
1239
1240    /**
1241     * Sax the XML tag &lt;SIRET&gt;
1242     * <br>The value contains exactly 14 characters
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 _saxProgramSIRET(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1249    {
1250        Optional<OrgUnit> orgUnit = program.getOrgUnits()
1251                .stream()
1252                .filter(StringUtils::isNotBlank)
1253                .map(id -> _getOrgUnit(id))
1254                .filter(Objects::nonNull)
1255                .findFirst();
1256        
1257        LHEOUtils.createMandatoryLHEOElement(contentHandler, orgUnit.isPresent() ? orgUnit.get() : program, "SIRET", orgUnit.isPresent() ? orgUnit.get().getSIRET() : null, 14, 14); 
1258    }
1259
1260    /**
1261     * Sax for SIRET information the XML tag &lt;extras&gt;
1262     * <br>Can contains all not LHEO normalized elements
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 _saxSIRETInformationExtras(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1269    {
1270        // No extras in default implementation
1271        XMLUtils.createElement(contentHandler, "extras");
1272    }
1273    
1274    /**
1275     * Sax the XML tag &lt;nom-organisme&gt;
1276     * <br>The value contains between 1 to 255 characters
1277     * @param contentHandler the content handler
1278     * @param program the program to sax
1279     * @param additionalParameters the additional parameters
1280     * @throws SAXException if a saxing exception occurred
1281     */
1282    protected void _saxProgramOrgUnitName(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1283    {
1284        Optional<OrgUnit> orgUnit = program.getOrgUnits()
1285                .stream()
1286                .filter(StringUtils::isNotBlank)
1287                .map(id -> _getOrgUnit(id))
1288                .filter(Objects::nonNull)
1289                .findFirst();
1290        
1291        LHEOUtils.createMandatoryLHEOElement(contentHandler, orgUnit.isPresent() ? orgUnit.get() : program, "nom-organisme", orgUnit.isPresent() ? orgUnit.get().getTitle() : null, 1, 255);
1292    }
1293
1294    /**
1295     * Sax the XML tag &lt;raison-sociale&gt;
1296     * <br>The value contains between 1 to 255 characters
1297     * @param contentHandler the content handler
1298     * @param program the program to sax
1299     * @param additionalParameters the additional parameters
1300     * @throws SAXException if a saxing exception occurred
1301     */
1302    protected void _saxProgramCorporateName(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1303    {
1304        Optional<OrgUnit> orgUnit = program.getOrgUnits()
1305                .stream()
1306                .filter(StringUtils::isNotBlank)
1307                .map(id -> _getOrgUnit(id))
1308                .filter(Objects::nonNull)
1309                .findFirst();
1310        
1311        LHEOUtils.createMandatoryLHEOElement(contentHandler, orgUnit.isPresent() ? orgUnit.get() : program, "raison-sociale", orgUnit.isPresent() ? orgUnit.get().getTitle() : null, 1, 255);
1312    }
1313
1314    /**
1315     * Sax the XML tag &lt;coordonnees-organisme&gt;
1316     * <br>Can contain the following XML tags:
1317     * <br>[1,1] &lt;coordonnees&gt;
1318     * <br>[0,N] &lt;extras&gt;
1319     * @param contentHandler the content handler
1320     * @param program the program to sax
1321     * @param additionalParameters the additional parameters
1322     * @throws SAXException if a saxing exception occurred
1323     */
1324    protected void _saxProgramOrgUnitDetails(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1325    {
1326        XMLUtils.startElement(contentHandler, "coordonnees-organisme");
1327        
1328        // <coordonnees>
1329        _saxOrgUnitDetailsCoordinates(contentHandler, program, additionalParameters);
1330        
1331        // <extras>
1332        _saxOrgUnitDetailsExtras(contentHandler, program, additionalParameters);
1333        
1334        XMLUtils.endElement(contentHandler, "coordonnees-organisme");
1335    }
1336
1337    /**
1338     * Sax for orgUnit details the XML tag &lt;coordonnees&gt;
1339     * <br>Can contain the following XML tags:
1340     * <br>[0,1] &lt;civilite&gt;
1341     * <br>[0,1] &lt;nom&gt;
1342     * <br>[0,1] &lt;prenom&gt;
1343     * <br>[0,3] &lt;ligne&gt;
1344     * <br>[0,1] &lt;adresse&gt;
1345     * <br>[0,1] &lt;telfixe&gt;
1346     * <br>[0,1] &lt;portable&gt;
1347     * <br>[0,1] &lt;fax&gt;
1348     * <br>[0,1] &lt;courriel&gt;
1349     * <br>[0,1] &lt;web&gt;
1350     * <br>[0,N] &lt;extras&gt;
1351     * @param contentHandler the content handler
1352     * @param program the program to sax
1353     * @param additionalParameters the additional parameters
1354     * @throws SAXException if a saxing exception occurred
1355     */
1356    protected void _saxOrgUnitDetailsCoordinates(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1357    {
1358        XMLUtils.startElement(contentHandler, "coordonnees");
1359        
1360        Optional<Person> personOpt = program.getOrgUnits()
1361            .stream()
1362            .filter(StringUtils::isNotBlank)
1363            .map(id -> _getOrgUnit(id))
1364            .filter(Objects::nonNull)
1365            .map(OrgUnit::getContacts)
1366            .flatMap(List::stream)
1367            .filter(StringUtils::isNotBlank)
1368            .map(id -> _getPerson(id))
1369            .filter(Objects::nonNull)
1370            .findFirst();
1371            
1372        if (personOpt.isPresent())
1373        {
1374            Person person = personOpt.get();
1375            _saxPersonCoordinate(contentHandler, person, additionalParameters);
1376        }
1377        
1378        XMLUtils.endElement(contentHandler, "coordonnees");
1379    }
1380    
1381    /**
1382     * Sax for orgUnit details the XML tag &lt;extras&gt;
1383     * <br>Can contains all not LHEO normalized elements
1384     * @param contentHandler the content handler
1385     * @param program the program to sax
1386     * @param additionalParameters the additional parameters
1387     * @throws SAXException if a saxing exception occurred
1388     */
1389    protected void _saxOrgUnitDetailsExtras(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1390    {
1391        // No extras in default implementation
1392        XMLUtils.createElement(contentHandler, "extras");
1393    }
1394    
1395    /**
1396     * Sax the XML tag &lt;contact-organisme&gt;
1397     * <br>Can contain the following XML tags:
1398     * <br>[1,1] &lt;coordonnees&gt;
1399     * <br>[0,N] &lt;extras&gt;
1400     * @param contentHandler the content handler
1401     * @param program the program to sax
1402     * @param additionalParameters the additional parameters
1403     * @throws SAXException if a saxing exception occurred
1404     */
1405    protected void _saxProgramContactOrgUnit(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1406    {
1407        XMLUtils.startElement(contentHandler, "contact-organisme");
1408        
1409        // <coordonnees>
1410        _saxContactOrgUnitCoordinates(contentHandler, program, additionalParameters);
1411        
1412        // <extras>
1413        _saxContactOrgUnitExtras(contentHandler, program, additionalParameters);
1414        
1415        XMLUtils.endElement(contentHandler, "contact-organisme");
1416    }
1417    
1418    /**
1419     * Sax for contact orgUnit the XML tag &lt;coordonnees&gt;
1420     * <br>Can contain the following XML tags:
1421     * <br>[0,1] &lt;civilite&gt;
1422     * <br>[0,1] &lt;nom&gt;
1423     * <br>[0,1] &lt;prenom&gt;
1424     * <br>[0,3] &lt;ligne&gt;
1425     * <br>[0,1] &lt;adresse&gt;
1426     * <br>[0,1] &lt;telfixe&gt;
1427     * <br>[0,1] &lt;portable&gt;
1428     * <br>[0,1] &lt;fax&gt;
1429     * <br>[0,1] &lt;courriel&gt;
1430     * <br>[0,1] &lt;web&gt;
1431     * <br>[0,N] &lt;extras&gt;
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 _saxContactOrgUnitCoordinates(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1438    {
1439        XMLUtils.startElement(contentHandler, "coordonnees");
1440        
1441        Optional<Person> personOpt = program.getContacts()
1442                .stream()
1443                .filter(StringUtils::isNotBlank)
1444                .map(id -> _getPerson(id))
1445                .filter(Objects::nonNull)
1446                .findFirst();
1447        
1448        if (personOpt.isPresent())
1449        {
1450            Person person = personOpt.get();
1451            _saxPersonCoordinate(contentHandler, person, additionalParameters);
1452        }
1453        
1454        XMLUtils.endElement(contentHandler, "coordonnees");
1455    }
1456    
1457    /**
1458     * Sax for contact orgUnit the XML tag &lt;extras&gt;
1459     * <br>Can contains all not LHEO normalized elements
1460     * @param contentHandler the content handler
1461     * @param program the program to sax
1462     * @param additionalParameters the additional parameters
1463     * @throws SAXException if a saxing exception occurred
1464     */
1465    protected void _saxContactOrgUnitExtras(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1466    {
1467        // No extras in default implementation
1468        XMLUtils.createElement(contentHandler, "extras");
1469    }
1470    
1471    /**
1472     * Sax for responsible orgUnit the XML tag &lt;extras&gt;
1473     * <br>Can contains all not LHEO normalized elements
1474     * @param contentHandler the content handler
1475     * @param program the program to sax
1476     * @param additionalParameters the additional parameters
1477     * @throws SAXException if a saxing exception occurred
1478     */
1479    protected void _saxResponsibleOrgUnitExtras(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1480    {
1481        // No extras in default implementation
1482        XMLUtils.createElement(contentHandler, "extras");
1483    }
1484    
1485    /**
1486     * Sax the XML tag &lt;code-RNCP&gt;
1487     * <br>The value contains between 1 and 6 characters
1488     * @param contentHandler the content handler
1489     * @param program the program to sax
1490     * @param additionalParameters the additional parameters
1491     * @throws SAXException if a saxing exception occurred
1492     */
1493    protected void _saxProgramRNCPCode(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1494    {
1495        Optional<String> rncpCode = Stream.of(program.getRncpCode())
1496                .filter(StringUtils::isNotBlank)
1497                .findFirst();
1498        
1499        LHEOUtils.createLHEOElement(contentHandler, program, "code-RNCP", rncpCode.isPresent() ? rncpCode.get() : null, 1, 6);
1500    }
1501    
1502    /**
1503     * Sax the XML tag &lt;code-CERTIFINFO&gt;
1504     * <br>The value contains between 1 and 6 characters
1505     * @param contentHandler the content handler
1506     * @param program the program to sax
1507     * @param additionalParameters the additional parameters
1508     * @throws SAXException if a saxing exception occurred
1509     */
1510    protected void _saxProgramCERTIFINFOCode(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1511    {
1512        // No CERTIFINFO code in default implementation
1513    }
1514    
1515    /**
1516     * Sax for certification the XML tag &lt;extras&gt;
1517     * <br>Can contains all not LHEO normalized elements
1518     * @param contentHandler the content handler
1519     * @param program the program to sax
1520     * @param additionalParameters the additional parameters
1521     * @throws SAXException if a saxing exception occurred
1522     */
1523    protected void _saxCertificationExtras(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1524    {
1525        // No extras in default implementation
1526        XMLUtils.createElement(contentHandler, "extras");
1527    }
1528    
1529    /**
1530     * Sax for programs the XML tag &lt;extras&gt;
1531     * <br>Can contains all not LHEO normalized elements
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 _saxProgramsExtras(ContentHandler contentHandler, Program program, Map<String, Object> additionalParameters) throws SAXException
1538    {
1539        // No extras in default implementation
1540        XMLUtils.createElement(contentHandler, "extras");
1541    }
1542    
1543    /**
1544     * Sax for offers the XML tag &lt;extras&gt;
1545     * <br>Can contains all not LHEO normalized elements
1546     * @param contentHandler the content handler
1547     * @param programs the list of program to sax
1548     * @param additionalParameters the additional parameters
1549     * @throws SAXException if a saxing exception occurred
1550     */
1551    protected void _saxOffersExtras(ContentHandler contentHandler, List<Program> programs, Map<String, Object> additionalParameters) throws SAXException
1552    {
1553        // No extras in default implementation
1554        XMLUtils.createElement(contentHandler, "extras");
1555    }
1556    
1557    /**
1558     * Sax LHEO elements of a ametys person for the XML tag &gt;coordonnees&lt;
1559     * @param contentHandler the content handler
1560     * @param contact the person contact
1561     * @param additionalParameters the additional parameters
1562     * @throws SAXException if a saxing exception occurred
1563     */
1564    protected void _saxPersonCoordinate(ContentHandler contentHandler, Person contact, Map<String, Object> additionalParameters) throws SAXException
1565    {
1566        LHEOUtils.createCoordinateLHEOElementsPart1(
1567                contentHandler,
1568                contact,
1569                contact.getValue("personTitle"), // civilite 
1570                contact.getValue("lastName"), // nom
1571                contact.getValue("givenName"), // prenom
1572                (String) contact.getValue("address") // ligne
1573        );
1574        
1575        XMLUtils.startElement(contentHandler, "adresse");
1576        LHEOUtils.createAddressLHEOElements(
1577                contentHandler,
1578                contact,
1579                (String) contact.getValue("address"), // ligne
1580                contact.getValue("zipCode"), // codepostal
1581                contact.getValue("town"), // ville
1582                null, // departement
1583                null, // code-INSEE-commune
1584                null, // code-INSEE-canton
1585                null, // region
1586                null, // pays
1587                null, // geolocalisation/latitude
1588                null // geolocalisation/longitude
1589        );
1590        XMLUtils.endElement(contentHandler, "adresse");
1591        
1592        LHEOUtils.createCoordinateLHEOElementsPart2(
1593                contentHandler,
1594                contact,
1595                contact.getValue("phone"), // telfix
1596                null, // portable
1597                contact.getValue("fax"), //fax
1598                contact.getValue("mail"), // courriel
1599                contact.getValue("webLinkUrl") // web
1600        );
1601    }
1602    
1603    /**
1604     * Get ref content from id
1605     * @param id the ref content id
1606     * @return the ref content. Null if no exist
1607     */
1608    protected Content _getRefContent(String id)
1609    {
1610        try
1611        {
1612            return _resolver.resolveById(id);
1613        }
1614        catch (Exception e) 
1615        {
1616            getLogger().warn("Can't find person with id " + id, e);
1617            return null;
1618        }
1619    }
1620    
1621    /**
1622     * Get person from id
1623     * @param id the person id
1624     * @return the person content. Null if no exist
1625     */
1626    protected Person _getPerson(String id)
1627    {
1628        try
1629        {
1630            return _resolver.resolveById(id);
1631        }
1632        catch (Exception e) 
1633        {
1634            getLogger().warn("Can't find person with id " + id, e);
1635            return null;
1636        }
1637    }
1638    
1639    /**
1640     * Get orgUnit from id
1641     * @param id the orgUnit id
1642     * @return the orgUnit content. Null if no exist
1643     */
1644    protected OrgUnit _getOrgUnit(String id)
1645    {
1646        try
1647        {
1648            return _resolver.resolveById(id);
1649        }
1650        catch (Exception e) 
1651        {
1652            getLogger().warn("Can't find orgUnit with id " + id, e);
1653            return null;
1654        }
1655    }
1656    
1657    /**
1658     * Convert rich-text to string for LHEO
1659     * @param richText the rich-text
1660     * @return the transformed rich-text
1661     */
1662    protected String _richText2String(RichText richText)
1663    {
1664        return richText != null ? _richTextHelper.richTextToString(richText) : null;
1665    }
1666}